Universal-Image-Loader(UIL) 源码详解

一、UIL设置及使用:

1. Include library(官方文档)

Manual:

  • Download JAR
  • Put the JAR in the libs subfolder of your Android project

or

Maven dependency:

<dependency>
	<groupId>com.nostra13.universalimageloader</groupId>
	<artifactId>universal-image-loader</artifactId>
	<version>1.9.5</version>
</dependency>

or

Gradle dependency:

compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
2. Android Manifest
<manifest>
	<!-- Include following permission if you load images from Internet -->
	<uses-permission android:name="android.permission.INTERNET" />
	<!-- Include following permission if you want to cache images on SD card -->
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
	...
</manifest>
3. Application or Activity class (before the first usage of ImageLoader)
public class MyActivity extends Activity {
	@Override
	public void onCreate() {
		super.onCreate();

		// Create global configuration and initialize ImageLoader with this config
		ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
			...
			.build();
		ImageLoader.getInstance().init(config);
		...
	}
}
4. 详细参数设置(不要全部拷贝,选择需要的)
File cacheDir = StorageUtils.getCacheDirectory(context);
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
	.memoryCacheExtraOptions(480, 800) //设置内存缓存图片的最大宽高 default=device screen dimensions
	//设置缓存到disk的图片最大宽高和对图片的处理(resizing/compressing),但它会使ImageLoader变慢
	.diskCacheExtraOptions(480, 800, processorForDiskCache) 
	.taskExecutor(...) //设置自定义的从网络获取图片任务的Executor
	.taskExecutorForCachedImages(...) //设置自定义的从disk获取图片任务的Executor
	.threadPoolSize(3) //default 核心线程数(未设置自定义的Executor时有效)
	.threadPriority(Thread.NORM_PRIORITY - 2) // default 线程优先级
	.tasksProcessingOrder(QueueProcessingType.FIFO) // default 线程池中任务的处理顺序
	.denyCacheImageMultipleSizesInMemory() //对同一个url是否允许在内存中存储多个尺寸
	.memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //设置内存存储的方式
	.memoryCacheSize(2 * 1024 * 1024) //设置内存存储的大小
	.memoryCacheSizePercentage(13) // default 设置内存存储大小的百分比值
	.diskCache(new UnlimitedDiskCache(cacheDir)) // default设置disk存储的方式
	.diskCacheSize(50 * 1024 * 1024) //设置disk存储的大小
	.diskCacheFileCount(100) //设置disk存储的文件数量
	.diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default 设置文件名生成器
	.imageDownloader(new BaseImageDownloader(context)) // default 设置下载器
	.imageDecoder(new BaseImageDecoder()) // default 设置图片解码器
	.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) //default图片显示方式,可自定义
	.writeDebugLogs() // 输出日志
	.build();

.defaultDisplayImageOptions(DisplayImageOptions.createSimple()) 创建了默认的,下面看下自定义的:

DisplayImageOptions options = new DisplayImageOptions.Builder()
	.showImageOnLoading(R.drawable.ic_stub) // resource or drawable 当图片正在加载的时候显示的图片
	.showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable 当图片URI为空的时候显示的图片
	.showImageOnFail(R.drawable.ic_error) // resource or drawable 当图片加载失败的时候显示的图片  
	.resetViewBeforeLoading(false)  // default 加载前ImageAware是否设为null
	.delayBeforeLoading(1000) // 延迟加载的时间
	.cacheInMemory(false) // default 是否内存缓存
	.cacheOnDisk(false) // default 是否缓存到disk
	.preProcessor(...) // 内存缓存之前的预处理,不缓存也会处理
	.postProcessor(...) // 显示之前的再处理,在内存缓存之后
	.extraForDownloader(...) //设置额外的内容给ImageDownloader  
	.considerExifParams(false) // default 是否考虑JPEG图像EXIF参数(旋转,翻转)  
	// 当源图片大小和显示大小不一致时,设置decodingOptions.inSampleSize的计算方式。
	// 详细请参考源码BaseImageDecoder#prepareDecodingOptions()
	.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default
	.bitmapConfig(Bitmap.Config.ARGB_8888) // default 图片的解码类型
	.decodingOptions(...) //设置图片的解码配置  
	.displayer(new SimpleBitmapDisplayer()) // default 设置图片的显示方式
	.handler(new Handler()) // default
	.build();

Acceptable URIs examples

"http://site.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)

二、源码分析

(一)、重要模块分析


以上是UIL的全貌,这里我们主要分析红框里相对复杂的模块


1. DiskCache分析


1.1 DishCache:disk缓存的接口定义类,定义了基本的方法(见图)

1.2 BaskDiskCache:这是一个抽象基类,实现了最基本的把inputstream和bitmap存储到disk上

(1). save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener)

用FileNameGenerator来对imageUri生成一个唯一的文件名,把imageStream拷贝到文件里,可用listener来监听拷贝的进度和终止拷贝(当进度大于75%时无法终止)

