UIL的源码分析

UIL的源码分析

上次分析了UIL的基本使用方法,在本篇文章中我们将通过分析UIL的源码来深入了解一下UIL.

我们先来看一下一下UIL加载图片的流程(基本上ImageLoader.displayImage方法的运行流程):


简单描述一下流程:

UIL加载一张图片时通常会先判断在内存中是否存在该图片,再判断磁盘(disk)中是否有,如果都没有就从网络中加载。最后根据原先在UIL中的配置判断是否需要缓存Bitmap到内存或磁盘中。Bitmap加载完后,就对它进行解析,然后显示到特定的ImageView中。

 

每一个图片的加载和显示任务都运行在独立的线程中,除非这个图片缓存在内存中,这种情况下图片会立即显示。如果需要的图片缓存在本地,他们会开启一个独立的线程队列。如果在缓存中没有正确的图片,任务线程会从线程池中获取,因此,快速显示缓存图片时不会有明显的障碍。Bitmap加载完后,就对它进行解析,然后显示到特定的ImageView中。

UIL中主要有以下一些核心的类,后面我们都会一一进行分析:

Ø  ImageLoader:是具体下载图片,缓存图片,显示图片的具体执行类。

Ø  ImageLoaderConfiguration:管理ImageLoader一些全局配置。

Ø  ImageLoaderEngine:任务分发器,主要负责分发LoadAndDisplayImageTask和ProcessAndDisplayImageTask给具体的线程池去执行

Ø  ImageAware:显示图片的对象,可以是ImageView等。

Ø  ImageDownloader:图片下载器,负责从图片的各个来源(网络、本地等)获取输入流

Ø  Cache:图片缓存,分为MemoryCache和DiskCache。

Ø  MemoryCache:内存图片缓存,常用LruMemoryCache。

Ø  DiskCache:磁盘图片缓存.

Ø  ImageDecoder:图片解码器,负责将图片输入流InputStream转换为Bitmap对象,默认调用bitmaoFactory.decode方法

Ø  BitmapProcessor:图片处理器,负责从缓存读取或写入前对图片进行处理。

Ø  BitmapDisplayer:将Bitmap对象显示在相应的控件ImageAware上, 加一些特殊显示效果

Ø  LoadAndDisplayImageTask:用于加载并显示图片的任务。

Ø  ProcessAndDisplayImageTask:用于处理并显示图片的任务。

Ø  DisplayBitmapTask:用于显示图片的任务。

 

了解了UIL的处理基本流程后,我们下面来分析UIL的源码。上一篇中讨论了UIL的基本用法,UIL的核心类就是ImageLoader,我们就从ImageLoader开始。

ImageLoader

首先,我们来看一下ImageLoader的构造函数。

private volatilestaticImageLoaderinstance;

/** Returns singleton class instance */
public static ImageLoadergetInstance() {
  
if (instance==null) {
     
synchronized (ImageLoader.class) {
        
if (instance==null) {
           
instance =newImageLoader();
        
}
      }
   }
  
return instance;
}

protected ImageLoader() { //采用protected而非private,所以ImageLoader可以被继承
}

从代码可以看出,ImageLoader采用DCL(Double Check Lock)的形式实现了单例模式。(在Android设计模式单例模式,有详细分析单例模式)。

在ImageLoader中含有线程池,缓存系统,网络请求等,如果构造多个实例,非常消耗资源也没有必要。所以采用单例模式来实现,确保ImageLoader在一个应用中只有一个实例。用户通过getInstnace方法获取ImageLoader单例对象。

ImageLoaderConfiguration

用户在使用ImageLoader加载图片之前,需要使用ImageLoaderConfiguration对其进行初始化:

ImageLoader.getInstance().init(configuration);/*使用基本配置信息初始化ImageLoader*/

 

我们来看一下init方法:

