胖虎谈ImageLoader框架(一)

前言

从学校出来的这半年时间,发现很少有时间可以静下来学习和写博文了,为了保持着学习和分享的习惯,我准备之后每周抽出一部分时间为大家带来一个优秀的Android框架源码阅读后的理解系列博文。

期许:希望可以和大家一起学习好此框架,也希望大家看博文前最好是先了解下框架的基本使用场景和使用方法,有什么问题可以留言给我,交流学习。

当然,再好的博文,也不如自己看一遍源码!



这周为大家带来的是《胖虎谈ImageLoader框架》系列,分析优秀的框架源码能让我们更迅速地提升,大家共勉!!

源码包下载地址:http://download.csdn.net/detail/u011133213/9210765

希望我们尊重每个人的成果,转载请注明出处。

转载于:CSDN 胖虎 , http://blog.csdn.net/ljphhj


正文

框架的重要性:读者在读此文前,也许并未使用过ImageLoader框架,但如果你是一个Android应用开发者,我觉得这个框架是你必须要学会使用的。当然如果你阅读了详细的源码后,理解了,应该收获更大。

框架的用处:异步加载图片(本地图片or网络图片), 结合上Android控件:Listview, GridView,ViewPager,Gallery等任何一个你想要显示图片的地方。

框架中的包结构:

框架使用流程梳理:

为了让大家更容易看清楚整个ImageLoader的使用流程,我先简单归结为以下三步,请务必先看明白这3步再继续往下。

1.开发者使用异步图片加载框架,实例化出ImageLoaderConfiguration对象后,根据自己的需要配置了相应的参数值。(如果开发者需要图片加载显示出来的时候对图片做一些其他统一操作,可以为ImageLoaderConfiguration对象设置一个DisplayImageOptions,当然在第3步中也可以做此设置)

2.ImageLoader.getInstance()获取到单例ImageLoader对象,调用init(ImageLoaderConfiguration)进行ImageLoader中成员变量ImageLoaderConfiguration的初始化。

3.开发者在需要加载图片时,调用ImageLoader.displayImage(String uri, ImageAware imageAware)



居然此博文写到这里,那么这篇博文就不便再继续讲其他内容了,否则感觉有点杂乱了。那么我们来把这3步里面涉及到的类和方法分析下,就结束此博文。

DisplayImageOptions.java :

//加载图片的几种情况显示的图片资源R.drawable.....
//或者Drawable(情况:1.图片为空时  2.图片加载失败时  3.图片加载中)
private final int imageResOnLoading;
private final int imageResForEmptyUri;
private final int imageResOnFail;
private final Drawable imageOnLoading;
private final Drawable imageForEmptyUri;
private final Drawable imageOnFail; 

//加载图片前是否要重置view,其实就是ImageView.setImageDrawable(null);
private final boolean resetViewBeforeLoading;

//是否要把图片缓存在内存中
private final boolean cacheInMemory;

//是否要把图片缓存在SDcard上(可以指定缓存位置)
private final boolean cacheOnDisk;

//图片的缩放类型(NONE,NONE_SAFE,IN_SAMPLE_POWER_OF_2,IN_SAMPLE_INT,EXACTLY,EXACTLY_STRETCHED)
private final ImageScaleType imageScaleType;

//这个变量是BitmapFactory.Options类对象
//(图片压缩加载进来这个应该大家都知道,如果不清楚的,可以看我之前关于大图片加载的博文)
private final Options decodingOptions;

//加载前是否要先让线程停止一会儿Thread.sleep(delayBeforeLoading)
private final int delayBeforeLoading;

//图片文件中的Exif信息(大小,方向)是否需要考虑框架帮我们处理
private final boolean considerExifParams;

//开发者是否提供自己额外的下载器
private final Object extraForDownloader;

//图片缓存到内存前(如果没有设置缓存到内存,也会执行)和缓存到内存后执行的两个Process
private final BitmapProcessor preProcessor;
private final BitmapProcessor postProcessor;

//图片显示出来时做的统一操作
//(渐渐显示出来[FadeInBitmapDisplayer],圆角显示[RoundedBitmapDisplayer],普通显示[SimpleBitmapDisplayer])
private final BitmapDisplayer displayer;

//这个Handler和isSyncLoading是为了处理是否到线程池中执行
private final Handler handler;
private final boolean isSyncLoading;

ImageLoaderConfiguration.java :

//因为多处需要使用context.getResources()[如获取屏幕尺寸等],框架作者将其设为final对象
final Resources resources;