(2). boolean save(String imageUri, Bitmap bitmap)

用FileNameGenerator来对imageUri生成一个唯一的文件名,然后把bitmap压缩成png格式的图片存储到disk

(3). boolean remove(String imageUri)

根据imageUril来删除指定的图片

1.3 LimitedAgeDiskCache:根据存储时间来删除文件的缓存模式,在获取文件的时候,会检测获取的文件存储的时间是否大于maxFileAge,如果超过了就会删除

(1). public File get(String imageUri):关键函数

@Override  
public File get(String imageUri) {  
    File file = super.get(imageUri); //调用基类生成文件,用的是文件生成器  
    if (file != null && file.exists()) {  
        boolean cached;  
        Long loadingDate = loadingDates.get(file); //获取存入的时间  
        if (loadingDate == null) {  
            cached = false;  
            loadingDate = file.lastModified();  
        } else {  
            cached = true;  
        }  
        //如果大于传入的maxFileAge则从disk删除  
        if (System.currentTimeMillis() - loadingDate > maxFileAge) {  
            file.delete();  
            loadingDates.remove(file);  
        } else if (!cached) { //文件存在,但之前没有记录则记录之  
            loadingDates.put(file, loadingDate);  
        }  
    }  
    return file;  
} 

(2). boolean save(...):首先调用基类的save函数,然后把修改时间记录好

1.4 UnlimitedDiskCache extends BaseDiskCache:这个就是BaseDiskCache的默认实现,因为BaseDiskCache本来就是个无限制的存储模式,所以UnlimitedDiskCache里面什么都没做


2. MemoryCache分析

1.1 MemoryCache:这是接口定义类,定义了基本的方法,具体见类图

1.2 LruMemoryCache:这是Least recently used(最近最少使用)的存储模式,就是优先把最近最少使用的删除掉,这里利用了LinkedHashMap的特性来实现的。

LinkedHashMap#get(key):直接根据key返回value,然后把这条数据移动到头部

LinkedHashMap#put(key,value):把key和value插入到头部

插入的时候维护一个size来记录已有的bitmap的大小,当size>maxSize的时候会调用trimToSize去移除最少使用的数据(尾部的数据),直到size<maxSize

1.3 BaseMemoryCache:纯虚类,主要实现了用Map<String, Reference<Bitmap>>对bitmap的软引用存储

1.4 WeakMemoryCache:对BaseMemoryCache的软引用实现类,可以实例化

1.5 LimitedMemoryCache:纯虚类,对bitmap进行强引用存储,新添加的放入list尾部,当超过sizeLimit限制时,会从list头部开始删除,但是基类的弱引用不会删除

(1). put(String key, Bitmap value):关键函数源码

@Override
public boolean put(String key, Bitmap value) {
	boolean putSuccessfully = false;
	// Try to add value to hard cache
	int valueSize = getSize(value); //计算bitmap大小
	int sizeLimit = getSizeLimit(); //获取总的大小限制
	int curCacheSize = cacheSize.get(); //获取当前存储的总大小
	if (valueSize < sizeLimit) {
		while (curCacheSize + valueSize > sizeLimit) {
		    //这里的removeNext是虚函数,留给子类去实现(策略模式)
			Bitmap removedValue = removeNext(); 
			if (hardCache.remove(removedValue)) { //移除并更新存储大小
				curCacheSize = cacheSize.addAndGet(-getSize(removedValue));
			}
		}
		hardCache.add(value); //记录当前bitmap并更新存储大小
		cacheSize.addAndGet(valueSize);

		putSuccessfully = true;
	}
	// Add value to soft cache
	super.put(key, value); //调用基类添加软引用
	return putSuccessfully;
}

(2). abstract Bitmap removeNext():移除下一个bitmap,具体怎么移除留给子类实现(策略模式)

下面我们来看看它的4个子类是怎么实现removeNext的

1.5.1 FIFOLimitedMemoryCache:缓存模式=大小限制+先进先出队列限制。看代码:

@Override
public boolean put(String key, Bitmap value) {
	if (super.put(key, value)) { //调用基类来限制大小
		queue.add(value); //直接用队列的特性
		return true;
	} else {
		return false;
	}
}
@Override
protected Bitmap removeNext() {
	return queue.remove(0);
}

1.5.2 LRULimitedMemoryCache:缓存模式=大小限制+LRU(最近最少使用)