/**
 * Initializes ImageLoader instance withconfiguration.
<br />
 
* If configurations was set before ( {@link#isInited()}== true) then this method does nothing.<br />
 
* To force initialization with new configuration youshould {@linkplain#destroy() destroy ImageLoader} at first.
 *
只能初始化一次,如果想重置,需要先destroy原始的ImageLoader
 * @param configuration {@linkplainImageLoaderConfigurationImageLoader configuration}
 * @throws IllegalArgumentExceptionif
<b>configuration</b>parameter is null
 */
public synchronized void init(ImageLoaderConfiguration configuration) {
  
if (configuration==null) {
     
throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
  
}
  
if (this.configuration==null) {
      L.d(
LOG_INIT_CONFIG);
     
engine =newImageLoaderEngine(configuration);
      this
.configuration= configuration;
  
} else{
      L.w(
WARNING_RE_INIT_CONFIG);
  
}
}

从源码中可以看到,如果传入的参数configuration为空会抛出一个异常,而如果ImageLoader的成员变量configuration(即this. configuration)为空,则会做一些初始化的操作,而如果已经被设置了值,则不会做实质性的操作,只打印了一条Log信息。这也就说明,ImageLoader的配置不能重复初始化,如果一定要对配置进行重置,需要先将原来的ImageLoader对象销毁。

 

在上面的代码中,我们可以注意到有两个类:ImageLoaderConfiguration和ImageLoaderEngine。

ImageLoaderConfiguration是针对图片缓存的全局配置,主要有线程类、缓存大小、磁盘大小、图片下载与解析、日志方面的配置。采用Builder模式实现。通过将ImageLoaderConfiguration的构成函数私有化、字段私有化,使得外部不能访问该类的内部属性,用户只能通过Builder对象构造对象,实现了构建和表示分离。

privateImageLoaderConfiguration(finalBuilder builder) {
  
resources =builder.context.getResources();
  
maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
  
maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
 
 … …
}

public static classBuilder {
 
 public static final intDEFAULT_THREAD_POOL_SIZE=3;
 
 public static final intDEFAULT_THREAD_PRIORITY= Thread.NORM_PRIORITY-2;
   private
Context context;
   private int
maxImageWidthForMemoryCache = 0;

   … … //
省略部分代码
   public
Builder(Context context) {
     
this.context= context.getApplicationContext();
  
}

  
public BuildermemoryCacheExtraOptions(intmaxImageWidthForMemoryCache, intmaxImageHeightForMemoryCache) {
     
this.maxImageWidthForMemoryCache= maxImageWidthForMemoryCache;
      this
.maxImageHeightForMemoryCache= maxImageHeightForMemoryCache;
      return this;
  
}
  … ….

… ….

  
/** Builds configured {@link ImageLoaderConfiguration}object */
  
public ImageLoaderConfigurationbuild() {
     initEmptyFieldsWithDefaultValues()
;
      return new
ImageLoaderConfiguration(this);
  
}
  //初始化值为 null 的属性。若用户没有配置相关项
  
private void initEmptyFieldsWithDefaultValues() {
     
if (taskExecutor==null) {
        
taskExecutor = DefaultConfigurationFactory
               .createExecutor(
threadPoolSize,threadPriority,tasksProcessingType);
     
} else{
        
customExecutor =true;
     
}
    
… …
   }
}

 

ImageLoaderEngine

ImageLoaderEngine:UIL中将线程池相关的东西封装在ImageLoaderEngine类中了。

线程分为两类:

LoadAndDisplayImageTask 和 ProcessAndDisplayImageTask。engine负责分发任务给具体的线程池。

 

LoadAndDisplayImageTask:从网络或文件系统中加载图片,并处理处理图片,然后用DisplayBitmapTask来呈现图片。

ProcessAndDisplayImageTask:处理图片并用DisplayBitmapTask来呈现图片。

 

在init方法中,构建了ImageLoaderEngine的实例:

ImageLoaderEngine(ImageLoaderConfigurationconfiguration) {
  
this.configuration= configuration;
  
taskExecutor = configuration.taskExecutor;
  
taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
  
taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
}

主要属性:

(1).ImageLoaderConfiguration configuration

ImageLoader 的配置信息,可包括线程池、缓存、下载器等等。

(2). Executor  taskExecutor