//缓存于内存中的图片最大的宽和高
final int maxImageWidthForMemoryCache;
final int maxImageHeightForMemoryCache;

//缓存于SDCard中的图片最大的宽和高
final int maxImageWidthForDiskCache;
final int maxImageHeightForDiskCache;

//保存于SDCard之前执行此Process
final BitmapProcessor processorForDiskCache;

//这两个后面会有一篇博文专门介绍,此处先点一下:
//两个线程池分别为执行加载图片的线程和执行加载缓存于SDcard中图片的线程
//后面还有一个线程池是做分发动作的
//[判断SDCard中有缓存会分发给taskExecutorForCachedImages去执行,否则分发给taskExecutor去执行]
final Executor taskExecutor;
final Executor taskExecutorForCachedImages;

//开发者是否有自定义线程池对象
final boolean customExecutor;
final boolean customExecutorForCachedImages;

//线程池中线程数的最大值
final int threadPoolSize;

//线程池中线程的优先级,默认是5
final int threadPriority;

//线程池中线程的调度方式,LIFO or FIFO[根据自己的需要做设定]
final QueueProcessingType tasksProcessingType;

//内存缓存和SDCard缓存,后面会出一篇博文详细介绍这两种缓存里面的几种策略
final MemoryCache memoryCache;
final DiskCache diskCache;

//下载器(分为:无网络的下载器[NetworkDeniedImageDownloader],
//慢网络时的下载器[SlowNetworkImageDownloader],
//默认的下载器[BaseImageDownloader])
final ImageDownloader downloader;

//框架默认为BaseImageDecoder,主要是用于图片加载入内存中时算出Scale,
//还有显示出来的时候通过Exif信息判断图片是否旋转
final ImageDecoder decoder;

//此为之前提的类,为显示做的一些操作
final DisplayImageOptions defaultDisplayImageOptions;

//两种网络情况时的下载器对象
final ImageDownloader networkDeniedDownloader;
final ImageDownloader slowNetworkDownloader;

介绍到这里,相比对第1步中几个主要涉及到的类,还有类成员中的一些作用大家都有了一定的认知了。那么接下来进行第2步的讲解。

ImageLoader.java:

首先ImageLoader这边我想把这个单例模式的写法稍微提一下,大牛们一笑而过即可。

private volatile static ImageLoader instance;

/** Returns singleton class instance */
public static ImageLoader getInstance() {
    if (instance == null) {
        synchronized (ImageLoader.class) {
            if (instance == null) {
                instance = new ImageLoader();
            }
        }
    }
    return instance;
}

protected ImageLoader() {
}

ImageLoader中值得我们深究的成员变量极少,因为主要的参数信息多数都放在前面介绍的两个类中了,这里我们说下它里面的几个变量和几个方法:

ImageLoader中的几个成员变量:

// init(...) 方法用于初始化此变量值和初始化ImageLoaderEngine
private ImageLoaderConfiguration configuration;
private ImageLoaderEngine engine;

//此变量为默认的图片加载监听回调的对象
private final ImageLoadingListener emptyListener = new SimpleImageLoadingListener();

ImageLoader中的几个主要方法:

//加载图片的方式一:同步加载[代码中其实只是将DisplayImageOptions中的isSyncLoading设置为了true
//最终还是调用loadImage函数]
public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options)

