ImageLoader使用以及源码解析

       Android-Universal-Image-Loader是一个开源的UI组件程序,该项目的目的是提供一个可重复使用的仪器为异步图像加 载,缓存和显示。

      ImageLoader 是最早开源的 Android 图片缓存库, 强大的缓存机制, 早期使用这个图片加载框架的Android应用非常多。

       首先说它的简单使用;

       第一步:加入imageLoader的jar。地址贴下。

       http://www.java2s.com/Code/Jar/u/Downloaduniversalimageloaderjar.htm

       第二步,自定义一个以MyApplication命名的类,该类继承Application,并在AndroidManifest.xml文件中对MyApplication进行注册。

        第三步,在MyApplication中进行ImageLoader的初始化配置,包括加载或解码过程中发生错误显示的图片,设置下载的图片是否缓存在内存中等等。

        基础代码如下:

         

         ImageLoader使用的是单例模式。在项目中只需要设置一次就可以。

         ImageLoader的使用方式:

  • 根据url把图片展示到imageView控件上(异步加载)

        ImageLoader.getInstance().displayImage(imageUri, imageView);  

       方法displayImage是异步加载,可以不设置监听,待图片资源加载完毕,会自动把图片展示到控件上。 

  • 根据url把图片展示到imageView控件上,并监听图片加载是否完毕(异步加载)
ImageLoader.getInstance().displayImage(imageUrl, imageView, null , new                                                        
    SimpleImageLoadingListener(){            
          @Override 
          public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                //显示imageview
             } }, new ImageLoadingProgressListener(){
           @Override 
           public void onProgressUpdate(String imageUri, View view, int current, int total) {
               //total总进度,current当前加载进度
} }); 

          方法loadImage是异步加载,一般需要对加载过程设置监听,待图片资源加载完毕,再手动操作把图片展示到控件上。 

  • 根据url获取到图片文件并返回为位图格式对象(同步加载)

         Bitmap bmp = ImageLoader.getInstance().loadImageSync(imageUri); 

        方法loadImageSync是同步加载,待图片资源加载完毕,直接返回位图资源对象

         ImageLoader的简单使用就介绍到此,接下来对它的源码进行读解。

         先上一张ImageLoader的设计图镇楼。

                  

    从 ImageLoader 的总体设计图看,整个库分为 ImageLoaderEngine,Cache 及 ImageDownloader,ImageDecoder,BitmapDisplayer,BitmapProcessor ,其中 Cache 分为 MemoryCache 和 DiskCache 两部分。

   简单的讲就是 ImageLoader 收到加载及显示图片的任务,并将它交给 ImageLoaderEngine,ImageLoaderEngine 分发任务到具体线程池去执行,任务通过 Cache 及 ImageDownloader 获取图片,中间可能经过 BitmapProcessor 和 ImageDecoder 处理,最终转换为Bitmap 交给 BitmapDisplayer 在 ImageAware 中显示。

    看下ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);源码,   

public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {  

        displayImage(uri, new ImageViewAware(imageView), options, null, null);  

    } 

从上面的代码中,我们可以看出,它会将ImageView转换成ImageViewAware, ImageViewAware主要是ImageView进行一个包装,将ImageView的强引用变成弱引用,当内存不足的时候,可以更好的回收ImageView对象,还有就是获取ImageView的宽度和高度。这使得我们可以根据ImageView的宽高去对图片进行一个裁剪,减少内存的使用。

      下面看displayImage具体的方法:   

        第1行代码是检查ImageLoaderConfiguration是否初始化,这个初始化是在Application中进行。

        第12-21行主要是针对url为空的时候做的处理 。

        第13行代码中,ImageLoaderEngine中存在一个HashMap,用来记录正在加载的任务,加载图片的时候会将ImageView的id和图片的url加上尺寸加入到HashMap中,加载完成之后会将其移除,然后将DisplayImageOptions的imageResForEmptyUri的图片设置给ImageView,最后回调给ImageLoadingListener接口通知这次任务完成。

        第1行主要是将ImageView的宽高封装成ImageSize对象,如果获取ImageView的宽高为0,就会使用手机屏幕的宽高作为ImageView的宽高,我们在使用ListView,GridView去加载图片的时候,第一页获取宽度是0,所以第一页使用的手机的屏幕宽高,后面的获取的都是控件本身的大小。

      第7行从内存缓存中获取Bitmap对象,我们可以再ImageLoaderConfiguration中配置内存缓存逻辑,默认使用的是LruMemoryCache。

      第11行中有一个判断,我们如果在DisplayImageOptions中设置了postProcessor就进入true逻辑,不过默认postProcessor是为null的,BitmapProcessor接口主要是对Bitmap进行处理,这个框架并没有给出相对应的实现,如果我们有自己的需求的时候可以自己实现BitmapProcessor接口。

       第22 -23行是将Bitmap设置到ImageView上面,这里我们可以在DisplayImageOptions中配置显示需求displayer,默认使用的是SimpleBitmapDisplayer,直接将Bitmap设置到ImageView上面,我们可以配置其他的显示逻辑, 他这里提供了FadeInBitmapDisplayer(透明度从0-1)RoundedBitmapDisplayer(4个角是圆弧)等, 然后回调到ImageLoadingListener接口。

         这段代码主要是Bitmap不在内存缓存,从文件中或者网络里面获取bitmap对象,实例化一个LoadAndDisplayImageTask对象,LoadAndDisplayImageTask实现了Runnable,如果配置了isSyncLoading为true, 直接执行LoadAndDisplayImageTask的run方法,表示同步,默认是false,将LoadAndDisplayImageTask提交给线程池对象。

