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接口。
以下为BaseImageDecoder的decode方法,将输入流转化为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());
}