用于执行从源获取图片任务的 Executor

(3). Executor  taskExecutorForCachedImages

用于执行从缓存获取图片任务的 Executor

(4). ExecutortaskDistributor

任务分发线程池,任务指 LoadAndDisplayImageTask  ProcessAndDisplayImageTask ,因为只需要分发给上面的两个 Executor 去执行任务,不存在较耗时或阻塞操作,所以用newCachedThreadPool即可。

主要函数:

(1). voidsubmit(final LoadAndDisplayImageTask task)

/** Submits taskto execution pool */
void submit(finalLoadAndDisplayImageTask task) {
  
taskDistributor.execute(newRunnable() {
     
@Override
     
public void run() {
         Fileimage =
configuration.diskCache.get(task.getLoadingUri());
         boolean
isImageCachedOnDisk = image !=null&&image.exists();
        
initExecutorsIfNeed();
         if
(isImageCachedOnDisk) {
           
taskExecutorForCachedImages.execute(task);
        
} else{
           
taskExecutor.execute(task);
        
}
      }
   })
;
}

添加一个 LoadAndDisplayImageTask 。直接用 taskDistributor 执行一个 Runnable,在 Runnable内部根据图片是否被磁盘缓存过确定使用 taskExecutorForCachedImages 还是 taskExecutor 执行该 task

(2). voidsubmit(ProcessAndDisplayImageTask task)

/** Submits taskto execution pool */
void submit(ProcessAndDisplayImageTasktask) {
   initExecutorsIfNeed()
;
  
taskExecutorForCachedImages.execute(task);
}

添加一个 ProcessAndDisplayImageTask 。直接用 taskExecutorForCachedImages 执行该 task

ImageLoaderEngine的主要工作:

taskDistributor用来尝试读取磁盘中是否有图片缓存,因为涉及磁盘操作,需要用线程来执行。根据是否有对应的图片缓存,将图片加载的任务分发到对应的执行器。如果图片已经缓存在磁盘,则通过taskExecutorForCachedImages执行,如果图片没有缓存在磁盘,则通过taskExecutor执行。

ImageLoader--displayImage

回到ImageLoader,我们来看一下ImageLoader的主要方法:

查看上面ImageLoader的方法图,ImageLoader主要有三种加载图片的方式。

displayImage,loadImage和loadImageSync。

我们一般会使用前两种,因为这两种是异步加载的,而loadImageSync是同步的。

虽然根据不同参数,重载了很多的方法,但是所有的方法,最后都会调用下面这个方法:

/**
 *
将图片显示任务添加到线程池中,. Image will be set to ImageAware whenit's turn.*
* @param
uri   图片地址 (i.e. "http://site.com/image.png", "file:///mnt/sdcard/image.png")
* @param
imageAware       图片的显示容器

*@paramoptions         设置图片解码和显示的规则

*@paramtargetSize      {图片大小

*@paramlistener        图片加载状态监听
* @param
progressListener图片加载进度监听
*                        

*/
public void displayImage(String uri,ImageAware imageAware,DisplayImageOptions options,
      
ImageSize targetSize,ImageLoadingListener listener,ImageLoadingProgressListener progressListener) {
   checkConfiguration()
;//检查ImageLoader的配置是否初始化
   if (imageAware == null) {//检查图片显示容器,为空,则报错
     
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
  
}
  
if (listener==null) {//检查图片加载监听,为空,则初始化
      listener =
defaultListener;
  
}
  
if (options==null) { //检查图片显示的设置,为空,则初始化
      options =
configuration.defaultDisplayImageOptions;
  
}

  
if (TextUtils.isEmpty(uri)){ //检查图片地址,地址为空则取消任务,并处理反馈和通知UI
     
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;
  
}

  
if (targetSize==null) {//检查图片尺寸参数,为空,则初始化
      targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware
,configuration.getMaxImageSize());
  
}

//进行准备工作,生成内存缓存key
   String memoryCacheKey =MemoryCacheUtils.generateKey(uri
,targetSize);
  
