Android-Universal-Image-Loader 源码解读

Android-Universal-Image-Loader 源码解读

Tips:文章为拜读@CodingForAndroid 后有感而作的分享,先对做者表示感谢,附原文地址:http://blog.csdn.net/u011733020android

0 前言

  • 在Android开发中,对于图片的加载能够说是个老生常谈的问题了,图片加载是一个比较坑的地方,处理很差,会有各类奇怪的问题,好比 加载致使界面卡顿,程序crash。数据库

  • 所以 如何高效的加载大量图片,以及如何加载大分辨率的图片到内存,是咱们想要开发一款优质app时不得不去面对与解决的问题。缓存

  • 一般开发中,咱们只有两种选择:安全

    • 使用开源框架网络

    • 本身去实现处理图片的加载与缓存。app

  • 一般一开始让咱们本身去写,咱们会无从下手,所以先去分析一下开源的思路,对咱们的成长颇有必要。框架

  • 目前使用频率较高的图片缓存框架有 Universal-Image-Loader、android-Volley、Picasso、Fresco和Glide五大Android开源组件。ide

  • 首先排除android-Volley 孰优孰劣,后面再去验证,剩下的四种对于 图片加载缓存的思想,从大方向上应该是相似的工具

  • 而Android-Universal-Image-Loader 做为一款比较经典的框架,从早期到如今一直都比较常见,这里就拿Android-Universal-Image-Loader 来看一下它对图片处理的思想,以帮助咱们理解,以便于咱们也能写出相似的框架。url

1 工做流程

  • 前面介绍了如何在咱们的项目中使用Android-Universal-Image-Loader,本文看一下UIL的工做过程。

  • 在看以前咱们先看一下官方的这张图片,它表明着全部条件下的执行流程:

  • 图片给出的加载过程分别对应三种状况:

    1. 当内存中有该 bitmap 时,直接显示。

    2. 当本地有该图片时,加载进内存,而后显示。

    3. 内存本地都没有时,请求网络,下载到本地,接下来加载进内存,而后显示。

* 过程分析:

  • 最终展现图片仍是调用的 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, memoryCacheKey);  
               loadedFrom = LoadedFrom.DISC_CACHE;  
         
               checkTaskNotActual();  
               bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));  
           }  
           // 本地也没有  
           if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {  
               L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);  
               loadedFrom = LoadedFrom.NETWORK;  
         
               String imageUriForDecoding = uri;  
               if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {  
                   imageFile = configuration.diskCache.get(uri);  
                   if (imageFile != null) {  
                       imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());  
                   }  
               }  
         
               checkTaskNotActual();  
               bitmap = decodeImage(imageUriForDecoding);
  • 就是前面的状况,先看本地文件,若是有,加载进内存,并显示。

  • 若是没有则 下载,首先判断是否容许保存到本地,若是容许则下载到本地,接下来经过bitmap = decodeImage(imageUriForDecoding);拿到目标bitmap ,并返回 用于显示。

2 缓存策略分析

  • 经过上图,咱们能够总结出 UIL采用的是 内存(memory cache)+本地(disk cache) 的两级缓存策略。

  • 采用缓存的好处有如下几点:

    1. 减小每次请求网络下载消耗的的流量。

    2. 复用直接从内存/本地中获取,提升了加载速度。

  • 那么咱们接下来看一下UIL 是采起哪些方式去缓存内存和本地文件的。

  • 经过查看UIL的lib库咱们能够看出,整个lib 主要有三个包组成

  1. cache:管理缓存

  2. core:下载的核心

  3. utils:一些辅助工具。

utils 不用管,剩下的两部分就是整个项目的精髓: 下载展现 和缓存。
咱们这里先看一下cache:

  • 也是由两部分组成:磁盘和内存。

DiskCache(本地缓存)

disc 有两种cache类型:

  • 第一类是是基于DiskLruCache的LruDiskCache

  • 第二类是基于BaseDiskCache的LimitedAgeDiskCache 和UnlimitedDiskCache 。

  • 这两种的相同点是都是将请求到的图片 inputStream写到本地文件中。不一样点在鱼管理方式不一样,

  • LruDiskCache是根据 size > maxSize || fileCount > maxFileCount || 或者存的数据超过2000条而自动去删除。

  • LimitedAgeDiskCache 是根据存入时间与当前时间差,是否大于过时时间 去判断是重新下载 仍是重复利用。

  • UnlimitedDiskCache:这个就是不限制cache大小,只要disk 上有空间 就能够保存到本地。

  • 以上三个都实现了DiskCache 这个接口,具体工做过程是 save get remove clear 等几个方法,相似于数据库的 curd 操做。

 MemoryCache(内存缓存)

  • memory的缓存 的实现类比较多,都是实现了 MemoryCache 这个接口

    public interface MemoryCache {

    /** 
         Puts value into cache by key 
          根据Key将Value添加进缓存中 
         @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into 
         cache 
        */  
       boolean put(String key, Bitmap value);  
     
       /** Returns value by key. If there is no value for key then null will be returned. */  
       根据Key 取Value  
       Bitmap get(String key);  
     
       /** Removes item by key */  
       根据Key移除对应的Value  
       Bitmap remove(String key);  
     
       /** Returns all keys of cache */  
       返回全部的缓存Keys  
       Collection<String> keys();  
     
       /** Remove all items from cache */  
       状况缓存的map  
       void clear();

    }

  • 比较多,不一一说,拿比较经常使用的LruLimitedMemoryCache 说一下吧

    @Override  
       public boolean put(String key, Bitmap value) {  
           boolean putSuccessfully = false;  
           // Try to add value to hard cache  
           // 当前要存入的 size  
           int valueSize = getSize(value);  
           // 约定的最大size  
           int sizeLimit = getSizeLimit();  
           //当前存在的size 大小  
           int curCacheSize = cacheSize.get();  
           //若是当前没有满,存入  
           if (valueSize < sizeLimit) {  
               // 判断 存入后若是 超出了约定的 maxsize  则删除掉最先的那一条  
               while (curCacheSize + valueSize > sizeLimit) {  
                   Bitmap removedValue = removeNext();  
                   if (hardCache.remove(removedValue)) {  
                       curCacheSize = cacheSize.addAndGet(-getSize(removedValue));  
                   }  
               }  
               hardCache.add(value);  
               cacheSize.addAndGet(valueSize);  
         
               putSuccessfully = true;  
           }  
           // 若是过大,则不存入到上面的集合,则将value 先new WeakReference<Bitmap>(value)中,而后在加入Map<k v> 中  
           // Add value to soft cache  
           super.put(key, value);  
           return putSuccessfully;  
       }
  • 注释的很详细, 首先判断大小能够加入List<Bitmap> hardCache 这样一个集合中, 若是能够则加入在判断 当前集合是否超出 设置的默认最大值,若是该图片不能加入到这个集合中,那么首先将value 添加到WeakReference<Bitmap>(value)中,而后将WeakReference 做为value 添加到另外一个Map 中保存。

3 总结

  • 前面看上去比较很差理解,第一遍看可能会以为很乱,这里在总结一下加载的过程:

相关文章
相关标签/搜索