前言
提到图片加载框架,最经典的应该就是Universal-Image-Loader了。此篇文章不会涉及如何使用,而是通过查看源码的方式对框架的原理进行理解。
源码解析
当我们要显示一张图片时,实际上只需要一行代码就可以做到:
ImageLoader.getInstance().displayImage(address,imgv);
getInstance()很显然是一个单例模式,那么我们跟进去displayImage方法:
public void displayImage(String uri, ImageView imageView) {
displayImage(uri, new ImageViewAware(imageView), null, null, null);
}
在这里创建了一个ImageViewAware对象。
public ImageViewAware(ImageView imageView) {
super(imageView);
}
//他的父类是ViewAware,贴上父类的构造函数:
public ViewAware(View view) {
this(view, true);
}
public ViewAware(View view, boolean checkActualViewSize) {
if (view == null) throw new IllegalArgumentException("view must not be null");
this.viewRef = new WeakReference<View>(view);
this.checkActualViewSize = checkActualViewSize;
}
这里涉及到了一个概念:强引用、弱引用、虚引用的区别。
- 强引用。我们平时使用的引用默认都是强引用。如果一个对象可以被某个强引用找到(即存在一个强引用指向该对象),那么这个对象一定不会被Java虚拟机所回收。如果当内存不够时,强引用指向的对象又无法被回收,那么,就会抛出OutOfMemory异常。
- 弱引用(SoftReference)。如果一个对象不存在指向它的强引用,并且存在指向它的弱引用。那么,当内存不够时,Java虚拟机会将该对象回收。而如果内存充足,那么就不会回收该对象。
- 虚引用(WeakReference)。如果一个对象不存在指向它的强引用和弱引用,并且存在指向它的虚引用。当虚拟机进行垃圾回收时,无论此时内存是否充足,这个对象都会被回收。
看到这里,我想大家就会明白了,使用虚引用能够有效的避免内存泄露。
我们继续查看displayImage方法,代码比较长,把源码的解释写到了注释里:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
displayImage(uri, imageAware, options, null, listener, progressListener);
}
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration(); //用来检查是否对ImageLoader进行了配置,只需要在Application中初始化一次
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) { //检查是否需要回调监听
listener = defaultListener;
}
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
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;
}
if (targetSize == null) {
//在获取图片之前,首先查看ImageView的大小
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
//使用url和大小作为主键
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
//使用HashMap将ImageView的id和主键对应存放起来,等效于为ImageView设置tag,从而解决ListView图片乱序等问题。
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);
}
}
}
重要步骤的方法的解释已经写到了注释里,就不再赘述了。首先来看一下defineTargetSizeForView方法。
public static ImageSize defineTargetSizeForView(ImageAware imageAware, ImageSize maxImageSize) {
int width = imageAware.getWidth();
if (width <= 0) width = maxImageSize.getWidth();
int height = imageAware.getHeight();
if (height <= 0) height = maxImageSize.getHeight();
return new ImageSize(width, height);
}
public int getWidth() { ///ImageViewAware类
int width = super.getWidth();
if (width <= 0) {
ImageView imageView = (ImageView) viewRef.get();
if (imageView != null) {
width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check maxWidth parameter
}
}
return width;
}
public int getWidth() { //ViewAware类
View view = viewRef.get();
if (view != null) {
final ViewGroup.LayoutParams params = view.getLayoutParams();
int width = 0;
if (checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {
width = view.getWidth(); // Get actual image width
}
if (width <= 0 && params != null) width = params.width; // Get layout width parameter
return width;
}
return 0;
}
宽度和高度的逻辑是一样的,在这里以宽度为例。在ViewAware类的getWidth()方法中,可以看到,如果ImageView的宽度不是wrap_content,那么就返回ImageView的宽度;如果ImageView的宽度是wrap_content,那么获取宽度的逻辑应该转移到ImageViewAware类的getWidth方法,如果ImageView设置了maxWidth这个属性,那么就返回maxWidth,否则获取宽度的逻辑应该转移到defineTargetSizeForView类中,maxImageSize.getWidth()方法返回的数值,实际上是我们在初始化ImageLoader设置的参数。
接下来我们来看一下最主要的流程的代码,也就是displayImage最底部的代码。很容易看出来,LoadAndDisplayImageTask应该是一个Thread,跟进去看一下run方法。
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();
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();
}
//将图像显示在ImageView中
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
首先我们来看一下tryLoadBitmap方法。
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
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()) { //tryCacheImageOnDisk方法中,首先从网络中获取图像,然后保存到硬盘
//这时候硬盘中一定存在该图像
imageFile = configuration.diskCache.get(uri);
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);
}
}
} 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();
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 (IOException e) {
L.e(e);
loaded = false;
}
return loaded;
}
private boolean downloadImage() throws IOException {
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);
}
}
}
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();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey,
Scheme.FILE.wrap(targetFile.getAbsolutePath()), uri, targetImageSize, ViewScaleType.FIT_INSIDE,
getDownloader(), specialOptions);
Bitmap bmp = decoder.decode(decodingInfo);
if (bmp != null && configuration.processorForDiskCache != null) {
L.d(LOG_PROCESS_IMAGE_BEFORE_CACHE_ON_DISK, memoryCacheKey);
bmp = configuration.processorForDiskCache.process(bmp);
if (bmp == null) {
L.e(ERROR_PROCESSOR_FOR_DISK_CACHE_NULL, memoryCacheKey);
}
}
if (bmp != null) {
//将图像保存到硬盘中
saved = configuration.diskCache.save(uri, bmp);
bmp.recycle();
}
}
return saved;
}
通过上面的分析,我们知道了如何获取到Bitmap。然后来看一下将Bitmap显示出来的部分。
final class DisplayBitmapTask implements Runnable {
.......
@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);
//将图像显示在ImageView中
displayer.display(bitmap, imageAware, loadedFrom);
//从HashMap中,将ImageView的id和url组成的键值对删去
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
/** Checks whether memory cache key (image URI) for current ImageAware is actual */
private boolean isViewWasReused() {
String currentCacheKey = engine.getLoadingUriForView(imageAware);
return !memoryCacheKey.equals(currentCacheKey);
}
}
到这里,整个流程就分析完了~希望让你有所收获~