engine.prepareDisplayTaskFor(imageAware,memoryCacheKey);

//通知UI,加载开始
   listener.onLoadingStarted(uri,imageAware.getWrappedView());

//根据内存缓存key从内存中获取
   Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
   if
(bmp != null&& !bmp.isRecycled()) {//如果内存中存在该图片,并且没有被回收
    
   if (options.shouldPostProcess()) { //图片是否需要处理,默认不需要处理
         ImageLoadingInfo imageLoadingInfo =
newImageLoadingInfo(uri,imageAware,targetSize,memoryCacheKey,
              
options,listener,progressListener,engine.getLockForUri(uri));

//构建ProcessAndDisplayImageTask,处理和呈现图片
        
ProcessAndDisplayImageTask displayTask =newProcessAndDisplayImageTask(engine,bmp,imageLoadingInfo,
              
defineHandler(options));
         if
(options.isSyncLoading()) { //是否同步加载图片
            displayTask.run()
;
        
} else{
           
engine.submit(displayTask);//提交任务,将任务加入线程池中
        
}
      }
else { //图片不需要处理,则直接呈现,并通知UI
        options.getDisplayer().display(bmp
, imageAware, LoadedFrom.MEMORY_CACHE);
        
listener.onLoadingComplete(uri,imageAware.getWrappedView(),bmp);
     
}
   }
else { //图片不存在内存中
     
if (options.shouldShowImageOnLoading()){//是否显示加载时的图片
        imageAware.setImageDrawable(options.getImageOnLoading(
configuration.resources));
     
} elseif(options.isResetViewBeforeLoading()) {//加载前是否重置图片
         imageAware.setImageDrawable(
null);
     
}

      ImageLoadingInfo imageLoadingInfo =
new ImageLoadingInfo(uri,imageAware,targetSize,memoryCacheKey,
           
options,listener,progressListener,engine.getLockForUri(uri));

//构建LoadAndDisplayImageTask任务
      LoadAndDisplayImageTask displayTask = newLoadAndDisplayImageTask(engine,imageLoadingInfo,
           
defineHandler(options));
      if
(options.isSyncLoading()) { //是否是同步加载
         displayTask.run()
;
     
} else{
        
engine.submit(displayTask); //提交任务,将任务加入线程池中
     
}
   }
}

ImageAware

我们首先来讨论ImageAware:显示图片的对象,可以是ImageView等。

 

ImageAware接口:

public interfaceImageAware {
  
int getWidth();
  
int getHeight();
  
ViewScaleType getScaleType();
  
View getWrappedView();//返回被包装的类的,图片在该类上显示
  
boolean isCollected();//是否被回收
  
int getId();//得到标示Id,
  
boolean setImageDrawable(Drawable drawable);为ImageAwave设置Drawable
  
boolean setImageBitmap(Bitmap bitmap);//为ImageAwave设置bitmap
}

关于id:

ImageLoaderEngine 中用这个 id标识正在加载图片的 ImageAware 和图片内存缓存 key 的对应关系,图片请求前会将内存缓存 key与新的内存缓存 key进行比较,如果不相等,则之前的图片请求会被取消。这样当 ImageAware 被复用时就不会因异步加载(前面任务未取消)而造成错乱了。

ViewAware.java

封装 Android View来显示图片的抽象类,实现了 ImageAware 接口,利用 Reference  WarpView 防止内存泄露。

publicViewAware(View view, booleancheckActualViewSize){
  
if (view==null)thrownewIllegalArgumentException("view must not be null");

   this
.viewRef=newWeakReference<View>(view);
   this
.checkActualViewSize= checkActualViewSize;
}

view 表示需要显示图片的对象。

checkActualViewSize 表示通过 getWidth()  getHeight() 获取图片宽高时返回真实的宽和高,还是 LayoutParams 的宽高,true表示返回真实宽和高。