LoadAndDisplayImageTask的run()中,如果waitIfPaused(), delayIfNeed()返回true的话,直接从run()方法中返回了,不执行下面的逻辑。先看waitIfPaused()方法。


         我们在使用ListView,GridView去加载图片的时候,有时候为了滑动更加的流畅,我们会选择手指在滑动或者猛地一滑动的时候不去加载图片,所以才提出了这么一个方法。

     这里用到了PauseOnScrollListener这个类,使用很简单ListView.setOnScrollListener(new PauseOnScrollListener(pauseOnScroll, pauseOnFling )), pauseOnScroll控制我们缓慢滑动ListView,GridView是否停止加载图片,pauseOnFling 控制猛的滑动ListView,GridView是否停止加载图片
除此之外,这个方法的返回值由isTaskNotActual()决定。

       再看看isTaskNotActual()的源码。

    isViewCollected()是判断我们ImageView是否被垃圾回收器回收了,如果回收了,LoadAndDisplayImageTask方法的run()就直接返回了,isViewReused()判断该ImageView是否被重用,被重用run()方法也直接返回。

       主要是ListView,GridView我们会复用item对象,假如我们先去加载ListView,GridView第一页的图片的时候,第一页图片还没有全部加载完我们就快速的滚动,isViewReused()方法就会避免这些不可见的item去加载图片,而直接加载当前界面的图片。

  1. ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;  
  2.         L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);  
  3.         if (loadFromUriLock.isLocked()) {  
  4.             L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);  
  5.         }  
  6.   
  7.         loadFromUriLock.lock();  
  8.         Bitmap bmp;  
  9.         try {  
  10.             checkTaskNotActual();  
  11.   
  12.             bmp = configuration.memoryCache.get(memoryCacheKey);  
  13.             if (bmp == null || bmp.isRecycled()) {  
  14.                 bmp = tryLoadBitmap();  
  15.                 if (bmp == null) return; // listener callback already was fired  
  16.   
  17.                 checkTaskNotActual();  
  18.                 checkTaskInterrupted();  
  19.   
  20.                 if (options.shouldPreProcess()) {  
  21.                     L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);  
  22.                     bmp = options.getPreProcessor().process(bmp);  
  23.                     if (bmp == null) {  
  24.                         L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);  
  25.                     }  
  26.                 }  
  27.   
  28.                 if (bmp != null && options.isCacheInMemory()) {  
  29.                     L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);  
  30.                     configuration.memoryCache.put(memoryCacheKey, bmp);  
  31.                 }  
  32.             } else {  
  33.                 loadedFrom = LoadedFrom.MEMORY_CACHE;  
  34.                 L.d(LOG_GET_IMAGE_FROM_MEMORY_CACHE_AFTER_WAITING, memoryCacheKey);  
  35.             }  
  36.   
  37.             if (bmp != null && options.shouldPostProcess()) {  
  38.                 L.d(LOG_POSTPROCESS_IMAGE, memoryCacheKey);  
  39.                 bmp = options.getPostProcessor().process(bmp);  
  40.                 if (bmp == null) {  
  41.                     L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);  
  42.                 }  
  43.             }  
  44.             checkTaskNotActual();  
  45.             checkTaskInterrupted();  
  46.         } catch (TaskCancelledException e) {  
  47.             fireCancelEvent();  
  48.             return;  
  49.         } finally {  
  50.             loadFromUriLock.unlock();  
  51.         }  

          第1行代码有一个loadFromUriLock,这个是一个锁,获取锁的方法在ImageLoaderEngine类的getLockForUri()方法中。

        这个锁对象与图片的url是相互对应的,假如在一个ListView中,某个item正在获取图片的过程中,而此时我们将这个item滚出界面之后又将其滚进来,滚进来之后如果没有加锁,该item又会去加载一次图片,假设在很短的时间内滚动很频繁,那么就会出现多次去网络上面请求图片,所以这里根据图片的Url去对应一个ReentrantLock对象,让具有相同Url的请求就会在第7行等待,等到这次图片加载完成之后,ReentrantLock就被释放,刚刚那些相同Url的请求就会继续执行第7行下面的代码。

        第12行,它们会先从内存缓存中获取一遍,如果内存缓存中没有在去执行下面的逻辑,所以ReentrantLock的作用就是避免这种情况下重复的去从网络上面请求图片。

            第14行的方法tryLoadBitmap(),这个方法确实也有点长,我先告诉大家,这里面的逻辑是先从文件缓存中获取有没有Bitmap对象,如果没有在去从网络中获取,然后将bitmap保存在文件系统中。

          先判断文件缓存中有没有该文件,如果有的话,直接去调用decodeImage()方法去解码图片,该方法里面调用BaseImageDecoder类的decode()方法,根据ImageView的宽高,ScaleType去裁剪图片再看看tryLoadBitmap()方法。

            第1行表示从文件缓存中获取的Bitmap为null,或者宽高为0,就去网络上面获取Bitmap。

           第6行代码是否配置了DisplayImageOptions的isCacheOnDisk,表示是否需要将Bitmap对象保存在文件系统中,一般我们需要配置为true, 默认是false这个要注意下,然后就是执行tryCacheImageOnDisk()方法,去服务器上面拉取图片并保存在本地文件中。

  1. private Bitmap decodeImage(String imageUri) throws IOException {  
  2.     ViewScaleType viewScaleType = imageAware.getScaleType();  
  3.     ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,  
  4.             getDownloader(), options);  
  5.     return decoder.decode(decodingInfo);  
  6. }  
  7.   
  8. /** @return <b>true</b> - if image was downloaded successfully; <b>false</b> - otherwise */  
  9. private boolean tryCacheImageOnDisk() throws TaskCancelledException {  
  10.     L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);  
  11.   
  12.     boolean loaded;  
  13.     try {  
  14.         loaded = downloadImage();  
  15.         if (loaded) {  
  16.             int width = configuration.maxImageWidthForDiskCache;  
  17.             int height = configuration.maxImageHeightForDiskCache;  
  18.               
  19.             if (width > 0 || height > 0) {  
  20.                 L.d(LOG_RESIZE_CACHED_IMAGE_FILE, memoryCacheKey);  
  21.                 resizeAndSaveImage(width, height); // TODO : process boolean result  
  22.             }  
  23.         }  
  24.     } catch (IOException e) {  
  25.         L.e(e);  
  26.         loaded = false;  
  27.     }  
  28.     return loaded;  
  29. }  
  30.   
  31. private boolean downloadImage() throws IOException {  
  32.     InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());  
  33.     return configuration.diskCache.save(uri, is, this);  
  34. }  

         第6行的downloadImage()方法是负责下载图片,并将其保持到文件缓存中,将下载保存Bitmap的进度回调到IoUtils.CopyListener接口的onBytesCopied(int current, int total)方法中,所以我们可以设置ImageLoadingProgressListener接口来获取图片下载保存的进度,这里保存在文件系统中的图片是原图。

       第16-17行,获取ImageLoaderConfiguration是否设置保存在文件系统中的图片大小,如果设置了maxImageWidthForDiskCache和maxImageHeightForDiskCache,会调用resizeAndSaveImage()方法对图片进行裁剪然后在替换之前的原图,保存裁剪后的图片到文件系统的。

     在Application中实例化ImageLoaderConfiguration的时候设置maxImageWidthForDiskCache和maxImageHeightForDiskCache可以设置原图和缩略图。

         6-12行是否要对Bitmap进行处理,这个需要自行实现,14-17就是将图片保存到内存缓存中。

     最后这两行代码就是一个显示任务,直接看DisplayBitmapTask类的run()方法。

       假如ImageView被回收了或者被重用了,回调给ImageLoadingListener接口,否则就调用BitmapDisplayer去显示Bitmap。

          ImageLoadader中用了很多的设计模式,比如建造者模式,装饰模式,代理模式,策略模式等等,方便我们使用者扩展。

猜你喜欢

转载自my.oschina.net/u/3761887/blog/1615210