@Override
public boolean put(String key, Bitmap value) {
	if (super.put(key, value)) {
		lruCache.put(key, value); //直接利用LinkedHashMap来保证LRU
		return true;
	} else {
		return false;
	}
}
@Override
protected Bitmap removeNext() {
	Bitmap mostLongUsedValue = null;
	synchronized (lruCache) {
		Iterator<Entry<String, Bitmap>> it = lruCache.entrySet().iterator();
		if (it.hasNext()) {
			Entry<String, Bitmap> entry = it.next();
			mostLongUsedValue = entry.getValue();
			it.remove(); //删除最少使用的
		}
	}
	return mostLongUsedValue;
}
1.5.3 LargestLimitedMemoryCache:缓存模式=大小限制+首先移除最大
@Override
protected Bitmap removeNext() {
	Integer maxSize = null;
	Bitmap largestValue = null;
	Set<Entry<Bitmap, Integer>> entries = valueSizes.entrySet();
	synchronized (valueSizes) {
		for (Entry<Bitmap, Integer> entry : entries) {
			if (largestValue == null) {
				largestValue = entry.getKey();
				maxSize = entry.getValue();
			} else {
				Integer size = entry.getValue();
				if (size > maxSize) { //选择size最大的那个
					maxSize = size;
					largestValue = entry.getKey();
				}
			}
		}
	}
	valueSizes.remove(largestValue); //移除最大的
	return largestValue;
}
1.5.4 UsingFreqLimitedMemoryCache:缓存模式=大小限制+首先移除使用次数最少的
@Override
public Bitmap get(String key) {
	Bitmap value = super.get(key);
	// Increment usage count for value if value is contained in hardCahe
	if (value != null) {
		Integer usageCount = usingCounts.get(value);
		if (usageCount != null) {
			usingCounts.put(value, usageCount + 1); //每使用一次就记录+1
		}
	}
	return value;
}
@Override
protected Bitmap removeNext() {
	Integer minUsageCount = null;
	Bitmap leastUsedValue = null;
	Set<Entry<Bitmap, Integer>> entries = usingCounts.entrySet();
	synchronized (usingCounts) {
		for (Entry<Bitmap, Integer> entry : entries) {
			if (leastUsedValue == null) {
				leastUsedValue = entry.getKey();
				minUsageCount = entry.getValue();
			} else {
				Integer lastValueUsage = entry.getValue();
				if (lastValueUsage < minUsageCount) { //寻找使用次数最少的
					minUsageCount = lastValueUsage;
					leastUsedValue = entry.getKey();
				}
			}
		}
	}
	usingCounts.remove(leastUsedValue); //移除
	return leastUsedValue;
}
1.6 FuzzyKeyMemoryCache:这个类的作用是保证一个key存储一次,这里采用了装饰模式,首先看下它的构造函数:
public FuzzyKeyMemoryCache(MemoryCache cache, Comparator<String> keyComparator) {
	this.cache = cache;
	this.keyComparator = keyComparator;
}

这里它接收了一个MemoryCache来对它进行装饰,具体体现在put方法:

@Override
public boolean put(String key, Bitmap value) {
	// Search equal key and remove this entry
	synchronized (cache) {
		String keyToRemove = null;
		for (String cacheKey : cache.keys()) { //遍历寻找相同的key
			if (keyComparator.compare(key, cacheKey) == 0) { //这里的比较方式是传入的
				keyToRemove = cacheKey;
				break;
			}
		}
		if (keyToRemove != null) {
			cache.remove(keyToRemove); //移除,保证同一个key只存储一次
		}
	}
	return cache.put(key, value);
}

这个装饰模式有什么用呢?

简单点说就是在当前传入的MemoryCache基础上做进一步的限制。

比如对于FIFOLimitedMemoryCache来说,如果我前后存入两个相同key值的大图片,显然浪费了存储空间,这时候如果调用FuzzyKeyMemoryCache进行装饰一下,就可以去重了。

1.7 LimitedAgeMemoryCache:基于时间限制的装饰模式,实现方式可参考LimitedAgeDiskCache


3. display显示模块分析

3.1 BitmapDisplayer:接口类,定义了一个显示的函数见类图。有4个实现类

3.2 SimpleBitmapDisplayer:没有多余功能,直接设置bitmap显示

3.3 FadeInBitmapDisplayer:使用动画来渐显bitmap

public static void animate(View imageView, int durationMillis) {
	if (imageView != null) {
		AlphaAnimation fadeImage = new AlphaAnimation(0, 1); //逐渐显示出来
		fadeImage.setDuration(durationMillis);
		fadeImage.setInterpolator(new DecelerateInterpolator()); //显示的速度越来越慢
		imageView.startAnimation(fadeImage);
	}
}

3.4 CircleBitmapDisplayer:对原始图片进行圆形裁剪显示,还可以在圆形图片外面画一个圆圈,圆圈的颜色和线条大小可在构造函数传入。

3.4 RoundedBitmapDisplayer:圆角图片,在构造函数中还可传入圆角半径,margin等,来看看源码:

public static class RoundedDrawable extends Drawable {

	public RoundedDrawable(Bitmap bitmap, int cornerRadius, int margin) {
		this.cornerRadius = cornerRadius; //圆角大小
		this.margin = margin; //边距,这里会占据bitmap的图像
        //BitmapShader:Bitmap着色器。其实也就是用 Bitmap 的像素来作为图形或文字的填充
		bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
		mBitmapRect = new RectF (margin, margin, bitmap.getWidth() - margin, bitmap.getHeight() - margin);
		
		paint = new Paint();
		paint.setAntiAlias(true);      //设置抗锯齿开关
		paint.setShader(bitmapShader); //设置着色器
		paint.setFilterBitmap(true);   //设置是否使用双线性过滤来绘制 Bitmap
		paint.setDither(true);         //设置图像的抖动
	}