如果为 true 会导致一个问题, View 在还没有初始化完成时加载图片,这时它的真实宽高为0,会取它 LayoutParams 的宽高,而图片缓存的 key 与这个宽高有关,所以当 View 初始化完成再次需要加载该图片时, getWidth()  getHeight() 返回的宽高都已经变化,缓存 key 不一样,从而导致缓存命中失败会再次从网络下载一次图片。可通过 ImageLoaderConfiguration.Builder.denyCacheImageMultipleSizesInMemory() 设置不允许内存缓存缓存一张图片的多个尺寸。

ImageViewAware

封装 ImageView来显示图片的 ImageAware ,继承了 ViewAware 

构造函数:

publicImageViewAware(ImageView imageView) {
  
super(imageView);
}

应用:

在一个ImageView上显示图片,首先会对ImageView进行包装。

public voiddisplayImage(String uri,ImageView imageView,DisplayImageOptionsoptions,
     
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
   displayImage(uri
, new ImageViewAware(imageView),options,listener,progressListener);
}

 

NonViewAware

仅包含处理图片相关信息却没有需要显示图片的 View ImageAware ,实现了 ImageAware 接口。常用于加载图片后调用回调接口而不是显示的情况。

ImageLoader中的loadImage方法,实现了图片的加载,但是不需要直接显示图片,所以就新建了一个NonViewAware作为参数传入到displayImage方法中。

public voidloadImage(String uri,ImageSize targetImageSize,DisplayImageOptions options,
     
ImageLoadingListener listener,ImageLoadingProgressListenerprogressListener) {
   checkConfiguration()
;
   if
(targetImageSize == null) {
      targetImageSize =
configuration.getMaxImageSize();
  
}
  
if (options==null) {
      options =
configuration.defaultDisplayImageOptions;
  
}

   NonViewAware imageAware =
new NonViewAware(uri,targetImageSize,ViewScaleType.CROP);
  
displayImage(uri,imageAware,options,listener,progressListener);
}

MemoryCache

在displayImage中,加载图片首先需要去内存缓存中查找图片。那么UIL内存是如何实现的呢?

代码中内存缓存就是:configuration.memoryCache,查看configuration类,可以找到:

finalMemoryCachememoryCache;

所以内存缓存类是MemoryCache,来查看一下源码:

/**
 * Interface for memory cache
 */
public interface MemoryCache {
  
/**
    * Puts value into cache by key
    * @return rue
- if value was put into cache successfully, false- if value was notput into
    * cache
    */
  
boolean put(String key,Bitmapvalue);
  
/** Returns value by key. If there is no value for key thennull will be returned. */
  
Bitmap get(Stringkey);
  
/** Removes item by key */
  
Bitmap remove(String key);
  
/** Returns all keys of cache */
  
Collection<String> keys();
  
/** Remove all items from cache */
  
void clear();
}

MemoryCache只是一个接口类。UIL提供了一下八种实现类,当然用户也可以自定义实现自己的内存缓存。

1. 只使用的是强引用缓存 

·        LruMemoryCache(这个类就是这个开源框架默认的内存缓存类每次Bitmap被访问时,它就被移动到一个队列的头部。当Bitmap被添加到一个空间已满的cache时,在队列末尾的Bitmap会被挤出去。

·        FuzzyKeyMemoryCache(使用Comparator使得一些不同的keys被当做是等价的。在对象被put的时候,, 具有“相同”意义的keys将会先被移除)

·        LimitedAgeMemoryCache(当 bitmap加入缓存中的时间超过我们设定的值,将其删除)

 2.使用强引用和弱引用相结合的缓存有

·         UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap

·        LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)

·        FIFOLimitedMemoryCache(先进先出的缓存策略,当超过设定值,先删除最先加入缓存的bitmap

·        LargestLimitedMemoryCache(当超过缓存限定值,先删除最大的bitmap对象)

 3.只使用弱引用缓存

 WeakMemoryCache(这个类缓存bitmap的总大小没有限制,唯一不足的地方就是不稳定,缓存的图片容易被回收掉)


ProcessAndDisplayImageTask

如果内存中存在图片并且图片需要处理,则构建ProcessAndDisplayImageTask来处理和呈现图片。而默认情况下图片是不需要处理的。

@Override
public void run() {
 
 BitmapProcessor processor =imageLoadingInfo.options.getPostProcessor();
  
Bitmap processedBitmap = processor.process(bitmap);
  
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap,imageLoadingInfo,engine,
        
LoadedFrom.MEMORY_CACHE);
  
LoadAndDisplayImageTask.runTask(displayBitmapTask,imageLoadingInfo.options.isSyncLoading(),handler,engine);
}

