首先,本人非常钦佩和仰慕 Android-Universal-Image-Loader 的作者:Sergey Tarasevich
关于风:
自己从没有写过类似源码品读这样的博客,瞬间觉得有点压力和激动。一方面是这么一个写的如此精妙和伟大的库,让我等小辈去评头论足,实在显得不够尊重。另一方面则是,如此一个广泛使用的,从2011年就面世的库,一旦自己能力不够,导致品读的不到位,抑或是误导了广大的用户,这个责任就难当了。所以此时此刻,自己的心情是忐忑的,是惶恐的,是复杂和纠结的。
在此,先感谢 Sergey Tarasevich,再感谢各位读者。
品读这么伟大的库,一篇博客是不够的,那么两篇?也是不够的!废话少说!
首先抛出一个问题:如何阅读一个开源库?从什么地方入手?
我的思路是从如何使用方面入手,比如Universal-Image-Loader库的使用方式:
1.在App的入口处全局配置ImageLoader.
public static void initImageLoader(Context context) {
// This configuration tuning is custom. You can tune every option, you may tune some of them,
// or you can create default configuration by
// ImageLoaderConfiguration.createDefault(this);
// method.
ImageLoaderConfiguration.Builder config = new
ImageLoaderConfiguration.Builder(context);
config.threadPriority(Thread.NORM_PRIORITY - 2);
config.denyCacheImageMultipleSizesInMemory();//拒绝在内存中缓存2种size的图片策略
config.diskCacheFileNameGenerator(new Md5FileNameGenerator());//磁盘缓存文件名的产生方案,你选择采用其他策略
config.diskCacheSize(50 * 1024 * 1024); // 磁盘缓存大小 50 MiB
config.tasksProcessingOrder(QueueProcessingType.LIFO);//配置任务队列的执行方式
config.writeDebugLogs(); // Remove for release app
// Initialize ImageLoader with configuration.
ImageLoader.getInstance().init(config.build());
}
ImageLoader的可配置的信息非常丰富,上面所列的只是一部分。这种全局配置的策略是很值推广,ImageLoader本身有自己的默认策略,但是默认毕竟是默认的,无法满足不同业务的需求。所以我们在开发一款工具类开源库的时候,可以将这种插件式的设计思想引入到你的库当中。
2.图片显示调用:ImageLoader.getInstance().displayImage()
这个方法的背后隐藏了这个库的所有秘密。ImageLoader.getInstance()采用单例的设计模式:
/** Returns singleton class instance */
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
双重检查的单例模式,单例模式的实现方法之一,保证全局只有一个实例存在,节约了频繁创建和销毁对象时系统资源的开销,提高了我们系统的性能。
获取ImageLoader的实例之后,就开始调用displayImage()方法去显示图片了,从源码中我们可以看到,在ImageLoader这个类中多次重载了displayImage()的方法。但是核心的方法往往只有一个,而且这个核心的方法也非常有意思,我们一起来看看。
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();
... //此处省略部分源码
if (TextUtils.isEmpty(uri)) {//过滤url为空的情况
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));//显示你配置url为空时显示的图片
} else {
imageAware.setImageDrawable(null);//如果没有配置,则显示空图
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);//回调完成接口给上层用户
return;
}
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);//根据图片url生产一个键
//将imageView的hashcode与url产生的键做一个map映射
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//获取内存中缓存的bitmap
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {//如果内存中存在bmp
if (options.shouldPostProcess()) {//判断是否需要异步加载图片
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
} else {//直接加载图片
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
... //此处省略部分源码
//下面是处理图片的网络下载或者加载本地磁盘缓存图片的逻辑
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
}
}
代码有点多,但是写的非常精彩,在这里我不做展开,主要是帮大家屡一下图片的显示逻辑,所有的逻辑都隐藏在代码中。下面是我画的一个流程图
图片加载流程图流程图画的不是很好,但是可以说明问题。好了,源码再结合流程图应该可以很好的说明了ImageLoader加载图片的主要逻辑。后面的几篇博客我会继续解读ImageLoader源码。会有这么几个方面:
1. ImageLoaderEngine 的源码解读
2. BaseImageDownloader 的源码解读
3. ImageLoader 的内存缓存策略
4. ImageLoader 的磁盘缓存策略
5. ImageLoader 的接口编程思想
有问题欢迎留言!
网友评论