	@Override
	protected void onBoundsChange(Rect bounds) {
		super.onBoundsChange(bounds);
		mRect.set(margin, margin, bounds.width() - margin, bounds.height() - margin);
		
		// Resize the original bitmap to fit the new bound
		Matrix shaderMatrix = new Matrix();
		//当边界变化时,调整bitmap的显示区域
		shaderMatrix.setRectToRect(mBitmapRect, mRect, Matrix.ScaleToFit.FILL);
		bitmapShader.setLocalMatrix(shaderMatrix);
		
	}

	@Override
	public void draw(Canvas canvas) {
		canvas.drawRoundRect(mRect, cornerRadius, cornerRadius, paint); //画圆角矩形
	}
}

再来看个效果图:


3.5 RoundedVignetteBitmapDisplayer:除了圆角图片,还会留下装饰图案的效果。对比一下上图:


仔细看会发现下图的四个角有一个灰色的蒙层,但是不太明显,那么我来一个明显的:

这个够明显了,那么我们还是来看下源码,看看究竟是怎么实现的:

protected static class RoundedVignetteDrawable extends RoundedDrawable {

	RoundedVignetteDrawable(Bitmap bitmap, int cornerRadius, int margin) {
		super(bitmap, cornerRadius, margin); //圆角半径和边距
	}

	@Override
	protected void onBoundsChange(Rect bounds) {
		super.onBoundsChange(bounds);
		//RadialGradient:径向渐变
		RadialGradient vignette = new RadialGradient(
				mRect.centerX(), mRect.centerY() * 1.0f / 0.7f, mRect.centerX() * 1f,
				new int[]{0, 0x7f00ff00, 0xffff0000}, new float[]{0.0f, 0.5f, 1.0f},
				Shader.TileMode.CLAMP);

		Matrix oval = new Matrix();
		oval.setScale(1.0f, 0.7f);   //把y方向缩小
		vignette.setLocalMatrix(oval);
        //设置着色器,ComposeShader:混合着色器,所谓混合,就是把两个 Shader 一起使用。
		paint.setShader(new ComposeShader(bitmapShader, vignette, PorterDuff.Mode.SRC_OVER));
	}
}

public RadialGradient(float centerX, float centerY, float radius, int colors[], float stops[], TileMode tileMode)

RadialGradient:径向渐变。前面三个字段比较好理解,我们来看下colors[]和stops[]数组。这两个数组的大小必须相同,大小根据需要定,不一定是这里的3,stop中的值(描述的是径向方向的百分比,所以必须是0~1范围)是和color一一对应的。从上面的图你应该就能猜出分别表示:0~50%半径 的径向渐变由 0~0x7f00ff00.


4. decode解码模块分析

4.1 interface ImageDecoder:基础接口类

(1) Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException

只定义了一个解码接口,传入的ImageDecodingInfo是一个包含解码所需要的数据结构类,字段如下:

imageKey:uri+大小组成的key,eg:http://h.hiphotos.baidu.com/2cf5e.jpg_720x400
imageUri Scheme包装过的基于Uri生成的disk文件路径,eg:file:///data/user/0/com.example.test/cache/-87789357
originalImageUri 调用接口是传入的原始uri
targetSize 图片的目标显示尺寸
imageScaleType 图片解码时采样使用的类型
viewScaleType 图片在ImageView内显示的类型(FIT_INSIDE/CROP)
downloader 图片的下载器
extraForDownloader 下载器需要的辅助信息
considerExifParams 是否需要考虑图片 Exif 信息

decodingOptions 图片的解码信息,为 BitmapFactory.Options

4.2 BaseImageDecoder implements ImageDecoder

解码器实现类。源码分析放到后面流程分析里。


5. download图片下载模块

5.1 interface ImageDownloader:下载接口定义类,里面还定义了一个枚举类,枚举UIL所支持的协议类型

public enum Scheme {
    HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
}

(1) InputStream getStream(String imageUri, Object extra):获取图片的接口

5.2 BaseImageDownloader implements ImageDownloader

图片下载实现类。对上面Scheme中的各种资源分别加载图片。


6. imageaware图片显示模块

6.1 interface ImageAware:图片接口定义类,定义了获取各种图片属性的方法

6.2 abstract class ViewAware implements ImageAware

它是对View的包装类(装饰模式),定义了对View的弱引用来避免内存泄漏

(1) int getWidth():首先调用view.getWidth(),如果为0再继续调用LayoutParams的width

(2) int getHeight():首先调用view.getHeight(),如果为0再继续调用LayoutParams的height

(3) boolean setImageDrawable(Drawable drawable):在主线程调用虚方法设置图片显示