ProcessAndDisplayImageTask任务的逻辑比较简单,利用BitmapProcessor来处理图片,并构建DisplayBitmapTask对象,然后调用LoadAndDisplayImageTask.runTask方法。

BitmapProcessor

而其中,BitmapProcessor代码如下,UIL只是提供了接口,并没有提供任何实现。默认情况下,UIL也不进行图片的处理。所以用户如果想要处理图片,则需要在在初始化ImageLoader的配置时,实现这个接口,并添加到配置中。

public interfaceBitmapProcessor {
 
   Bitmapprocess(Bitmapbitmap);
}

我们再来看runTask函数,如下,

static voidrunTask(Runnable r,booleansync,Handler handler,ImageLoaderEngine engine) {
  
if (sync){ //是否同步
      r.run()
;//调用run方法只是runnable的一个普通方法调用,还是在主线程里执行。
   } elseif (handler == null) {
      engine.fireCallback(r)
;
  
} else{
      handler.post(r)
;//在子线程中更新UI
  
}
}

DisplayBitmapTask

所以runTask方法就是执行任务r, 而上面的r就是DisplayBitmapTask。我们再来看一下DisplayBitmapTask:

@Override
public void run() {
  
if (imageAware.isCollected()) { //是否被GC回收
      L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED,memoryCacheKey);
     
listener.onLoadingCancelled(imageUri,imageAware.getWrappedView());
  
} elseif(isViewWasReused()) {//判断当前imageAware的缓存key是否正确
      L.d(LOG_TASK_CANCELLED_IMAGEAWARE_REUSED,memoryCacheKey);
     
listener.onLoadingCancelled(imageUri,imageAware.getWrappedView());
  
} else{
      L.d(
LOG_DISPLAY_IMAGE_IN_IMAGEAWARE,loadedFrom,memoryCacheKey);
     
displayer.display(bitmap,imageAware,loadedFrom);//显示图片
     
engine.cancelDisplayTaskFor(imageAware);
     
listener.onLoadingComplete(imageUri,imageAware.getWrappedView(),bitmap);
  
}
}

 

其中,图片的显示有displayer实现,也就是BitmapDisplayer类:

public interfaceBitmapDisplayer {
  
void display(Bitmap bitmap,ImageAwareimageAware,LoadedFrom loadedFrom);
}

 

UIL中实现了五种BitmapDisplay,用户也可以根据需求自行实现BitmapDisplay。

LoadAndDisplayImageTask

如果内存中不存在指定图片,则构建LoadAndDisplayImageTask来加载和呈现图片。

@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()) { //内存中不存在图片
         bmp = tryLoadBitmap()
; //从disc存储或者数据源中获取图片
         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);
           
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);//呈现图片
}

 

主要思路:先从内存缓存中获取图片,存在则直接处理和呈现;如果不存在调用tryLoadBitmap()方法获取图片,并处理。如果还是没有获取图片,则返回。该类的核心方法在于tryLoadBitmap():

tryLoadBitmap

