刚接触android的时候看到项目里面用到了ImageLoader这个图片缓存插件,当初抱着“知其然必要知其所以然”的想法还专门下载了它的源码没头苍蝇似的毫无章法的去看了看,当然最终因为对android相关知识的了解有限而作罢。本篇也不会细致深入的对此多做说明,只是对ImageLoader的创建过程等略作梳理,方便以后的使用。
在这里,从使用的源头开始说起慢慢抽丝拨茧,ImagerLoader提供了如下的来加载展示图片的方法:
通常直接会使用上图中的前五个来,至于的五个接口十八ImageView先包装秤ImageAware来使用。
在使用上图中的前五个方法的时候,简单查看源码:
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
displayImage(uri, new ImageViewAware(imageView), options, null, null);
}
就是把imageView封装成ImageViewAware后传给了如下方法:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener)
所以直接分析上面的方法就可以!
进入displayImage这个参数最多的方法,其实上图中的其他重载方法都会调用这个方法,该方法首先会执行checkConfiguration();方法来检测configuration是否为null,如果为null就抛出异常。
private void checkConfiguration() {
if (configuration == null) {
throw new IllegalStateException(ERROR_NOT_INIT);
}
}
那么这个configuration是什么呢?其实就是ImageLoaderConfiguration,其实用过ImageLoader的都应该清楚,我们在使用ImageLoader的时候首先就要在某一个地方比如Application的onCreate方法里面配置ImageLoaderConfiguration,用来初始化ImageLoader的一些配置参数:诸如缓存策略、可以缓存的文件数量等。
简单的配置代码如下:
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(mContext)
.threadPoolSize(Thread.NORM_PRIORITY - 2)
.threadPriority(Thread.NORM_PRIORITY - 2)
.memoryCache(new WeakMemoryCache())
.memoryCacheSize(2 * 1024 * 1024)
.memoryCacheSizePercentage(13) // default
.diskCache(new UnlimitedDiscCache(cacheDir)
.build(); // 开始构建
ImageLoader.getInstance().init(config);
这明显是一个Builder模式的应用,Builder模式的好处之一就是可以对比较复杂的对象逐步创建一个个小组件,然后由这些小组件最终完成复杂对象的初始化(构建)。上面代码通过Builder这个类来一步步创建ImageLoaderConfiguration这个类所需的一些参数之后,最终会通过build()方法来完成ImageLoaderConfiguation对象的创建!至于构建模式的说明读者可以参考网上的一些介绍。
其实build()方面也很简单:
public ImageLoaderConfiguration build() {
initEmptyFieldsWithDefaultValues();
//构造方法里面的this,指的就是Builder对象
return new ImageLoaderConfiguration(this);
}
build方法做了两个工作:
1)通过initEmptyFieldsWithDefaultValues()方法来设置一些默认的参数(前提是如果你通过Builder构建ImageLoaderConfiguation这个对象的过程中没有配置ImageLoaderConfiguation所需的参数),比如在Build的过程中没有调用Builder的imageDownloader方法来配置自己的下载图片的逻辑,那么在initEmptyFieldsWithDefaultValues方法中就会采用ImageLoader默认的下载方式来进行图片的下载:
if (downloader == null) {
downloader = DefaultConfigurationFactory.createImageDownloader(context);
}
2)返回ImageLoaderConfiguration对象。
ImageLoaderConfiguration的构造参数也很简单,就是把Builder对象构建的一个个组件设置给ImageLoaderConfiguration!该类的构造器是private的,切ImageLoaderConfiguration类是final的,而且它所持有的所有属性也是final的,一旦build完毕就不可更改!
private ImageLoaderConfiguration(final Builder builder) {
resources = builder.context.getResources();
maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;
maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;
processorForDiskCache = builder.processorForDiskCache;
taskExecutor = builder.taskExecutor;
taskExecutorForCachedImages = builder.taskExecutorForCachedImages;
threadPoolSize = builder.threadPoolSize;
threadPriority = builder.threadPriority;
tasksProcessingType = builder.tasksProcessingType;
diskCache = builder.diskCache;
memoryCache = builder.memoryCache;
defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
downloader = builder.downloader;
decoder = builder.decoder;
customExecutor = builder.customExecutor;
customExecutorForCachedImages = builder.customExecutorForCachedImages;
networkDeniedDownloader = new NetworkDeniedImageDownloader(downloader);
slowNetworkDownloader = new SlowNetworkImageDownloader(downloader);
}
上面主要讲了checkConfiguration()以及ImageLoaderConfiguation的构建过程。下面接着对displayImage方法的说明。接着就是我们非常熟悉的DisplayImageOptions的使用了,如果用户自己在使用displayImage一系列重载方法的时候没有传自定义的DisplayImageOptions对象,那么就会使用默认的DisplayImageOptions对象:
if (options == null) {
//configuration为上文所说的ImageLoaderConfiguration对象。
options = configuration.defaultDisplayImageOptions;
}
`这个默认的DisplayImageOptions是又Builder创建的ImageLoaderConfiguration来初始化的,代码如下:
final DisplayImageOptions defaultDisplayImageOptions;
private ImageLoaderConfiguration(final Builder builder) {
//此处省略了若干代码
defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
正如上面代码所示DisplayImageOptions类里面的defaultDisplayImageOptions引用是由builder构建过来的!你可以通过Builder类的defaultDisplayImageOptions方法来完成对defaultDisplayImageOptions的初始化工作:
public Builder defaultDisplayImageOptions(DisplayImageOptions defaultDisplayImageOptions) {
this.defaultDisplayImageOptions = defaultDisplayImageOptions;
return this;
}
那么问题来了?如果在创建Builder的时候如果没有调用defaultDisplayImageOptions()方法设置options岂不是为null?还记得上文build()方法里面的调用的initEmptyFieldsWithDefaultValues()么?这个方法就是:防止在使用Builder创建ImageLoaderConfiguration的时候客户端没有配置一些ImageLoader需要的组件而默认用ImageLoader一些组件。比如如果你的DisplayImageOptions没有在客户端指定,那么在initEmptyFieldsWithDefaultValues()里面有如下代码确保了defaultDisplayImageOptions不为null:
if (defaultDisplayImageOptions == null) {
defaultDisplayImageOptions = DisplayImageOptions.createSimple();
}
public static DisplayImageOptions createSimple() {
return new Builder().build();
}
其实上面的代码createSimple也是Builder模式的应用。到此ImageLoaderConfiguration的创建才算最终完成了,那么如何把这个对象叫给ImageLoader来使用呢?
ImageLoader是一个单例类,提供了如下方式来完成了ImageLoader的初始化工作:
Imageloader.getInstance().init(ImageLoaderConfiguration)
既然是通过init方法来完成ImageLoader与ImageLoaderCOnfiguration的关联,那么就让我们看看init方法都做了些神马?
public synchronized void init(ImageLoaderConfiguration configuration) {
if (configuration == null) {
throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
}
if (this.configuration == null) {
engine = new ImageLoaderEngine(configuration);
this.configuration = configuration;
} else {
}
}
也很简单,就是把configuration简单的赋予了ImageLoader.configuration引用!到此处ImageLoader初始化也完毕了,同样ImageLoaderConfiguration也初始化完毕了!所以就可以放心的调用ImageLoader.getInstance().displayImage来完成工作了!
在这里说一个可能不是技巧的技巧:在用DisplayImageOptions来控制页面显示样式的时候如果你的应用里面的图片样式:比如失败的图片,加载过程中的图片等都许多相似的地方,那么你就可以通过Builder的defaultDisplayImageOptions方法来手动指定默认的Options,个别页面如果需要不同的风格,就可以调用displayImage的其他重载方法传入具体通过DisplayImageOptions的Builder构建Options的对象就可以了,简单的配置代码如下:
private void initImageLoader(Context context) {
DisplayImageOptions options = new DisplayImageOptions.Builder().build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).
defaultDisplayImageOptions(options)//把上面创建的options交给ImageLoaderConfiguration
.build();
ImageLoader.getInstance().init(config);
}
在前面的一系列说明之后,终于进入的Imageloader的核心工作:展示图片!
if (TextUtils.isEmpty(uri)) {
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {//没有指定emptyUri的情况
imageAware.setImageDrawable(null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
return;
}
如果在配置DisplayImageOptions的时候通过它的Builder调用了showImageForEmptyUri方法,那么就让ImageView显示showImageForEmptyUri指定的那个图片资源,否则就神马都不显示 如果阅读仔细的话,上面也提供了加载图片的监听listener.onLoadingStarted,listener.onLoadingComplete,你也可以调用displayImage相关重载方法传入自己的ImageLoadingListener对象,否则ImageLoader会调用自己的默认实现。
图片资源非null的情况,那就很简单了,就是使用缓存的思路了:先从缓存中读取,如果缓存存在就用缓存中的图片资源,否则就下载新的资源并进行缓存!代码如下:
//1.对缓存进行一些配置
ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
//2.开始加载图片监听
listener.onLoadingStarted(uri, imageAware.getWrappedView());
//3.从内存缓存中获取Bitmap
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp != null && !bmp.isRecycled()) {//缓存存在
if (options.shouldPostProcess()) {//如果对DisplayImageOptions设置了BitmapProcessor
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
//开启一个Runnable
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {//同步执行显示图片逻辑
displayTask.run();
} else {//异步执行显示图片逻辑
engine.submit(displayTask);
}
} else {//如果没有配置BitmapProcessor
//通过BitmapDisplayer来完成显示图片的逻辑,该Displayer如果没指定的话就是用DefaultConfigurationFactory.createBitmapDisplayer();
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));
//初始化一个下载的Runnable
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {//同步处理下载
displayTask.run();
} else {//异步下载图片
engine.submit(displayTask);
}
}
上面代码中的注释说明了各个模块的作用,关于详细的内容限于篇幅以及时间太晚就另外开一篇博客进行说明。
总结一下本博客的要点:
1)ImageLoader的使用先通过Builder模式构建ImageLoaderConfiguration对象
2)通过Builder模式一步步创建DisplayImageOptions对象。
3)通过ImageLoader的init方法把ImageLoaderConfiguration对象和ImageLoader对象关联起来,当然也使得ImageLoader也关联了DisplayImageOptions对象
4)通过ImageLoader的displayImage重载方法,结合ImageLoaderConfiguration对象和DisplayImageOptions对象完成了对ImageView的展示图片的功能。
简单的用图片表示其三者之间的关系:
图片画的有点丑,不过也凑合,本篇博客到此为止,如有不当之处欢迎批评指正共同学习和提高。