(4) boolean setImageBitmap(Bitmap bitmap):在主线程调用虚方法设置图片显示

(5) abstract void setImageDrawableInto(Drawable drawable, View view)

(6) abstract void setImageBitmapInto(Bitmap bitmap, View view)

6.3 class ImageViewAware extends ViewAware

主要是针对ImageView进行处理,实现具体的设置图片的方法

6.4 class NonViewAware implements ImageAware

只做处理图片的数据存储,与显示无关


7 listener下载及结果监听模块

7.1 interface ImageLoadingListener:接口定义类,定义的函数如下:

void onLoadingStarted(String imageUri, View view)

void onLoadingFailed(String imageUri, View view, FailReason failReason)

void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)

void onLoadingCancelled(String imageUri, View view);

7.2 class SimpleImageLoadingListener implements ImageLoadingListener

空实现类,作用就是你不用重载上面的4个方法了

7.3 interface ImageLoadingProgressListener:下载进度监听

void onProgressUpdate(String imageUri, View view, int current, int total)

7.4 PauseOnScrollListener implements OnScrollListener

这个主要是用于对ListView,GridView的滑动监听,当滑动的时候会暂停图片的下载线程

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
	switch (scrollState) {
		case OnScrollListener.SCROLL_STATE_IDLE:
			imageLoader.resume();  //空闲时恢复
			break;
		case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
			if (pauseOnScroll) {
				imageLoader.pause(); //滑动时暂停
			}
			break;
		case OnScrollListener.SCROLL_STATE_FLING:
			if (pauseOnFling) {
				imageLoader.pause(); //急速滑动时暂停
			}
			break;
	}
	if (externalListener != null) {
		externalListener.onScrollStateChanged(view, scrollState);
	}
}

(二)、图片下载的主流程分析

首先来看一张来自官网的图片,它描述了不同缓存状态下的下载步骤:

在介绍主流程之前,有必要先对核心的几个类进行介绍:

1. ImageLoader:程序的调用入口类,在调用下载图片之前,必须先调用ImageLoader#init()函数来初始化配置。

public static ImageLoader getInstance() {
	if (instance == null) {
		synchronized (ImageLoader.class) {
			if (instance == null) {
				instance = new ImageLoader();
			}
		}
	}
	return instance;
}

protected ImageLoader() {
}

这里用了双重检验标准的单例模式。ImageLoader注意用于调用下载图片和取消下载,来看下主要的几个函数:

1.1 public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

核心函数:它根据传入的uri进行图片下载,下载完成根据options和targetSize进行处理后直接显示到imageAware,期间你可以用progressListener跟踪下载进度,listener监听下载结果。源码后面再介绍。

1.2 public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener progressListener)

这个函数和displayImage最大的区别就是少了一个imageAware参数,也就是说需要你用listener监听返回的bitmap,然后自己显示。源码如下:

public void loadImage(String uri, ImageSize targetImageSize, DisplayImageOptions options,
		ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
	checkConfiguration(); //检查ImageLoaderConfiguration是否已经设置,未设置则抛出错误
	if (targetImageSize == null) {
	    //设置一个图片的最大尺寸,如果使用者调用了memoryCacheExtraOptions()进行设置就采用设置的
	    //否则会使用手机屏幕大小作为默认设置
	    targetImageSize = configuration.getMaxImageSize();
	}
	if (options == null) { 
	    //图片的显示参数,在每次调用加载图片时,都可以根据需求传入不同的
	    options = configuration.defaultDisplayImageOptions;
	}
    //NonViewAware:只做存储数据用,和显示无关
	NonViewAware imageAware = new NonViewAware(uri, targetImageSize, ViewScaleType.CROP);
	displayImage(uri, imageAware, options, listener, progressListener); //调用已有的下载函数
}

1.3 public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) 

看这名字就知道,是一个同步图片下载请求,源码如下:

public Bitmap loadImageSync(String uri, ImageSize targetImageSize, DisplayImageOptions options) {
	if (options == null) {//图片的显示参数,在每次调用加载图片时,都可以根据需求传入不同的
		options = configuration.defaultDisplayImageOptions;
	}
	options = new DisplayImageOptions.Builder().cloneFrom(options).syncLoading(true).build();//设置为同步

    //同步监听器,当图片下载完成时会存储起来给返回时使用
	SyncImageLoadingListener listener = new SyncImageLoadingListener();
	loadImage(uri, targetImageSize, options, listener); //调用已有功能函数,避免重复代码
	return listener.getLoadedBitmap();
}

1.4 public void cancelDisplayTask(ImageView imageView)

取消下载和显示图片

2. ImageLoaderEngine:引擎类,主要负责任务的分发工作。来看一下它里面的3个线程池:

private Executor taskDistributor;

任务分发线程池。它其实是一个Executors.newCachedThreadPool,这个线程池比较适合 耗时时间比较短的多任务处理