privateBitmaptryLoadBitmap()throwsTaskCancelledException {
   Bitmap bitmap =
null;
   try
{
      File imageFile =
configuration.diskCache.get(uri); //从disc缓存中获取图片
      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) {

//disc中不存在图片
         L.d(
LOG_LOAD_IMAGE_FROM_NETWORK,memoryCacheKey);
        
loadedFrom =LoadedFrom.NETWORK; //实际上并不单单指网络,指从数据源而非缓存中获取

        
String imageUriForDecoding = uri;

//如果图片需要缓存到磁盘,则从网络端下载图片,并缓存到disc
         if
(options.isCacheOnDisk() &&tryCacheImageOnDisk()) {
            imageFile =
configuration.diskCache.get(uri);
            if
(imageFile != null) { //处理uri,指向本地缓存
               imageUriForDecoding =Scheme.FILE.wrap(imageFile.getAbsolutePath());
           
}
         }

         checkTaskNotActual()
;

/*解码图片,如果图片已从数据原加载则传入的url是本地缓存,如果没有加载,则传入的是原始的uri需要进行图片的加载*/
         bitmap = decodeImage(imageUriForDecoding);

         if
(bitmap == null|| bitmap.getWidth() <=0|| bitmap.getHeight() <=0) {
            fireFailEvent(FailType.
DECODING_ERROR, null);
        
}
      }
   }
catch (IllegalStateExceptione) {
      fireFailEvent(FailType.
NETWORK_DENIED, null);
  
} catch(TaskCancelledException e) {
     
throw e;
  
} catch(IOException e) {
      L.e(e)
;
     
fireFailEvent(FailType.IO_ERROR,e);
  
} catch(OutOfMemoryError e) {
      L.e(e)
;
     
fireFailEvent(FailType.OUT_OF_MEMORY,e);
  
} catch(Throwable e) {
      L.e(e)
;
     
fireFailEvent(FailType.UNKNOWN,e);
  
}
  
return bitmap;
}

在tryLoadBitmap方法中:先从本地磁盘缓存中获取图片文件,如果不存在,如果图片需要保存到本地磁盘则通过tryCacheImageOnDisk方法从数据源获取图片并保存到本地磁盘,然后decodeImage(imageUriForDecoding)从磁盘中获取图片并。如果图片不需要保存在磁盘中,则调用decodeImage(imageUriForDecoding)从数据源获取图片。

DiskCache

上面也有提到disc缓存,那么UIL中disc缓存又有哪些实现呢?

磁盘缓存其实就是将文件写入磁盘UIL提供了几种常见的磁盘缓存策略,当然如果你觉得都不符合你的要求,你也可以自己去扩展。在UIL中有着比较完整的存储策略,根据预先指定的空间大小,使用频率(生命周期),文件个数的约束条件,都有着对应的实现策略。最基础的接口DiskCache和抽象类BaseDiskCache。

大致上硬盘缓存部分有两种DiskCache,一种是继承BaseDiskCache的实现的LimitedAgeDiskCache和UnlimitedDiskCache,另一种是使用DiskLruCache的LruDiskCache。以上的磁盘缓存类都实现了DiskCache接口。

·        LimitedAgeDiscCache(设定文件存活的最长时间,当超过这个值,就删除该文件)

·        UnlimitedDiscCache(这个缓存类没有任何的限制,默认的磁盘缓存)

·        LruDiskCache(使用LRU算法,近期最少使用算法)

顺便提一下,上图中naming中的几个类,HashCodeFileNameGenerator和Md5FileNameGenerator实现了FileNameGenerator接口。主要用于根据某种规则生成缓存文件的文件名。

·        HashCodeFileNameGenerator,该类负责获取文件名称的hashcode然后转换成字符串。

·        Md5FileNameGenerator,该类把源文件的名称同过md5加密后保存。

tryCacheImageOnDisk

/**@return<b>true</b>- if image wasdownloaded successfully;<b>false</b>- otherwise */
private boolean tryCacheImageOnDisk()throwsTaskCancelledException {
   L.d(
LOG_CACHE_IMAGE_ON_DISK,memoryCacheKey);

   boolean
loaded;
   try
{
      loaded = downloadImage()
; //下载图片
      if
(loaded) {
        
int width=configuration.maxImageWidthForDiskCache;
         int
height = configuration.maxImageHeightForDiskCache;
         if
(width > 0|| height >0) {
            L.d(
LOG_RESIZE_CACHED_IMAGE_FILE,memoryCacheKey);

//处理并缓存图片
           
resizeAndSaveImage(width,height);//TODO : process boolean result
        
}
      }
   }
catch (IOExceptione) {
      L.e(e)
;
     
loaded = false;
  
}
  
return loaded;
}

