【拆轮子系列】Universal Image Loader 源码分析

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) 方法


二、源码分析

1. 整体的设计

图片来源 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


发布了58 篇原创文章 · 获赞 20 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/yxhuang2008/article/details/52842962