Android-Universal-Image-Loader (图片异步加载缓存库)的源码解读
前言:
在Android开发中,对于图片的加载可以说是个老生常谈的问题了,图片加载是一个比较坑的地方,处理不好,会有各种奇怪的问题,比如 加载导致界面卡顿,程序crash。因此 如何高效的加载大量图片,以及如何加载大分辨率的图片到内存,是我们想要开发一款优质app时不得不去面对与解决的问题。
通常开发中,我们只有两种选择:① 使用开源框架 ②自己去实现处理图片的加载与缓存。
通常一开始让我们自己去写,我们会无从下手,因此先去分析一下开源的思路,对我们的成长很有必要。
目前使用频率较高的图片缓存框架有 Universal-Image-Loader、android-Volley、Picasso、Fresco和Glide五大Android开源组件。
首先排除android-Volley 孰优孰劣,后面再去验证,剩下的四种对于 图片加载缓存的思想,从大方向上应该是类似的
而Android-Universal-Image-Loader 作为一款比较经典的框架,从早期到现在一直都比较常见,这里就拿Android-Universal-Image-Loader 来看一下它对图片处理的思想,以帮助我们理解,以便于我们也能写出类似的框架。
关于使用配置请看Android-Universal-Image-Loader (图片异步加载缓存库)的使用配置
正文:
</div>一 工作流程
前面介绍了如何在我们的项目中使用Android-Universal-Image-Loader,本文看一下UIL的工作过程。
在看之前我们先看一下官方的这张图片,它代表着所有条件下的执行流程:
图片给出的加载过程分别对应三种情况:
1.当内存中有该 bitmap 时,直接显示。
2.当本地有该图片时,加载进内存,然后显示。
3. 内存本地都没有时,请求网络,下载到本地,接下来加载进内存,然后显示。
过程分析:</div>
最终展示图片还是调用的ImageLoader 这个类中的 display() 方法,那么我们就把注意力集中到ImageLoader 这个类上,看下display()内部怎么实现的。
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) { // 首先检查初始化配置,configuration == null 抛出异常 checkConfiguration(); if (imageAware == null) { throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS); } if (listener == null) { listener = defaultListener; } 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; } // 根据 配置的大小与图片实际大小得出 图片尺寸 ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize()); String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); engine.prepareDisplayTaskFor(imageAware, memoryCacheKey); listener.onLoadingStarted(uri, imageAware.getWrappedView()); // 首先从内存中取,看是否加载过,有缓存直接用 Bitmap bmp = configuration.memoryCache.get(memoryCacheKey); 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 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 { // 没有缓存 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 displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, defineHandler(options)); if (options.isSyncLoading()) { displayTask.run(); } else { engine.submit(displayTask); } } }
可以看到这个方法还不长,大体流程也向前面图中描述的:
首先判断传入的目标url 是" ",如果空,是否配置了默认的图片,接下来重点在url 是合法的情况下,去加载bitmap,首先从内存中去取,看能否取到(如果前面加载到内存,并且缓存过,没有被移除,则可以取到),如果取到则直接展示就可以了。
如果没有在内存中取到,接下来执行LoadAndDisplayImageTask 这个任务,主要还是看run()方法的执行过程:
@Override public void run() { if (waitIfPaused()) return; if (delayIfNeed()) return; ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock; L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey); if (loadFromUriLock.isLocked()) { L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey); } loadFromUriLock.lock(); Bitmap bmp; try { checkTaskNotActual(); bmp = configuration.memoryCache.get(memoryCacheKey); if (bmp == null || bmp.isRecycled()) { // cache 中没有,下载 bmp = tryLoadBitmap(); if (bmp == null) return; // listener callback already was fired checkTaskNotActual(); checkTaskInterrupted(); if (options.shouldPreProcess()) { L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey); bmp = options.getPreProcessor().process(bmp); if (bmp == null) { L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey); } } // 加入到内存的缓存 if (bmp != null && options.isCacheInMemory()) { L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey); //LruMemoryCache configuration.memoryCache.put(memoryCacheKey, bmp); } } else { loadedFrom = LoadedFrom.MEMORY_CACHE; L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey); } if (bmp != null && options.shouldPostProcess()) { L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey); bmp = options.getPostProcessor().process(bmp); if (bmp == null) { L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey); } } checkTaskNotActual(); checkTaskInterrupted(); } catch (TaskCancelledException e) { fireCancelEvent(); return; } finally { loadFromUriLock.unlock(); } DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom); runTask(displayBitmapTask, syncLoading, handler, engine); }
这里一开始进行了一些基本的判断,比如是否当前暂停加载,延时加载等情况。
接下来,因为设置到了下载读写等过程,所以加了 锁,保证线程安全,下载过程是在 上面的// cache 中没有,这个注释下面的tryLoadBitmap() 方法中进行的,这个方法中做了什么,我们一会在看,现在继续往下走,下载后拿到了bitmap,接着进行判断是否 把bitmap加入到内存中的缓存中。 最后在DisplayBitmapTask 的run 方法中setImageBitmap设置为背景。
这就是大体工作流程,也是前面说的的 三种情况
1. 内存中有,直接显示。
2. 内存中没有 本地有,加载进内存并显示。
3 本地没有,网络下载,本地保存,加载进内存,显示。
接下来再看前面说的下载方法tryLoadBitmap(), 由于比较长,这里只看关键的 try代码块中的操作:
// 尝试 本地文件中是否有缓存 File imageFile = configuration.diskCache.get(uri); if (imageFile != null && imageFile.exists() && imageFile.length() > 0) { L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memor