一、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进行装饰一下,就可以去重了。
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);
}
到这里,流程就走完了,下面用一张流程图来结束吧!