new ThreadPoolExecutor(0, Integer.MAX_VALUE, //核心线程数为0(节省资源),非核心线程数无限
	60L, TimeUnit.SECONDS,  // 空闲60秒会被回收
	new SynchronousQueue<Runnable>(), //同步队列表示有任务时立马执行,优先利用空闲线程,没有就创建
	threadFactory);

private Executor taskExecutor:是一个自定义线程池,创建方法如下:

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-")); // threadPoolSize默认是3,也可以自己定义
}

private Executor taskExecutorForCachedImages(适合非耗时任务)

这个线程池的创建方法和taskExecutor一样,那么这里为什么还要创建一个相同的线程池呢?

用途不一样。taskExecutor主要用于需要网络请求的线程,而这个主要是读取disk上面已经缓存好的图片,很明显,不请求网络的情况下,解析一张图片还是比较快的,如果混合使用,显示disk图片的线程会被网络加载(网络不好的情况下)的线程阻挡很长时间,导致缓存在disk上的图片加载速度慢。

3. DisplayBitmapTask implements Runnable

主要用于图片的显示,虽然实现了Runnable接口,但是它不是运行在子线程而是主线程。

4. LoadAndDisplayImageTask implements Runnable, IoUtils.CopyListener

主要用于加载网络图片和disk图片

5. ProcessAndDisplayImageTask implements Runnable

主要是对图片的一个自定义处理,基本属于非耗时任务,所以会调用taskExecutorForCachedImages来执行

6. DefaultConfigurationFactory

在设置ImageLoaderConfiguration时,当某些重要参数没有传入的情况下,DefaultConfigurationFactory会构建默认的。比如线程池、diskCache策略、memoryCache策略、downloader、decoder等等


核心的类就介绍到这里了,下面我们进入主流程的源码分析:

ImageLoader#displayImage

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
		ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
	checkConfiguration(); //如果未配置ImageLoaderConfiguration直接抛异常
	if (imageAware == null) {
		throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
	}
	if (listener == null) {
		listener = defaultListener;  //默认SimpleImageLoadingListener
	}
	if (options == null) {
		options = configuration.defaultDisplayImageOptions;
	}

	if (TextUtils.isEmpty(uri)) {
		engine.cancelDisplayTaskFor(imageAware); //取消下载和显示图片的任务
		listener.onLoadingStarted(uri, imageAware.getWrappedView()); //回调函数
		if (options.shouldShowImageForEmptyUri()) { //显示空uri所对应设置的图片
			imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
		} else {
			imageAware.setImageDrawable(null);
		}
		listener.onLoadingComplete(uri, imageAware.getWrappedView(), null); //回调函数
		return;
	}

	if (targetSize == null) { 
	    //获取目标size,如果宽高为0,则使用已设置的最大值,如果未设置则使用屏幕宽高
		targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
	}
	String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize); //在uri后面拼接图片宽x高
	//以imageAware中的id为key值put memoryCacheKey进缓存
	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(); //同步的话,直接调用run函数
			} else {
				engine.submit(displayTask); //异步则提交到线程池taskExecutorForCachedImages(非耗时任务池)
			}
		} 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(); //同步的话,直接调用run函数
		} else {
			engine.submit(displayTask); //异步则提交到任务分发线程池 taskDistributor
		}
	}
}
46行提交ProcessAndDisplayImageTask后就会进入它的run方法。

ProcessAndDisplayImageTask#run

@Override
public void run() {
	L.d(LOG_POSTPROCESS_IMAGE, imageLoadingInfo.memoryCacheKey);

    //获取用户设置好的处理器
	BitmapProcessor processor = imageLoadingInfo.options.getPostProcessor(); 
	Bitmap processedBitmap = processor.process(bitmap); //直接调用用户定义的处理方法
	//封装显示的task,然后调用LoadAndDisplayImageTask的静态函数runTask
	DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(processedBitmap, imageLoadingInfo, engine, LoadedFrom.MEMORY_CACHE);
	LoadAndDisplayImageTask.runTask(displayBitmapTask, imageLoadingInfo.options.isSyncLoading(), handler, engine);
}

//LoadAndDisplayImageTask的公共ranTask
static void runTask(Runnable r, boolean sync, Handler handler, ImageLoaderEngine engine) {
	if (sync) {
		r.run(); //同步则直接调用run
	} else if (handler == null) {
		engine.fireCallback(r); //handler为空则直接加入taskDistributor线程池
	} else {
		handler.post(r); //交给handler去处理(具体在什么线程执行由handler创建的线程决定)
	}
}

66行把任务交给了taskDistributor,那么我们来看下它是怎么分发任务的。

ImageLoaderEngine#submit

