Universal Image Loader 是个比较老一点的图片加载器,虽然现在比较少用,但是它的一些设计理念还是很好的,值得我们去看看。
一、使用例子
我们先来看看一个简单的使用例子
// ImageLoaderConfiguration 使用了 Build 模式
ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);
// ImageLoader 使用单例模式
ImageLoader.getInstance().init(configuration);
String url = "http://img14.poco.cn/mypoco/myphoto/20130131/22/17323571520130131221457027_640.jpg";
ImageLoader.getInstance().displayImage(url, imageView);
// 加载过程的完整回调
ImageLoader.getInstance().loadImage(url, new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
Log.i(TAG, "onLoadingStarted");
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
Log.i(TAG, "onLoadingFailed");
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
Log.i(TAG, "onLoadingComplete");
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
Log.i(TAG, "onLoadingCancelled");
}
});
所以是对 ImageLoader 进行配置,我们这里使用了默认配置,然后调用了 进行加载就可以了。后面只是显示回调而已,无他。所以,整个 ImageLoader 使用分为三个步骤:
1. 配置 ImageloaderConfiguration;
2. 使用配置初始化 ImageLoader
3. 调用 ImageLoader.loadImage(url, imageview) 方法
二、源码分析
图片来源 codekk
2. 整体的流程
图片来源 codekk
3.总流程
4.主要类
(1). ImageLoader
. 主要类,对外提供接口;
. 主要成员变量
ImageLoaderConfiguration configuration 下载的配置;
ImageLoaderEngine engine 处理下载任务,内部使用线程池;
ImageloadingListener defaultListener 默认的回调监听;
(2). DefaultConfigurationFactory
为 ImaeLoaderConfiguration 提供了一些默认的配置;
(3). DisplayBitmapTask
. 是一个 Runnable , implements Runnable;
. 在主线程被调用,用来显示图片 Bitmap;
. 主要是在 run() 方法中
displayer.display(bitmap, imageAware, loadedFrom); 显示 Bitmap, displayer 默认实现类是 SimpleBitmapDisplayer;
engine.cancelDisplayTaskFor(imageAware) 取消任务
listener.onLoadingComplete(imageUri, imageAware,getWrappedView, bitmap); 回调加载完成
(4). DisplayImageOptions
图片配置的一些选项配置,内部使用 Builder 模式
(5). ImageLoaderConfiguration
对整个 ImageLoader 进行配置,例如内存缓存, MamoryCache, 磁盘缓存 DiskCache, 图片下载 ImageDownLoader 的 配置等。
内部使用了 Builder 模式;
(6). ImageLoaderEngine
用于加载 DisplayTask ,内部使用了线程池;
主要方法是 submit(LoadAndDisplayImageTask task) 用于文件或网络加载图片
和 submit(ProcessAndDisplayImage) 用于缓存加载图片
(7). ProcessAndDisplayImageTask
. 一个 Runable, implement Runnable, 主要是处理 Bitmap 后,从内存中获取 Bitmap 显示图片;
. 在 run() 方法中使用 DisplayBitmapTask 显示图片;
(8). LoadAndDisplayImageTask
是一个 Runnable, implements Runnable, 主要是从文件或者网络中获取图片数据,最终使用 DisplayBitmapTask 显示图 片。
(9). BaseDiskCache
. abstract class BaseDiskCache implements DiskCache 是一个抽象类,实现 DiskCache 接口
. 基本的磁盘缓存类,默认实现类是 UnlimitedDiskCache
. 关系
(10). LruDiskCache
. implements DiskCache
. 使用LRU 算法实现的磁盘缓存
(11). FileNameGenerator
. interface, 文件名生成
. 主要是实现类是
Md5FileNameGenerator 利用 MD5 生成图片 uri
HashCodeFileNameGenerator 利用HashCode 生成图片 uri
(12). BaseMemoryCache
. 基本的内存缓存类,是 abstract 类, implements MemoryCache
. 其他的 MemoryCache 都是以此为基础实现的,实现类需要实现方法 createReference(Bitmap value) 方法
因为要存储 Bitmap ,一般都是利用 WeakReference 实现,防止内存泄露.
(13). BaseImageDecoder
implements ImageDecoder , 根据需要的尺寸对图片进行采样等处理;
(14). BitmapDisplayer
. 接口,用于显示 Bitmap
. 主要实现类有 圆形显示 CircleBitmapDisplayer, 渐进动画显示 FadeInBitmapDisplayer 等, 默认实现类是 SimpleBitmapDisplayer
(15). BaseImageDownloader
. implements ImageDownloader
. 通过 uri 获取图片流,内部可以通过 network, fileSystem 或者 app resource 获取;
. 如果是网络的话,内部是使用 HttpURLConnection
(16). 图片显示
(18). Listener
. ImageLoadingListener, ImageLoadingProcessListener 图片加载过程;
. SimpleImageLoadingListener implements ImageLoadingListener, 提供一个可以监听图片加载的回调,也是默认配置;
. PausOnScrollLitener 使用在 ListView 或者 GridView 中,它 implements OnScrollListener, 防止列表加载图片乱序的问 题。
主要是通过重写 onSrollStateChanged(...) 方法, 在里面通过判断滑动的不同状态, ImageLoader 进行相应的动 作,此方法在其他地方可以使用。
@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);
}
}
5. 从内存加载的图片的过程
这里是主要过程,忽略了一些条件判断
6. 从文件和网络加载过程
这里是主要流程 ,忽略了一些条件判断
三、学习点
我们拆轮子,不仅仅对源码进行分析,了解其内部结构,我们还有看看一些细节,有一些值得我们学习的东西,以下是我个人总结的,不一定对每个人都适用。
1. ImegeLoaderConfiguration 适用了Build 模式,具体看源码,另外可以去看看《Effective Java》 一书的第二章会有详细的解析 Build 模式;
2. ImageLoader 使用了单例模式,这个类型的单例模式保证线程安全,我们也可以借鉴
/** Returns singleton class instance */
// 单例模式
public static ImageLoader getInstance() {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
protected ImageLoader() {
}
3. ImgeViewAware.getImageViewFieldValeu(...) 中
使用了 setAcessible(true) 可以反射获取一个类的 私有属性
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
// 返回类中声明的给定名称的域
Field field = ImageView.class.getDeclaredField(fieldName);
// 为反射对象设置可访问标志。 flag 为 true 表明屏蔽 Java 语言的访问检查,使得对象的
// 私有属性也可以被查询和设置。
// Set the accessible flag for this object to the indicated boolean value. A value of true indicates
// that the reflected object should suppress Java language access checking when it is used. A value
// of false indicates that the reflected object should enforce Java language access checks.
// 《Java 核心技术 卷 I》P205
// 设置后可以访问 private 属性
field.setAccessible(true);
int fieldValue = (Integer) field.get(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (Exception e) {
L.e(e);
}
return value;
}
这里用了一个例子说明,详细看注释
<span style="white-space:pre"> </span>private void text(){
Text eText = new Text("ijiiii");
try {
Field field = Text.class.getDeclaredField("text");
field.setAccessible(true);
Log.i("yxh", "text111 " + field.get(eText));
Field field2 = Text.class.getDeclaredField("text");
Log.i("yxh", "text222 " + (String)field2.get(eText)); // 这里报错,拿不到 private 属性
} catch (Exception e) {
Log.i("yxh", "text error ");
e.printStackTrace();
}
}
4. 在 ImgeLoadConfiguration.Builder() 使用 context.getAppliactionContext() 替代 context
因为涉及到网络请求,退出 Activity 时, 可能网络请求还在线程池中执行,如果是 Activity 的 context ,可能会造成内存 泄露,所以使用ApplicationContext 替代;
5. ViewAware 中使用了弱引用保存 View, 防止内存泄露
<span style="white-space:pre"> </span>new WeakReference<View>(view); // 弱引用
6. LoadAndDisplayImageTask.run() 方法中,使用了 ReetrantLock 加锁
loadFromUriLock.lock(); // 加锁, 在 try 外面
Bitmap bmp;
try {
// 检测是否重用, 是否被取消
checkTaskNotActual();
// 尝试从内存缓存中获取 bitmap , 如果为空,则调用 tryLoadBitmap() 方法加载 Bitmap
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
// 获取 Bitmap, 先从文件中,如果没有,再从网络中获取
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);
}
// 对 Bitmap 先进行处理
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(); // 解锁, 一定要在 finally 里面
}
// 显示图片
DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
runTask(displayBitmapTask, syncLoading, handler, engine);
}
ReentrantLock 的使用模板, 《Java 核心技术 卷 I》 P644
lock.lock();
try{
// do something
} finally{
lock.unlock(); // 一定要在 finally 里面
}
7. 在 ImageLoaderEngine 中
(1). 使用了同步包装器 Collections.synchronizedMap(...) 保证了 cacheKeysForImageAwares 的线程安全;
(2). 使用 WeakHashMap 存储 ReentrantLock
(3). 使用 AtomicBoolen 原子 保证 boolean 的线程安全
// 同步器 Collections.synchronizedMap 保证 cacheKeysForImageAwares 的线程安全, 用来存储 ImageAware 的 ID 和 memoryCacheKey
private final Map<Integer, String> cacheKeysForImageAwares = Collections.synchronizedMap(new HashMap<Integer, String>());
// 使用图片的 uri 为 key , 存储 ReentrantLock 锁, 通过 ImageLoadingInfo ,最终在 LoadAndDisplayImageTask 中调用,用来锁住 里面在
// 文件或者网络加载 Bitmap
private final Map<String, ReentrantLock> uriLocks = new WeakHashMap<String, ReentrantLock>();
private final AtomicBoolean paused = new AtomicBoolean(false);
说明:
1. 使用同步包装器的方法使用锁的型式加以保护,提供了线程安全访问; 不过最后还是利用 ConcurrentHashMap ,多线程访问不会彼此阻塞。如果是经常修改的列表,可以使用 CopyOnWriteArrayList 替代 ArrayList. 《Java 核心技术 卷I 》P 674
8.利用 MD5 生成文件名存在本地,保证数据的安全, Md5FileNameGenerator 类
四、结束
1、更加详细的分析可以看 codekk 的源码分析
2. 源码里面的分析的注释版本,我已经上传到 github 上,地址 https://github.com/yxhuangCH/universalimageloader_mynot