原项目的github地址是:https://github.com/nostra13/Android-Universal-Image-Loader
一、框架的介绍
Android-Universal-Image-Loader是一个开源的UI组件程序,该项目的目的是提供一个可重复使用的仪器为异步图像加载,缓存和显示。
有几个基本的类:
ImageLoaderConfiguration是针对图片缓存的全局配置,主要有线程类、缓存大小、磁盘大小、图片下载与解析、日志方面的配置。
ImageLoader是具体下载图片,缓存图片,显示图片的具体执行类,它有两个具体的方法displayImage(...)、loadImage(...),但是其实最终他们的实现都是displayImage(...)。
DisplayImageOptions用于指导每一个Imageloader根据网络图片的状态(空白、下载错误、正在下载)显示对应的图片,是否将缓存加载到磁盘上,下载完后对图片进行怎么样的处理。
框架的使用自行参考github上的介绍,此处不再赘述。
二、图片的加载
我们分析一下 ImageLoader.displayImage 方法
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, 2 ImageLoadingListener listener, ImageLoadingProgressListener progressListener) { 3 //检查UIL的配置是否被初始化 4 checkConfiguration(); 5 if (imageAware == null) { 6 throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS); 7 } 8 if (listener == null) { 9 listener = emptyListener; 10 } 11 if (options == null) { 12 options = configuration.defaultDisplayImageOptions; 13 } 14 15 if (TextUtils.isEmpty(uri)) { 16 engine.cancelDisplayTaskFor(imageAware); 17 listener.onLoadingStarted(uri, imageAware.getWrappedView()); 18 if (options.shouldShowImageForEmptyUri()) { 19 imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources)); 20 } else { 21 imageAware.setImageDrawable(null); 22 } 23 listener.onLoadingComplete(uri, imageAware.getWrappedView(), null); 24 return; 25 } 26 //计算Bitmap的大小,以便后面解析图片时用 27 ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize()); 28 String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); 29 engine.prepareDisplayTaskFor(imageAware, memoryCacheKey); 30 31 listener.onLoadingStarted(uri, imageAware.getWrappedView()); 32 //Bitmap是否缓存在内存? 33 Bitmap bmp = configuration.memoryCache.get(memoryCacheKey); 34 if (bmp != null && !bmp.isRecycled()) { 35 L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey); 36 37 if (options.shouldPostProcess()) { 38 ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, 39 options, listener, progressListener, engine.getLockForUri(uri)); 40 //处理并显示图片 41 ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo, 42 defineHandler(options)); 43 if (options.isSyncLoading()) { 44 displayTask.run(); 45 } else { 46 engine.submit(displayTask); 47 } 48 } else { 49 //显示图片 50 options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE); 51 listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp); 52 } 53 } else { 54 if (options.shouldShowImageOnLoading()) { 55 imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources)); 56 } else if (options.isResetViewBeforeLoading()) { 57 imageAware.setImageDrawable(null); 58 } 59 60 ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, 61 options, listener, progressListener, engine.getLockForUri(uri)); 62 //启动一个线程,加载并显示图片 63 LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo, 64 defineHandler(options)); 65 if (options.isSyncLoading()) { 66 displayTask.run(); 67 } else { 68 engine.submit(displayTask); 69 } 70 } 71 }我们可以看到图片的大概加载流程:
首先会根据 URI 和 ImageSize 的实例生成唯一的key。整个框架就是通过这个 key 来唯一区分图片的。如果DisplayImageOptions中配置了内存中存放缓存,就根据key判断内存中是否有该图片,如果有就处理显示图片。如果内存中没有就要寻求从硬盘上或通过网络请求获取图片。所以开启了一个 ProcessAndDisplayImageTask 线程。
private Bitmap tryLoadBitmap() throws TaskCancelledException { 2 Bitmap bitmap = null; 3 try { 4 //尝试从磁盘缓存中读取Bitmap 5 File imageFile = configuration.diskCache.get(uri); 6 if (imageFile != null && imageFile.exists()) { 7 L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey); 8 loadedFrom = LoadedFrom.DISC_CACHE; 9 10 checkTaskNotActual(); 11 bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath())); 12 } 13 //没有缓存在磁盘,从网络中下载图片 14 if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { 15 L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey); 16 loadedFrom = LoadedFrom.NETWORK; 17 18 String imageUriForDecoding = uri; 19 if (options.isCacheOnDisk() && tryCacheImageOnDisk()) { 20 imageFile = configuration.diskCache.get(uri); 21 if (imageFile != null) { 22 imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath()); 23 } 24 } 25 26 checkTaskNotActual(); 27 bitmap = decodeImage(imageUriForDecoding); 28 29 if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { 30 fireFailEvent(FailType.DECODING_ERROR, null); 31 } 32 } 33 } catch (IllegalStateException e) { 34 fireFailEvent(FailType.NETWORK_DENIED, null); 35 } catch (TaskCancelledException e) { 36 throw e; 37 } catch (IOException e) { 38 L.e(e); 39 fireFailEvent(FailType.IO_ERROR, e); 40 } catch (OutOfMemoryError e) { 41 L.e(e); 42 fireFailEvent(FailType.OUT_OF_MEMORY, e); 43 } catch (Throwable e) { 44 L.e(e); 45 fireFailEvent(FailType.UNKNOWN, e); 46 } 47 return bitmap; 48 }可以看到该线程首先会尝试从硬盘中根据 key 来获取图片,如果没有,才会发起http请求从网络上加载,inputstream 解析解码为bitmap。整个流程如下图:
三、缓存处理机制
主要谈谈内存的缓存控制,运用的是LRU(Least Recently Used)Menmory Cache。
具体实现是通过LinkedHashMap。每次获取到相关图片时,在map中寻找有没有相应的key。如果有的话该数据结构自动把该key对应的键值对移到队首,如果没有就把该key加到队首。如果缓存满了,就删除队尾元素。
@Override public V get(Object key) { 2 /* 3 * This method is overridden to eliminate the need for a polymorphic 4 * invocation in superclass at the expense of code duplication. 5 */ 6 if (key == null) { 7 HashMapEntry<K, V> e = entryForNullKey; 8 if (e == null) 9 return null; 10 if (accessOrder) 11 makeTail((LinkedEntry<K, V>) e); 12 return e.value; 13 } 14 15 // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590). 16 int hash = secondaryHash(key); 17 HashMapEntry<K, V>[] tab = table; 18 for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; 19 e != null; e = e.next) { 20 K eKey = e.key; 21 if (eKey == key || (e.hash == hash && key.equals(eKey))) { 22 if (accessOrder) 23 makeTail((LinkedEntry<K, V>) e); 24 return e.value; 25 } 26 } 27 return null; 28 }