//加载图片的方式二:这种方式可以避免过ImageLoader重复加载同一个Uri但是只调用一次回调监听函数的问题。
//[想知道的,可以看看源码O(∩_∩)O] ,最终其实还是调用displayImage
public void loadImage(String uri,ImageSize targetImageSize,DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

//加载图片的方法三:所有加载图片的函数最终都会调用这个函数
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

终于要讲解displayImage(……)这个函数了,其实这个函数的阅读过程就是对整个ImageLoader框架的理解分析过程。

ImageLoader.displayImage(……):

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
    //检查ImageLoaderConfiguration是否初始化(之前第2步调用init就是为了初始化这个值)
    checkConfiguration();

    //ImageAware可以理解为等价于你传入的ImageView,只不过它里面对ImageView做了个包装,
    //相当于ImageView的包装类,内部把ImageView做为WeakReference<View>弱引用保存,
    //因此此ImageView可被GC回收,所以存在有当图片加载结束,或者未开始加载前ImageView被回收,
    //就无需做即将进行的操作,省去资源消耗
    if (imageAware == null) {
        throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
    }

    //传入的参数如果不需要对Image Loading时做监听,
    //那么默认赋值为emptyListener,内部回调函数里没有任何操作
    if (listener == null) {
        listener = emptyListener;
    }

    //没有传入指定的DisplayImageOptions,就用ImageLoaderConfiguration赋值时的这个对象值为准。
    if (options == null) {
        options = configuration.defaultDisplayImageOptions;
    }

    //如果传入的要加载的图片的URI是空的,引擎取消显示这个图片的任务,
    //并做出一些相应的动作
    //(如果开发者指定了没有该图片要显示默认图片等)
    if (TextUtils.isEmpty(uri)) {
        engine.cancelDisplayTaskFor(imageAware);
        listener.onLoadingStarted(uri, imageAware.getWrappedView());
        if (options.shouldShowImageForEmptyUri()) {
            imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
        } else {
            imageAware.setImageDrawable(null);
        }
        listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
        return;
    }

    //计算这个图片的大小(如果ImageView的大小小于定义的最大宽和高,
    //那么就用ImageView的大小作为该图片的大小)
    ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware,
    configuration.getMaxImageSize());

    //图片在内存中缓存是以Map<String, Reference<Bitmap>>的形式存储的,
    //因此需要定义一种方式来生成一个唯一可以标示图片的Key,
    //作者采用了URI_W*H的方式作为Key值。
    String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
    engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);

    listener.onLoadingStarted(uri, imageAware.getWrappedView());

    //先到内存中查询是否已经存在此Key值对应的Bitmap
    Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);

    //图片存在&&没被回收,显示出来。
    //(这里需要做一些操作:即之前提到的几个类中说的,
    //比如在图片加载入内存后做的操作,
    //还有就是把图片显示到控件的Task任务submit到taskExecutorForCachedImages线程池中去排队执行)
    if (bmp != null && !bmp.isRecycled()) {
        L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);

        if (options.shouldPostProcess()) {
            ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize,
            memoryCacheKey,options, listener, progressListener, engine.getLockForUri(uri));
            //内存中存在有此图片,只需要执行ProcessAndDisplayImageTask任务即可
            ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp,
            imageLoadingInfo,defineHandler(options));
            if (options.isSyncLoading()) {
                displayTask.run();
            } else {
                //提给taskExecutorForCachedImages线程池去执行
                engine.submit(displayTask);
            }
        } else {
            options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
            listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
        }
    } else {
        if (options.shouldShowImageOnLoading()) {
            imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
        } else if (options.isResetViewBeforeLoading()) {
            imageAware.setImageDrawable(null);
        }

        ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize,
        memoryCacheKey,options, listener, progressListener, engine.getLockForUri(uri));

        //内存中没有此图片,需要执行LoadAndDisplayImageTask任务,
        //这个LoadAndDisplayImageTask是整个ImageLoader的核心,下篇博文会详细讲解
        LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine,
        imageLoadingInfo,defineHandler(options));
        if (options.isSyncLoading()) {
            displayTask.run();
        } else {
            engine.submit(displayTask);
        }
    }
}

总结

其实这篇博文旨在将ImageLoader的基本流程简单梳理,并没有更深入地介绍ImageLoader中的内存和SDcard缓存的机制,线程池的调度机制等知识,之后的几篇系列博文会陆续将这些知识点补上,本文最后贴张ImageLoader官方提供的流程图片,大家看完此博文后,可以对照下此图来做一个理解。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-06 15:48:10

胖虎谈ImageLoader框架(一)的相关文章

胖虎谈ImageLoader框架(四)

前言 从学校出来的这半年时间,发现很少有时间可以静下来学习和写博文了,为了保持着学习热情,我希望自己抽出一部分时间来写一些Android框架源码阅读后的理解系列博文. 期许:希望可以和大家一起学习好此框架,也希望大家看博文前最好是先了解下框架的基本使用场景和使用方法,有什么问题可以留言给我,交流学习. 当然,再好的博文,也不如自己看一遍源码! 这次为大家带来的是<胖虎谈ImageLoader框架>系列,分析优秀的框架源码能让我们更迅速地提升,大家共勉!! 源码包下载地址:http://down

浅谈数据库框架,见笑,请多指正

浅谈数据库框架,见笑,请多指正 http://weibo.com/p/1001603724746155003486 一友说"插件式存储又割裂了SQL引擎的完整逻辑...总体而言在现有框架下MySQL的优化器没有多大改进的价值". 我们且做个技术分析: 1 插件式框架,可以静态/动态加载组件,方便在同类不同属家的模块间切换,这种设计是良好的. 很多软件的设计都采用了"微内核+插件"这样的方式构筑了强大的应用.如Ecplise生态圈. 2 数据库范围内, MySQL的属