void submit(final LoadAndDisplayImageTask task) {
	taskDistributor.execute(new Runnable() {
		@Override
		public void run() {
		    //首先获取disk上的文件索引,判断是否已经缓存
			File image = configuration.diskCache.get(task.getLoadingUri());
			boolean isImageCachedOnDisk = image != null && image.exists();
			//检查taskExecutor和taskExecutorForCachedImages是否可用,不可用则创建
			initExecutorsIfNeed(); 
			if (isImageCachedOnDisk) { //存在则调用非耗时线程池处理
				taskExecutorForCachedImages.execute(task);
			} else {
				taskExecutor.execute(task);
			}
		}
	});
}

不过调用的哪个线程池的execute,都会进入到LoadAndDisplayImageTask的run方法,这个方法很重要。翠花~~上源码!

LoadAndDisplayImageTask#run

@Override
public void run() {
    //如果被暂停了则调用wait等待,比如ListView滑动过程中会暂停加载,resume的时候会调用notifyAll
	if (waitIfPaused()) return;  
	if (delayIfNeed()) return;   //如果用户设置的延迟执行则Thread.sleep等待

	ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
	L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
	if (loadFromUriLock.isLocked()) {
		L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
	}
    //如果没有被其它线程获取,获取锁成功后计数器为1,立即返回
    //如果自己已经获取过了,则计数器+1,立即返回
    //如果已经被其它线程获取,则当前线程会被禁用并处于休眠状态,直到重新获取
	loadFromUriLock.lock();
	Bitmap bmp;
	try {
		checkTaskNotActual(); //判断ImageAware是否被回收和重用

		bmp = configuration.memoryCache.get(memoryCacheKey); //再次判断内存是否存在
		if (bmp == null || bmp.isRecycled()) {
			bmp = tryLoadBitmap(); //加载图片,马上进入分析
			if (bmp == null) return; // tryLoadBitmap已经处理过了,这里就直接返回了

			checkTaskNotActual();  //判断ImageAware是否被回收和重用
			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();  //判断ImageAware是否被回收和重用
		checkTaskInterrupted(); //判断当前线程是否被中断
	} catch (TaskCancelledException e) {
		fireCancelEvent(); //调用listener.onLoadingCancelled
		return;
	} finally {
		loadFromUriLock.unlock(); //解锁
	}
    //获取图片成功,调用显示task直接显示
	DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
	runTask(displayBitmapTask, syncLoading, handler, engine);
}

LoadAndDisplayImageTask#tryLoadBitmap

private Bitmap tryLoadBitmap() throws TaskCancelledException {
	Bitmap bitmap = null;
	try {
		File imageFile = configuration.diskCache.get(uri); //获取disk索引文件
		if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
			L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
			loadedFrom = LoadedFrom.DISC_CACHE; //标记从disk获取的

			checkTaskNotActual(); //判断ImageAware是否被回收和重用
			//解析disk缓存的图片文件
			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;
			//如果允许disk缓存则调用tryCacheImageOnDisk获取图片并缓存到disk
			if (options.isCacheOnDisk() && tryCacheImageOnDisk()) { 
				imageFile = configuration.diskCache.get(uri); //获取刚刚缓存到disk的文件
				if (imageFile != null) { //用Scheme包装成decodeImage能识别的形式
					imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
				}
			}

			checkTaskNotActual();
			bitmap = decodeImage(imageUriForDecoding);//这个uri可能是网络或者disk的

			if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
				fireFailEvent(FailType.DECODING_ERROR, null); //调用listener.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;
}

第11行和第28行都在调用decodeImage,我们来瞅瞅它:

LoadAndDisplayImageTask#decodeImage

private Bitmap decodeImage(String imageUri) throws IOException {
	ViewScaleType viewScaleType = imageAware.getScaleType(); //获取View显示的模式
	ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
			getDownloader(), options);
	return decoder.decode(decodingInfo); //进入BaseImageDecoder的decode函数
}

//BaseImageDecoder#decode
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
	Bitmap decodedBitmap;
	ImageFileInfo imageInfo;

	InputStream imageStream = getImageStream(decodingInfo); //见下面
	if (imageStream == null) {
		L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
		return null;
	}
	try {
		imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo); //确定图片的大小和旋转角度
		//重置imageStream,如果失败会调用getImageStream重新获取
		imageStream = resetStream(imageStream, decodingInfo); 
		//根据ImageScaleType来计算图片的decodingOptions.inSampleSize的值
		//换句话说就是根据imageSize和targetSize来确定图片应该放大还是缩小,计算出scale值
		Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
		//根据计算的decodingOptions来解析
		decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
	} finally {
		IoUtils.closeSilently(imageStream);
	}

	if (decodedBitmap == null) {
		L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
	} else {
	    //prepareDecodingOptions计算的是int类型的粗略的scale值,而这里是计算出精确的float类型的scale值
	    //把decodedBitmap伸缩变换成targetSize大小的bitmap
		decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal);
	}
	return decodedBitmap;
}

//BaseImageDecoder#getImageStream 最终调用了downloader的getStream函数
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
	return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
上面停留在了downloader的getStream函数调用。再来看看tryLoadBitmap中第20行的tryCacheImageOnDisk函数是如何获取图片并缓存到disk的

