DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.bitmapConfig(Bitmap.Config.RGB_565)
.build();
ImageLoader.getInstance().displayImage(imageUrl,mImageView,options);
我们可以看到,UniversalImageLoader的用法还是很简单的,还有更多的配置我这里就不赘述了。这个示例中用到的设置主要是cacheInMemory(true)允许使用内存缓存,cacheOnDisk(true)允许使用磁盘缓存。
我们可以看到,在使用UIL时,我们最终调用了ImageLoader中的displayImage方法。方法源码如下:
public void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
displayImage(uri, new ImageViewAware(imageView), options, null, null);
}
其实这个是个重载方法,我这里只是抽取了一个最简单的示例。可以看到,Universal Image Loader是将ImageView包装成了ImageViewAware来进行后续逻辑的。
/**
* Wrapper for Android {@link android.widget.ImageView ImageView}. Keeps weak reference of ImageView to prevent memory
* leaks.
*
* @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* @since 1.9.0
*/
public class ImageViewAware extends ViewAware {
从该类的注释可以看出,它是将ImageView做了一个封装,同时它会将ImageView的强引用改为弱引用。即当内存不足时可以更好地对ImageView进行回收。同时该类还可以获取到ImageView的宽高,对ImageView进行裁剪,减少内存的使用。
多个displayImage()方法其实是在不断向深层次调用。我们进入最终一层的displayImage()方法,完整方法的源码如下:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();
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) {
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()) {
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);
}
} 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);
}
}
}
<1>从源码中可以看到,首先我们调用了checkConfiguration()方法,代码如下:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
checkConfiguration();
...
}
该方法对全局变量configuration进行了判断,如果为null,则抛出异常。
<2>接着源码中调用了如下代码:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
if (TextUtils.isEmpty(uri)) {
...
}
...
}
这里主要是判断url为空时,我们如何处理。
<3>在<2>中的if判断中,有如下代码
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
if (TextUtils.isEmpty(uri)) {
engine.cancelDisplayTaskFor(imageAware);
...
}
...
}
这里会记录我们加载的任务,加载图片的时候我们会将ImageView的id和图片的url,加上尺寸,加载到一个集合队列当中。
看一下engine到底是一个什么集合队列。在ImageLoader的全局变量中,我们找到了这个engine
private ImageLoaderEngine engine;
接着我们进入ImageLoaderEngine类中,我们在ImageLoaderEngine类的全局变量中又找到了如下属性
private final Map uriLocks = new WeakHashMap();
这里我们就可以看到它的实现还是通过一个HashMap实现的。这就说明我们所有的属性都会添加到这个HashMap当中。
那么我们回到ImageLoader类中,再看刚才的代码,我们就明白了,当我们判断我们的url为空后,我们会把ImageView的一些属性添加到这个HashMap当中,加载完之后我们会移除这个方法,因为它是cancelDisplay,说明加载完之后它会移除,这是对我们内存的一种优化,它不会将我们所有的配置全放入内存中占用内存空间。
<4>我们接着往后看
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
if (TextUtils.isEmpty(uri)) {
...
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
...
}
...
}
...
}
这里我们会调用getImageForEmptyUri将我们的属性配置给ImageView。
<5>我们接着往后看
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
if (TextUtils.isEmpty(uri)) {
...
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
return;
}
...
}
在这个if判断的最后,UIL会通过一个listener的回调,告诉我们这次任务已经完成了。回顾整个整个if模块,代码如下:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
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;
}
...
}
它就是判断url为空时进行默认图片的加载。
<6>当url不为空时,我们继续往后看,会执行如下代码:
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware,configuration.getMaxImageSize());
}
...
}
这个方法主要是将ImageView的宽高封装成ImageSize这个类型。如果我们获取ImageView的高度、宽度为0的话,我们就会使用手机屏幕的宽度、高度。
<7>获取targetSize之后,它会进行MemoryCache的操作,即从内存中获取我们需要的图片。
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
...
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
...
}
这里注意用到的是get方法。跟入这个get方法,可以看到MemoryCache是个接口类,它的具体实现是LruMemoryCache。get方法源码如下:
@Override
public final Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
synchronized (this) {
return map.get(key);
}
}
这里其实就是从缓存中获取Bitmap对象,默认使用的是LruCache算法。LruCache算法就是最近最少使用缓存对象。我们会把最近最少缓存对象从队列中移除,然后把最新的所要使用的缓存对象添加到队列当中。
<8>回到displayImage方法
在从内存中获取到Bitmap后,我们要对这个Bitmap进行判断
if (bmp != null && !bmp.isRecycled()) {
★1
}else{
★2
}
这个判断是Bitmap对象是否为空,是否已经进行了回收。
<9>在★1处,即Bitmap不为空并且未被回收时,我们进行如下判断
if (options.shouldPostProcess()) {
★★1
}else{
★★2
}
这个判断的意思是我们后期是否应当对图片进行处理。这里注意,这个postProcessor属性我们是可以在使用UniversalImageLoader的时候在Option中设置的。
<10>在★2处,即从内存中获取的bmp为空或已回收时,我们会执行如下代码:
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,defineHandler(options));
这里可以看出最后我们是通过这个Task进行图片的加载。其实所有图片加载的原理都是一样的,它都是内部需要一个线程池,然后不断发送消息给线程池,来进行图片的加载请求。
<11>我们进入LoadAndDisplayImageTask类来看一下它的内容
final class LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener {
...
}
这里该类实现的是一个Runnable接口,说明它就是一个线程,去执行图片的加载。
接着我们看一下它的run方法,代码如下:
@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();
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);
}
可以看到,在run方法中它首先执行了如下代码:
@Override
public void run() {
if (waitIfPaused()) return;
...
}
这个方法的场景主要用于滚动的ListView加载图片的时候。有时候为了使我们滑动更加流畅,我们会选择手指在滑动的时候,不去加载图片。所以才提供了这样的方法。ListView可以设置一个Listener,来使滑动时不加载图片。
在waitIfPaused()方法中,有如下代码:
private boolean waitIfPaused() {
...
if (pause.get()) {
synchronized (engine.getPauseLock()) {
...
}
}
return isTaskNotActual();
}
synchronized (engine.getPauseLock())其实就是获得找个栈的一个锁,即如果获得一个停止的锁就不去加载找个图片。这个方法最终返回isTaskNotActual(),即不仅要监听ListView的滑动,也要看这个图片加载Task是否是活动的。isTaskNotActual()方法源码如下:
private boolean isTaskNotActual() {
return isViewCollected() || isViewReused();
}
该方法内部主要判断View是否已被回收,是否这个View被复用了。也就是它会判断ListView在滑动时,ImageView是否被垃圾回收机制回收了。如果回收了的话,刚才所说的Task中run方法就直接返回了,不会做相应的加载。同样,如果ImageView已经被复用了,这时候我们的run方法也会直接返回。为什么会提到复用?ListView当中有很多会复用item的对象,当我们加载ListView时,首先会加载第一页图片。第一页图片还没加载完成就快速滑动。这时候我们使用isViewReused()这个方法来避免这些没有可见的item去加载图片,而直接加载当前已经显示在桌面上的图片。
<11>让我们回到LoadAndDisplayImageTask的run方法中
执行完所有判断后,它会获得一个重入锁
@Override
public void run() {
...
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
...
}
而这个重入锁它是通过ImageLoaderEngine类中的getLockForUri()方法获得的。
而重入锁有什么作用?在ListView中,某个item获取图片时,我们将这个item滚出界面后又滚进来,这时如果我们没有对它加锁,那这个item它又会加载一次图片。这样如果在短时间内频繁滚出滚进的话,那对我们的内存会有很大的消耗。所以在这里我们要判断一个重入锁,就是当ListView的item短时间内不断滚出滚进的时候,我们所进行的锁的判断。
<12>上一步我们根据url获取了一个重入锁对象,接着执行如下代码
@Override
public void run() {
...
loadFromUriLock.lock();
...
}
即我们会让重入锁对象在这里进行等待。等到图片加载完成后我们这个锁就会被释放。而刚刚那些相同url的请求,就会执行到接下来的代码中。
<13>接着执行如下代码
@Override
public void run() {
...
try {
...
bmp = configuration.memoryCache.get(memoryCacheKey);
...
} catch (TaskCancelledException e) {
...
} finally {
...
}
...
}
可以看到,它会首先从内存中获取Bitmap。
<14>接着会判断这个bmp是否为空,即从内存中有没有获取到图片
@Override
public void run() {
...
try {
...
if (bmp == null || bmp.isRecycled()) {
...
} else {
...
}
...
} catch (TaskCancelledException e) {
...
} finally {
...
}
...
}
如果内存中没有我们要的图片,我们执行如下代码
@Override
public void run() {
...
try {
...
if (bmp == null || bmp.isRecycled()) {
bmp = tryLoadBitmap();
...
} else {
...
}
...
} catch (TaskCancelledException e) {
...
} finally {
...
}
...
}
<15>我们进入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()) {
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;
}
首先它执行了如下语句
private Bitmap tryLoadBitmap() throws TaskCancelledException {
...
try {
File imageFile = configuration.diskCache.get(uri);
...
} catch (IllegalStateException e) {
...
} catch (TaskCancelledException e) {
...
} catch (IOException e) {
...
} catch (OutOfMemoryError e) {
...
} catch (Throwable e) {
...
}
...
}
这句代码意思是我们先从disk中尝试获取该文件。
接着执行如下语句
private Bitmap tryLoadBitmap() throws TaskCancelledException {
...
try {
...
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
...
}
...
} catch (IllegalStateException e) {
...
} catch (TaskCancelledException e) {
...
} catch (IOException e) {
...
} catch (OutOfMemoryError e) {
...
} catch (Throwable e) {
...
}
...
}
这里可以看到,我们对刚刚尝试获取的imageFile对象进行了判断,如果进入该判断,则说明我们成功从disk中获取到了图片。
如果我们从磁盘中获取到了图片,则在if判断中执行了如下代码
private Bitmap tryLoadBitmap() throws TaskCancelledException {
...
try {
...
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
...
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
...
} catch (IllegalStateException e) {
...
} catch (TaskCancelledException e) {
...
} catch (IOException e) {
...
} catch (OutOfMemoryError e) {
...
} catch (Throwable e) {
...
}
...
}
在执行完这个if语句后,我们又执行一个判断
private Bitmap tryLoadBitmap() throws TaskCancelledException {
...
try {
...
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
...
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
...
}
} catch (IllegalStateException e) {
...
} catch (TaskCancelledException e) {
...
} catch (IOException e) {
...
} catch (OutOfMemoryError e) {
...
} catch (Throwable e) {
...
}
...
}
如果进入这个判断,说明我们从磁盘中没有获取到相应的图片,只能从网络中去获取图片。
我们可以看一下它是如何做的。在这个if判断中有如下方法
private Bitmap tryLoadBitmap() throws TaskCancelledException {
...
try {
...
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
...
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
...
}
...
}
} catch (IllegalStateException e) {
...
} catch (TaskCancelledException e) {
...
} catch (IOException e) {
...
} catch (OutOfMemoryError e) {
...
} catch (Throwable e) {
...
}
...
}
可以看到我们调用了一个tryCacheImageOnDisk()方法,该方法源码如下
private boolean tryCacheImageOnDisk() throws TaskCancelledException {
...
try {
loaded = downloadImage();
if (loaded) {
...
if (width > 0 || height > 0) {
...
resizeAndSaveImage(width, height);
}
}
} catch (IOException e) {
...
}
...
}
在该方法中又调用了downloadImage方法,之后调用了resizeAndSaveImage方法把图片保存下来。resizeAndSaveImage方法就是将图片分别保存在了磁盘和内存当中。
<16>我们回到LoadAndDisplayImageTask的run方法中
@Override
public void run() {
...
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
这是最后两行代码,如果图片已经保存到缓存中,此时就要进行Bitmap的处理。我们进入DisplayBitmapTask的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);
}
}
这里判断了我们的ImageView是否被回收,是否被重用,如果有此类情况,就调用listener的cancel接口取消,如果没有,就调用displayer.display(bitmap, imageAware, loadedFrom);
@Override
public void run() {
if (imageAware.isCollected()) {
...
} else if (isViewWasReused()) {
...
} else {
...
displayer.display(bitmap, imageAware, loadedFrom);
...
listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);
}
}
这句代码是把图片显示出来,之后调用listener.onLoadingComplete(imageUri, imageAware.getWrappedView(), bitmap);告诉我们整个图片加载已完成。