该文章的基本思路借鉴于CodeKK源码分析
既然已经有了这么好的一篇Universal Image Loader源码分析的文章,那么为什么还要写下来呢?
原因很简单,我这几天都在看开源项目,东看看,西瞧瞧,没什么收获,后来觉得还不如仔细先研究一篇
并且要吃透,然后再去研究其他的,如果是泛泛而看很容易遗忘,对于我们这些菜鸟来说,好的东西需要
常常拿来研究,不断的去琢磨为什么要这么做,这才是王道。
开源项目源码https://github.com/nostra13/Android-Universal-Image-Loader
用起来是特别的简单,主要是两个功能,加载和显示。
ImageLoader
该类被设计成单例,通过他的静态getInstance()方法我们可以初始化出他的一个实例。
通常,我们选择在Application这个节点对其进行初始化。
ImageLoader.getInstance().init(new ImageLoaderConfiguration.Builder(this).build());
先声明一下,我之前看分析的时候,最困惑的就是各种类是在什么时候被初始化的,他的各种默认成员变量是什么等等,这估计是比较菜的原因,所以,我会更加强调类于类之间的关系。
在init方法中我们需要ImageLoaderConfiguration这个类,那么我们看它拿了这个类做了些什么。
if (this.configuration == null) {
L.d(LOG_INIT_CONFIG);
engine = new ImageLoaderEngine(configuration);
this.configuration = configuration;
}
他保存了这个类的引用,同时利用这个引用又把ImageLoaderEngine初始化了(也是核心类)。
我还想再看看ImageLoaderConfiguration利用Builder模式是怎样初始化的,为什么要这样?后面有许多类都是使用这个模式进行初始化的。
Builder是他的静态内部类。使用它来进行初始化是因为参数太多,但是呢,Builder中的参数又可以归为
一类,即在图片从网络到本地,显示的过程中,需要很多的参数来完成这一过程,我们可以把不同时期所需要的
参数封装在一个类里面。那么build()方法之后调用了initEmptyFieldsWithDefaultValues(),即用默认的参数初始化出一个ImageLoaderConfiguration的实例。
有了ImageLoader的实例之后,我们当然要使用了,比如,在ImageView上显示图片,从某个Url加载图片。
咱还是直接看怎么显示图片吧。只要是显示图片,都会调用这个函数
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
如果之前你没有指定什么listener,options,那么,他们在这里被赋予默认值,即通过默认的构造函数构造出来。这里面比较绕的就是DisplayImageOptions 的初始化。它是之前的ImageLoaderConfiguration的一个参数,参数之中还有参数,就像我说的,不同的阶段需要不同的参数,根据名字,我们可以大致判断,这是在显示图片时需要用到的参数。在之前初始化ImageLoaderConfiguration的时候,我们已经初始化出一个defaultDisplayImageOptions了,可能你还不知道。
if (defaultDisplayImageOptions == null) {
defaultDisplayImageOptions = DisplayImageOptions.createSimple();
}
public static DisplayImageOptions createSimple() {
return new Builder().build();
}
他同样也是利用Builder模式来进行初始化的。我们可以大致看看他的Builder里面存放了哪些参数。
如imageResForEmptyUri,如果用户给的是一个空的uri,我们需要显示的默认图片的id,还有我们最后需要用来显示图片的BitmapDisplayer等等。
接下来的事情就是,确定ImageAware的大小,利用这个大小和uri得到memoryCacheKey。然后,engine
将ImageAware的id和memoryCacheKey放到他内部的map中。接着我们尝试着从内存缓存中拿出刚才的
key所对应的bitmap。
1.假如拿到了,并且没有被回收。
进行一个判断,我们是否要对这个bitmap进行一个后期处理,然后再显示在ImageView上。
a. 需要后期初处理
我们就new出一个ProcessAndDisplayImageTask,看名字就知道,先处理再显示。该类实现了Runnable接口,看看他做了哪些事。
Bitmap processedBitmap = processor.process(bitmap);
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE);
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
处理完bitmap之后,new出一个DisplayBitmapTask,那么,怎么让这个显示任务跑起来呢,结果他调用了LoadAndDisplayImageTask的一个静态方法,其好处是他内部会有一个同步异步的判断。
b.不要处理后期处理
如果不需要后期处理,那我们直接显示就可以了。
2 假如没有拿到
重点在这
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, defineHandler(options));
new出一个加载和显示的任务,很明显,先加载,再显示。然后再看看他的run方法吧。
在run方法里面,他仍然不死心,先去内存缓存里面找一找,没找到的话,就tryLoadBitmap()
在这个方法中,我们会去网络加载bitmap,当然,这其中会发生任务取消的异常,抛出。
第一步:先到磁盘缓存里面看一看,没有的话,就tryCacheImageOnDisk(),这是真正下载图片的地方。
后面会走到downloadImage()的这一步
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
return configuration.diskCache.save(uri, is, this);
他把从网络拿到的流存到了磁盘上,而并不是直接解析成bitmap再存入本地的。这一前提是之前允许保存到磁盘。如果没有,那么直接调用
bitmap = decodeImage(imageUriForDecoding);
将网络流解析成图片。
在run方法的最后
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
将我们得到的bitmap显示出来。
这只是一个大致的流程梳理,后面我将关注更多的细节问题。