LoadAndDisplayImageTask#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);
				//获取disk图片,resize成指定的max宽高后重新save进disk
				resizeAndSaveImage(width, height); // TODO : process boolean result
			}
		}
	} catch (IOException e) {
		L.e(e);
		loaded = false;
	}
	return loaded;
}

private boolean downloadImage() throws IOException {
    //也调用了downloader的getStream函数
	InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());
	if (is == null) {
		L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);
		return false;
	} else {
		try { //进入BaseDiskCache的save函数
			return configuration.diskCache.save(uri, is, this); 
		} finally {
			IoUtils.closeSilently(is);
		}
	}
}

//BaseDiskCache#save
@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
	File imageFile = getFile(imageUri); //根据文件名生成器生成文件
	File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX);
	boolean loaded = false;
	try {
		OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize);
		try { 
		    //以每次bufferSize的大小将imageStream写入disk,用listener监听写入进度或者终止写入
			loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize);
		} finally {
			IoUtils.closeSilently(os);
		}
	} finally {
		if (loaded && !tmpFile.renameTo(imageFile)) { //把临时文件名改为缓存的文件名
			loaded = false;
		}
		if (!loaded) {
			tmpFile.delete();
		}
	}
	return loaded;
}

以上两个分支最终到走到了BaseImageDownloader的getStream函数,我们来看下它是如何针对不同资源图片进行加载的:

@Override
public InputStream getStream(String imageUri, Object extra) throws IOException {
	switch (Scheme.ofUri(imageUri)) { //分别调用对应函数加载
		case HTTP:
		case HTTPS:
			return getStreamFromNetwork(imageUri, extra); //网络
		case FILE:
			return getStreamFromFile(imageUri, extra); //读取文件
		case CONTENT:
			return getStreamFromContent(imageUri, extra); //Content
		case ASSETS:
			return getStreamFromAssets(imageUri, extra); //Asserts目录文件
		case DRAWABLE:
			return getStreamFromDrawable(imageUri, extra); //Drawable资源
		case UNKNOWN:
		default:
			return getStreamFromOtherSource(imageUri, extra);
	}
}

protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
	HttpURLConnection conn = createConnection(imageUri, extra); // 创建HttpURLConnection连接

	int redirectCount = 0; //如果返回的code是3开头的就重试5次
	while (conn.getResponseCode() / 100 == 3 && redirectCount < MAX_REDIRECT_COUNT) {
		conn = createConnection(conn.getHeaderField("Location"), extra);
		redirectCount++;
	}

	InputStream imageStream;
	try {
		imageStream = conn.getInputStream();
	} catch (IOException e) {
		// Read all data to allow reuse connection (http://bit.ly/1ad35PY)
		IoUtils.readAndCloseStream(conn.getErrorStream());
		throw e;
	}
	if (!shouldBeProcessed(conn)) {
		IoUtils.closeSilently(imageStream);
		throw new IOException("Image request failed with response code " + conn.getResponseCode());
	}
    //ContentLengthInputStream是InputStream的装饰类,提供了对InputStream的基本操作
	return new ContentLengthInputStream(new BufferedInputStream(imageStream, BUFFER_SIZE), conn.getContentLength());
}

protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
	String filePath = Scheme.FILE.crop(imageUri);
	if (isVideoFileUri(imageUri)) { //如果是视频
		return getVideoThumbnailStream(filePath); //返回视频的缩略图
	} else { //根据文件路径读取文件数据
		BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), BUFFER_SIZE);
		return new ContentLengthInputStream(imageStream, (int) new File(filePath).length());
	}
}

protected InputStream getStreamFromContent(String imageUri, Object extra) throws FileNotFoundException {
	ContentResolver res = context.getContentResolver();

	Uri uri = Uri.parse(imageUri);
	if (isVideoContentUri(uri)) { // video thumbnail
		Long origId = Long.valueOf(uri.getLastPathSegment());
		Bitmap bitmap = MediaStore.Video.Thumbnails
				.getThumbnail(res, origId, MediaStore.Images.Thumbnails.MINI_KIND, null);
		if (bitmap != null) {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			bitmap.compress(CompressFormat.PNG, 0, bos);
			return new ByteArrayInputStream(bos.toByteArray());
		}
	} else if (imageUri.startsWith(CONTENT_CONTACTS_URI_PREFIX)) { // contacts photo
		return getContactPhotoStream(uri);
	}

	return res.openInputStream(uri);
}

protected InputStream getStreamFromAssets(String imageUri, Object extra) throws IOException {
	String filePath = Scheme.ASSETS.crop(imageUri);
	return context.getAssets().open(filePath);
}

protected InputStream getStreamFromDrawable(String imageUri, Object extra) {
	String drawableIdString = Scheme.DRAWABLE.crop(imageUri);
	int drawableId = Integer.parseInt(drawableIdString);
	return context.getResources().openRawResource(drawableId);
}
到这里,流程就走完了,下面用一张流程图来结束吧!







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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值