2014-07-29 浅谈MVC框架中Razor与ASPX视图引擎

今天是在吾索实习的第15天.随着准备工作的完善,我们小组将逐步开始手机端BBS的开发,而且我们将计划使用MVC框架进行该系统的开发.虽然我们对MVC框架并不是非常熟悉,或许这会降低我们开发该系统的效率,但是我们可以通过边学边做的方式来实现其开发的.这不仅便于我们日后对系统的管理与维护,而且还给我们带来一个学习的动力与实践的地方. 但我们在创建一个基于MVC框架的项目时,就遇到一些问题了.那就是MVC的视图引擎是有两种的,一种是Razor,会以cshtml后缀的文件作为视图文件:另一种是ASPX,

教你写Android ImageLoader框架之初始配置与请求调度

## 前言 在教你写Android ImageLoader框架之基本架构中我们对SimpleImageLoader框架进行了基本的介绍,今天我们就从源码的角度来剖析ImageLoader的设计与实现.   在我们使用ImageLoader前都会通过一个配置类来设置一些基本的东西,比如加载中的图片.加载失败的图片.缓存策略等等,SimpleImageLoader的设计也是如此.配置类这个比较简单,我们直接看源码吧. ImageLoaderConfig配置 /** * ImageLoader配置类,

教你写Android ImageLoader框架之图片缓存 (完结篇)

在教你写Android ImageLoader框架系列博文中,我们从基本架构到具体实现已经更新了大部分的内容.今天,我们来讲最后一个关键点,即图片的缓存.为了用户体验,通常情况下我们都会将已经下载的图片缓存起来,一般来说内存和本地都会有图片缓存.那既然是框架,必然需要有很好的定制性,这让我们又自然而然的想到了抽象.下面我们就一起来看看缓存的实现吧. 缓存接口 在教你写Android ImageLoader框架之图片加载与加载策略我们聊到了Loader,然后阐述了AbsLoader的基本逻辑,其中

【军哥谈CI框架】之入门教程之第二讲:分析CI结构和CI是怎么工作的

[军哥谈CI框架]之入门教程之第二讲:分析CI结构和CI是怎么工作的 By hzp123   at 2014-02-18   265 阅读   0 回复   0.0 希赛币 之入门教程之第二讲:分析CI结构和CI是如何工作的大家好!上一节,我们共同部署了一个CI网站,做到这一点非常简单,但是,亲们,要知道这才刚刚开始额~~~ 这一节,我们主要来了解CI的文件结构和CI是如何工作的.这一点特别的重要! 一.CI的文件结构:了解CI的文件结构可以帮助我们快速的对CI框架有一个整体的认识,就好像我们去

教你写Android ImageLoader框架之图片加载与加载策略

在教你写Android ImageLoader框架之初始配置与请求调度中,我们已经讲述了ImageLoader的请求配置与调度相关的设计与实现.今天我们就来深入了解图片的具体加载过程以及加载的策略(包括按顺序加载和逆序加载) ,在这其中我会分享我的一些设计决策,也欢迎大家给我提建议. 图片的加载 Loader与LoaderManager的实现 在上一篇文章教你写Android ImageLoader框架之初始配置与请求调度中,我们聊到了Loader与LoaderManager. ImageLoa

浅谈angular框架

最近新接触了一个js框架angular,这个框架有着诸多特性,最为核心的是:MVVM.模块化.自动化双向数据绑定.语义化标签.依赖注入,以上这些全部都是属于angular特性,虽然说它的功能十分的强大,在开发中非常的实用,对于初接触者来说,要想熟练使用仍然需要一些时间来熟悉其中基础的语法规则,一些基本的用法,虽然说代码不需要死记硬背,但是对于新学习一个知识点,记住其中的一些基础概念,基本的用法,在这里不是提倡大家去背一些代码,该处谈到的熟悉指的是自己多动手去写,实在是想不起了再查看资料,尽量凭借

教你写Android ImageLoader框架之基本架构 .

前言 在Android开发中,ImageLoader应该算得上是最重要的开源库之一,由于项目原因(不能使用开源库),前段时间自己也是需要实现一个简单的ImageLoader,因此诞生了这个库,我们暂且叫它为SimpleImageLoader.就目前而言,你上网查ImageLoader资料的时候,基本上能够找到很简单的实现,基本上一个类就把所有的工作给做了,这就显得很不专业了嘛,很多时候我们不只是需要实现功能,而是希望能够在实现功能的同时在设计层面有所提升. SimpleImageLoader分享