ImageDownloder

可以看出真正实现图片下载的是downloadImage()方法:

private booleandownloadImage()throwsIOException {
   InputStream is = getDownloader().getStream(
uri,options.getExtraForDownloader());
   if
(is == null) {
      L.e(ERROR_NO_IMAGE_STREAM
,memoryCacheKey);
      return false;
  
} else{
     
try {
        
return configuration.diskCache.save(uri,is, this);
     
} finally{
         IoUtils.closeSilently(is)
;
     
}
   }
}

privateImageDownloadergetDownloader() {
   ImageDownloader d
;
   if
(engine.isNetworkDenied()) {
      d =
networkDeniedDownloader;
  
} elseif(engine.isSlowNetwork()) {
      d =
slowNetworkDownloader;
  
} else{
      d =
downloader;
  
}
  
return d;
}

 

最后通过ImageDownloder类来负责图片的加载。在UIL提供了ImageDownloder类的实现类BaseImageDownloader:

在BaseImageDownloader中,我们来看一下downloadImage()中调用的getStream方法:

@Override
public InputStreamgetStream(String imageUri,Object extra) throwsIOException {
  
switch (Scheme.ofUri(imageUri)){
     
case HTTP:
     
case HTTPS:
        
return getStreamFromNetwork(imageUri,extra);
      case
FILE:
        
return getStreamFromFile(imageUri,extra);
      case
CONTENT:
        
return getStreamFromContent(imageUri,extra);
      case
ASSETS:
        
return getStreamFromAssets(imageUri,extra);
      case
DRAWABLE:
        
return getStreamFromDrawable(imageUri,extra);
      case
UNKNOWN:
     
default:
        
return getStreamFromOtherSource(imageUri,extra);
  
}
}

可以知道ImageDownloder会根据图片的来源类型,用不同的方式获取图片,不仅仅是从网络端下载,也可能是来着asset,资源文件等中的图片。

decodeImage(StringimageUri)

privateBitmapdecodeImage(StringimageUri)throwsIOException {
   ViewScaleType viewScaleType =
imageAware.getScaleType();
  
ImageDecodingInfo decodingInfo = newImageDecodingInfo(memoryCacheKey,imageUri,uri,targetSize,viewScaleType,
        
getDownloader(),options);
   return
decoder.decode(decodingInfo);
}

ImageDecoder

UIL提供 BaseImageDecoder,实现ImageDecoder接口。

以下为BaseImageDecoderdecode方法,将输入流转化为bitmap

@Override
public Bitmapdecode(ImageDecodingInfo decodingInfo)throwsIOException {
   Bitmap decodedBitmap
;
  
ImageFileInfo imageInfo;

  
InputStream imageStream = getImageStream(decodingInfo);//获取输入流
   if
(imageStream == null) {
      L.e(
ERROR_NO_IMAGE_STREAM,decodingInfo.getImageKey());
      return null;
  
}
  
try { //将输入流转化为bitmap
      imageInfo = defineImageSizeAndRotation(imageStream
,decodingInfo);
     
imageStream = resetStream(imageStream,decodingInfo);
     
Options decodingOptions =prepareDecodingOptions(imageInfo.imageSize,decodingInfo);
     
decodedBitmap = BitmapFactory.decodeStream(imageStream, null,decodingOptions);
  
} finally{
      IoUtils.closeSilently(imageStream)
;
  
}

  
if (decodedBitmap==null) {
      L.e(
ERROR_CANT_DECODE_IMAGE,decodingInfo.getImageKey());
  
} else{
      decodedBitmap =considerExactScaleAndOrientatiton(decodedBitmap
,decodingInfo,imageInfo.exif.rotation,
           
imageInfo.exif.flipHorizontal);
  
}
  
return decodedBitmap;
}

 

再来看一下getImageStream方法,实际上调用的是ImageDownloader的getStream方法(这个方法上面已经提过了)。

protectedInputStreamgetImageStream(ImageDecodingInfodecodingInfo)throwsIOException {
  
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(),decodingInfo.getExtraForDownloader());
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值