本文仅对UIL中一些简单的用例做解析
画出源码目录树
首先,用脚本生成了该项目源码的目录树,然后大致浏览一下文件内容,猜测其作用:
-[ java ]
-[ com ]
-[ nostra13 ]
-[ universalimageloader ]
-[ cache ]
-[ disc ]
|- DiskCache.java
|
-[ impl ]
|- BaseDiskCache.java
|- LimitedAgeDiskCache.java
|- UnlimitedDiskCache.java
|
-[ ext ]
|- DiskLruCache.java
|- LruDiskCache.java
|- StrictLineReader.java
|- Util.java
|
-[ naming ]
|- FileNameGenerator.java
|- HashCodeFileNameGenerator.java
|- Md5FileNameGenerator.java
|
-[ memory ]
|- BaseMemoryCache.java
|- LimitedMemoryCache.java
|- MemoryCache.java
|
-[ impl ]
|- FIFOLimitedMemoryCache.java
|- FuzzyKeyMemoryCache.java
|- LargestLimitedMemoryCache.java
|- LimitedAgeMemoryCache.java
|- LRULimitedMemoryCache.java
|- LruMemoryCache.java
|- UsingFreqLimitedMemoryCache.java
|- WeakMemoryCache.java
|
-[ core ]
|- DefaultConfigurationFactory.java
|- DisplayBitmapTask.java
|- DisplayImageOptions.java
|- ImageLoader.java
|- ImageLoaderConfiguration.java
|- ImageLoaderEngine.java
|- ImageLoadingInfo.java
|- LoadAndDisplayImageTask.java
|- ProcessAndDisplayImageTask.java
|
-[ assist ]
|- ContentLengthInputStream.java
|- FailReason.java
|- FlushedInputStream.java
|- ImageScaleType.java
|- ImageSize.java
|- LoadedFrom.java
|- QueueProcessingType.java
|- ViewScaleType.java
|
-[ deque ]
|- BlockingDeque.java
|- Deque.java
|- LIFOLinkedBlockingDeque.java
|- LinkedBlockingDeque.java
|
-[ decode ]
|- BaseImageDecoder.java
|- ImageDecoder.java
|- ImageDecodingInfo.java
|
-[ display ]
|- BitmapDisplayer.java
|- CircleBitmapDisplayer.java
|- FadeInBitmapDisplayer.java
|- RoundedBitmapDisplayer.java
|- RoundedVignetteBitmapDisplayer.java
|- SimpleBitmapDisplayer.java
|
-[ download ]
|- BaseImageDownloader.java
|- ImageDownloader.java
|
-[ imageaware ]
|- ImageAware.java
|- ImageViewAware.java
|- NonViewAware.java
|- ViewAware.java
|
-[ listener ]
|- ImageLoadingListener.java
|- ImageLoadingProgressListener.java
|- PauseOnScrollListener.java
|- SimpleImageLoadingListener.java
|
-[ process ]
|- BitmapProcessor.java
|
-[ utils ]
|- DiskCacheUtils.java
|- ImageSizeUtils.java
|- IoUtils.java
|- L.java
|- MemoryCacheUtils.java
|- StorageUtils.java
从常用case入手,推断其项目架构
官网上给出的最简单的使用例子如下所示:
ImageLoader imageLoader = ImageLoader.getInstance(); // Get singleton instance
// Load image, decode it to Bitmap and display Bitmap in ImageView (or any other view
// which implements ImageAware interface)
imageLoader.displayImage(imageUri, imageView);
// Load image, decode it to Bitmap and return Bitmap to callback
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// Do whatever you want with Bitmap
}
});
// Load image, decode it to Bitmap and return Bitmap synchronously
Bitmap bmp = imageLoader.loadImageSync(imageUri);
下面一步步进行分析。
ImageLoader
看到 ImageLoader.getInstance()
这一句,应该能马上认出这是一个singleton。代码如下所示:
public class ImageLoader {
...
private volatile static ImageLoader instance;
/** Returns singleton class instance */
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
protected ImageLoader() {
}
构造函数是空的。volatile
关键字的解释可以看这里
DisplayImage
接下来到imageLoader.displayImage(imageUri, imageView);
这一句。其源码如下所示:
public void displayImage(String uri, ImageView imageView) {
displayImage(uri, new ImageViewAware(imageView), null, null, null);
}
注意到ImageView
被包装成了ImageViewAware
,ImageViewAware
继承于ImageAware
,如下所示:
<ImageAware> {
getWidth();
getHeight();
getScaleType();
getWrappedView();
isCollected();
getId();
setImageDrawable();
setImageBitmap();
}
一路调用到如下所示方法:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
}
其源代码如下所示,一边猜想它的调用逻辑,一边在关键点写下注释:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
// 1. 做一些合法性检查
checkConfiguration();
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {
listener = defaultListener; // 默认是SimpleImageLoadingListener,是ImageLoadingListener的空实现
}
if (options == null) {
options = configuration.defaultDisplayImageOptions; //在init()中初始化
}
//2. 如果uri为空,则取消对应imageAware的显示
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;
}
//3. 设置图片显示大小
if (targetSize == null) {
// 什么是target?
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()) {// 4. 如果hit cache,则直接显示
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 {// 5. 如果miss cache,就去下载它
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);
}
}
}
代码流程见文中的注释。可以看出三个需要重点研究的对象:Cache,Engine和Task,先说后面两个。
Engine
ImageLoaderEngine
首先看一下Engine的初始化流程。Enginer在ImageLoader的init中被实例化:
engine = new ImageLoaderEngine(configuration);
初始化代码如下所示:
ImageLoaderEngine(ImageLoaderConfiguration configuration) {
this.configuration = configuration;
taskExecutor = configuration.taskExecutor;
taskExecutorForCachedImages = configuration.taskExecutorForCachedImages;
taskDistributor = DefaultConfigurationFactory.createTaskDistributor();
}
可以看到新出现的角色:Executor。这是一个接口,代码如下所示:
public interface Executor {
void execute(java.lang.Runnable runnable);
}
可以合理猜想这是一个类似Runnable的接口,包装了在某个线程中执行的业务。
prepareDisplayTaskFor
再来看看engine的prepareDisplayTaskFor()方法:
void prepareDisplayTaskFor(ImageAware imageAware, String memoryCacheKey) {
cacheKeysForImageAwares.put(imageAware.getId(), memoryCacheKey);
}
还有与之对应的:
void cancelDisplayTaskFor(ImageAware imageAware) {
cacheKeysForImageAwares.remove(imageAware.getId());
}
还有:
String getLoadingUriForView(ImageAware imageAware) {
return cacheKeysForImageAwares.get(imageAware.getId());
}
其中 cacheKeysForImageAwares 定义如下:
private final Map<Integer, String> cacheKeysForImageAwares = Collections
.synchronizedMap(new HashMap<Integer, String>());
可见prepareDisplayTaskFor保存了image可memory cache的key的映射关系。猜想是用于快速判断当前image是否有正在被处理。
sumit
再来看看engine的sumit方法。按字面意义猜想是将一个task放到一个调度队列中按照指定的策略执行。
/** Submits task to execution pool */
void submit(ProcessAndDisplayImageTask task) {
initExecutorsIfNeed();
taskExecutorForCachedImages.execute(task);
}
private void initExecutorsIfNeed() {
if (!configuration.customExecutor && ((ExecutorService) taskExecutor).isShutdown()) {
taskExecutor = createTaskExecutor();
}
if (!configuration.customExecutorForCachedImages && ((ExecutorService) taskExecutorForCachedImages)
.isShutdown()) {
taskExecutorForCachedImages = createTaskExecutor();
}
}
private Executor createTaskExecutor() {
return DefaultConfigurationFactory
.createExecutor(configuration.threadPoolSize, configuration.threadPriority,
configuration.tasksProcessingType /* FIFO, LIFO */);
}
逻辑比较简单,先看一下DefaultConfigurationFactory的createExecutor方法:
/** Creates default implementation of task executor */
public static Executor createExecutor(int threadPoolSize, int threadPriority,
QueueProcessingType tasksProcessingType) {
boolean lifo = tasksProcessingType == QueueProcessingType.LIFO;
BlockingQueue<Runnable> taskQueue =
lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
createThreadFactory(threadPriority, "uil-pool-"));
}
可以看到,创建一个executor对象需要指定线程池大小,线程优先级,队列性质(FIFO/LIFO)和一个负责新建线程的工厂类。
ThreadPoolExecutor、Executor这些都是java concurrent包内置的类,详细使用方法可见[这篇文章](),这里不展开。
由此可以看出,engine中维护着executor,executor负责根据预先设置好的调度策略执行task。
ProcessAndDisplayImageTask
以ProcessAndDisplayImageTask为例,先回忆一下调用代码:
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
engine,bmp这两个没什么好说的,重点放在后两者。ImageLoadingInfo是一个Data Object,保存着这次Task的所有必要信息:
final class ImageLoadingInfo {
final String uri;
final String memoryCacheKey;
final ImageAware imageAware;
final ImageSize targetSize;
final DisplayImageOptions options;
final ImageLoadingListener listener;
final ImageLoadingProgressListener progressListener;
final ReentrantLock loadFromUriLock;
...
}
defineHandler的代码如下所示:
private static Handler defineHandler(DisplayImageOptions options) {
Handler handler = options.getHandler();
if (options.isSyncLoading()) { //如果是同步加载图片(堵塞),那么这个handler不起作用,设为null
handler = null;
} else if (handler == null && Looper.myLooper() == Looper.getMainLooper()) {
handler = new Handler(); // 没有指定Handler的情况下,只有主线程才能执行显示任务。
}
return handler;
}
这个方法的作用是返回一个执行显示bitmap动作的handler。然后我们回到ProcessAndDisplayImageTask:
final class ProcessAndDisplayImageTask implements Runnable {
private static final String LOG_POSTPROCESS_IMAGE = "PostProcess image before displaying [%s]";
private final ImageLoaderEngine engine;
private final Bitmap bitmap;
private final ImageLoadingInfo imageLoadingInfo;
private final Handler handler;
...
}
ProcessAndDisplayImageTask继承Runnable接口。当engine执行sumit时,ProcessAndDisplayImageTask的run方法会被调度执行。
@Override
public void run() {
L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);
BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor();
Bitmap processedBitmap = processor.process(bitmap);
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine,
LoadedFrom.MEMORY_CACHE); // 指定了DisplayBitmapTask的来源
LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}
其中PostProcessor为后置处理类,可以利用这个类对已载入的bitmap进行处理(ProcessAndDisplayImageTask中bitmap已经加载完成了)。处理完后将bitmap转交给DisplayBitmapTask类继续进行处理。下面进入LoadAndDisplayImageTask.runTask()方法:
static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
if (sync) {
r.run();
} else if (handler == null) {
engine.fireCallback(r);
} else {
handler.post(r);
}
}
可以看到,如果是同步显示的,就地执行DisplayBitmapTask的run方法。如果没有指定handler,则在engine中执行,否则在指定的handler中执行。DisplayBitmapTask。
DisplayBitmapTask
其源码如下所示:
final class DisplayBitmapTask implements Runnable {
...
private final Bitmap bitmap;
private final String imageUri;
private final ImageAware imageAware;
private final String memoryCacheKey;
private final BitmapDisplayer displayer;
private final ImageLoadingListener listener;
private final ImageLoaderEngine engine;
private final LoadedFrom loadedFrom;
public DisplayBitmapTask(Bitmap bitmap, ImageLoadingInfo imageLoadingInfo, ImageLoaderEngine engine,
LoadedFrom loadedFrom) {
this.bitmap = bitmap;
imageUri = imageLoadingInfo.uri;
imageAware = imageLoadingInfo.imageAware;
memoryCacheKey = imageLoadingInfo.memoryCacheKey;
displayer = imageLoadingInfo.options.getDisplayer();
listener = imageLoadingInfo.listener;
this.engine = engine;
this.loadedFrom = loadedFrom;
}
@Override
public void run() {
...
}
/** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
...
}
}
重点看其中的run方法:
@Override
public void run() {
if (imageAware.isCollected()) {
L.d(LOG_TASK_CANCELLED_IMAGEAWARE_COLLECTED, memoryCacheKey);
listener.onLoadingCancelled(imageUri, imageAware.getWrappedView());
} else if (isViewWasReused()) {
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);
}
}
首先,代码判断bitmap是否需要显示,如果不需要,则回调onLoadingCancelled。否则交给displayer去显示bitmap。最后再engine中清除imageAware的记录,并回调onLoadingComplete。
在这里遇到两个比较困惑的地方:isCollected和isViewWasReused。因为满足这两个条件都不需要执行display,代表image此时是collected和view已经reused了。
collected
当displayTask在执行时发现imageAware已经被回收了(GC或者别的原因),就会跳过显示这个bitmap。
isViewWasReused
isViewWasReused的代码如下所示:
/** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
String currentCacheKey = engine.getLoadingUriForView(imageAware);
return !memoryCacheKey.equals(currentCacheKey);
}
isViewWasReused返回true则说明当前task的uri不是engine对这个imageAware最后load的uri。也就是说用户在前一个uri还没有完全载入的时候,又对相同imageAware发起了load task。因为以最后一次意图加载的uri为准,所以该次task跳过显示bitmap。
接下来研究一下BitmapDisplayer这个类。BitmapDisplayer在DisplayImageOptions的Builder子类中赋值:
private BitmapDisplayer displayer = DefaultConfigurationFactory.createBitmapDisplayer();
最后追踪到:
/** Creates default implementation of {@link BitmapDisplayer} - {@link SimpleBitmapDisplayer} */
public static BitmapDisplayer createBitmapDisplayer() {
return new SimpleBitmapDisplayer();
}
public final class SimpleBitmapDisplayer implements BitmapDisplayer {
@Override
public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {
imageAware.setImageBitmap(bitmap);
}
}
可见BitmapDisplayer只是简单地调用了setImageBitmap方法:
public class ImageViewAware extends ViewAware {
...
@Override
protected void setImageBitmapInto(Bitmap bitmap, View view) {
((ImageView) view).setImageBitmap(bitmap);
}
}
LoadAndDisplayImageTask
当bitmap的memory cache存在时,运行ProcessAndDisplayImageTask,否则运行LoadAndDisplayImageTask。LoadAndDisplayImageTask涉及到网络下载和缓存策略,重点分析其中的run方法:
@Override
public void run() {
if (waitIfPaused()) return; //若engine当前处于Pause状态,则等待其Resume
if (delayIfNeed()) return; //是否需要显示一下loading image(防止闪烁)
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(); //对一个loadingInfo上锁
Bitmap bmp;
try {
checkTaskNotActual(); //检查bitmap是否被回收和当前uri是否与imageAware要显示的uri一致
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
bmp = tryLoadBitmap(); //一系列的缓存命中过程,下详述
if (bmp == null) return; // listener callback already was fired
checkTaskNotActual();
checkTaskInterrupted();
if (options.shouldPreProcess()) { //前置处理,影响cache
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); //将bitmap放到memory cache中
}
} 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);//后置处理,不影响cache
if (bmp == null) {
L.e(ERROR_POST_PROCESSOR_NULL, memoryCacheKey);
}
}
checkTaskNotActual();
checkTaskInterrupted(); //检查当前thread是否被interrupt了
} catch (TaskCancelledException e) {
fireCancelEvent();
return;
} finally {
loadFromUriLock.unlock(); //解锁
}
//见 ProcessAndDisplayImageTask 关于这段代码的解析
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
代码执行流程方面的解析见中文注释,值得注意的有:
waitIfPaused()和delayIfNeed()方法返回的时候都调用了isTaskNotActual()方法。这个方法的作用是判断当前的uri是否用户最后提交的uti(isViewCollected() || isViewReused())。
tryLoadBitmap()方法
tryLoadBitmap()的代码如下所示:
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
File imageFile = configuration.diskCache.get(uri); //是否命中磁盘缓存(DiskCache类)
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())); //由Decoder来进行decode
}
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()) { //尝试从网络下载图片,并将其保存到disk上
imageFile = configuration.diskCache.get(uri); // 再次从disk读入图片
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();
bitmap = decodeImage(imageUriForDecoding);
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
fireFailEvent(FailType.DECODING_ERROR, null); //在engine或指定handler上执行onLoadingFailed回调
}
}
} catch (IllegalStateException e) {
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;
}
代码执行流程方面的解析见中文注释,留意其中的tryCacheImageOnDisk()方法:
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
L.d(LOG_CACHE_IMAGE_ON_DISK, memoryCacheKey);
boolean loaded;
try {
loaded = downloadImage(); //下载image
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); // 将image保存到disk
}
}
} catch (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
private boolean downloadImage() throws IOException {
InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader()); // 调用Downloader下载bitmap
if (is == null) {
L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
return false;
} else {
try {
return configuration.diskCache.save(uri, is, this); // 将bitmap直接保存到DiskCache
} finally {
IoUtils.closeSilently(is);
}
}
}
private boolean resizeAndSaveImage(int maxWidth, int maxHeight) throws IOException {
// Decode image file, compress and re-save it
boolean saved = false;
File targetFile = configuration.diskCache.get(uri);
if (targetFile != null && targetFile.exists()) {
ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options)
.imageScaleType(ImageScaleType.IN_SAMPLE_INT).build(); // cloneFrom方法在已有的options的基础上添加其他option
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
getDownloader(), specialOptions);
Bitmap bmp = decoder.decode(decodingInfo); // Decoder根据targetImageSize,从File中解析出Bitmap
if (bmp != null && configuration.processorForDiskCache != null) {
L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
bmp = configuration.processorForDiskCache.process(bmp); // 保存到disk前对bitmap进行处理
if (bmp == null) {
L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
}
}
if (bmp != null) {
saved = configuration.diskCache.save(uri, bmp); // 将处理后的Bitmap保存到DiskCache
bmp.recycle();
}
}
return saved;
}
代码执行流程方面的解析见中文注释。值得注意的是,为什么要多次先写bitmap到文件再从文件将其读出来再使用?贴一张官网的流程图帮助大家思考:
至此,displayImage的流程简析完成。下面看一下第二个用例。
loadImage
用例的代码如下,作用是仅使用imageLoader的缓存和下载功能,显示部分由用户负责。
// Load image, decode it to Bitmap and return Bitmap to callback
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// Do whatever you want with Bitmap
}
});
最终会调用:
public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
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);
}
使用的依然是displayImage方法,但是传入了一个NonViewAware类。NonViewAware不包含ImageView对象,setImageDrawable和setImageBitmap方法都是空实现。
loadImageSync
用例代码如下所示:
// Load image, decode it to Bitmap and return Bitmap synchronously
Bitmap bmp = imageLoader.loadImageSync(imageUri);
最终调用如下所示:
public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) {
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
options = new DisplayImageOptions.Builder().cloneFrom(options).syncLoading(true).build();// cloneFrom方法在已有的options的基础上添加syncLoading特性
SyncImageLoadingListener listener = new SyncImageLoadingListener();
loadImage(uri, targetImageSize, options, listener);
return listener.getLoadedBitmap();
}
可以看出loadImageSync()是通过调用loadImage()来实现的。注意其传入参数SyncImageLoadingListener对象。SyncImageLoadingListener实现了SimpleImageLoadingListener接口,在onLoadingComplete()回调中将loadedImage保存下来,其后可通过getLoadedBitmap方法取出。
画框图
首先整理出上述代码中的关键类,然后大致画出它们之间的调用关系,如下所示:
+----------------------------+
|ImageDownloader |
| +------------------------+ |
| | ImageLoaderEngine | |
| | +------------------+ | |
| | |Deque | | |
| | | +----+ +----+ | | |
| | | |Task| |Task| ...| | |
| | | +----+ +----+ | | |
| | +------------------+ | |
| +------------------------+ |
| +-------------+ |
| |configuration| |
| +-------------+ |
+----------------------------+
+-------+
| Utils |
+-------+
+----------------------------------------------------+
|a running task |
| + |
| | |
| +----v----+ +---------+ |
| |mem cache+-------->Processor| |
| +----+--^-+ +--+------+ |
| | | | |
| | | | |
| +----v--+--+ | +---------+ |
| +-------->disk cache+-----+ +-->Displayer| |
| | +----+--^--+ | +---------+ |
| +---+-----+ | | | |
| |Processor| | | | |
| +---^-----+ +--v--+-+ | |
| +----------+Decoder| | |
| +-----^-+ | |
| | | |
| | +--v-------+ |
| +-----+Downloader| |
| +----------+ |
| |
+----------------------------------------------------+
本文只是对UIL的结构做了简单的解析,等到用的时候踩坑了再深入了解吧。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。