针对Android 的图片加载,有着太多的细节问题,需要注意,本文针对 Universal Image Loader 的一些技术点,进行细致的剖析一番。由于涉及的内容,所以打算分成三个系列,分别从视图(View),数据(Cache),网络等三个大的方面讲起:
视图
1. ImageLoader是如何实现对ImageView的包装的?
1)ImageAware:针对 ImageView 行为的抽象--接口,获取 ImageView 的宽度、高度、scaleType、id;以及包装的ImageView,设定图片;
2)ViewAware: 抽象类,实现ImageAware。提供了ViewRef的属性,来持有ImageView的弱引用;实现方式:this.viewRef = new WeakReference<View>(view)。
使用弱引用的目的,避免了异步耗时任务对 ImageView 的强引用,能够使 ImageView 能够被及时回收,防止内存泄露的发生(虽然很短暂的一瞬间)。
2. 在 ListView 加载显示图片的时候,当一个正在加载图片的 View 被滑出屏幕,ImageLoader 是否会取消此次下载图片任务,是如何取消的?
首先在加载获取图片时,是通过 ImageLoaderEngine 来进行提交进行的。在 ImageLoaderEngine 是启动线程池来异步加载图片,分别从内存、磁盘、网络中进行获取。而在这几步之前,会首先进行 View 是否被回收的判断,若是被回收,则抛出异常,并调用相应 listener 的 cancel 方法。
3. 在 ListView 的滑动过程中,如何暂停所有的图片加载任务?
在 ImageLoader 中,其提供了一个 PauseOnScrollListener
的类,在使用 ListView
的时,只需进行设置即可。
其实现原理则是在通过调用 ImageLoader 的 pause
和 resume
方法,在调用图片加载的第一步会进行判断,是否设置了暂停状态,如果设置了,则会通过对象锁 pauseLock
的 wait 方法,来使当前图片加载线程处于阻塞状态;当调用了 resume
方法,则会调用了 pauseLock
的 notifyAll 方法,来恢复线程的执行。
这样做的作用是达到 CPU 资源的充分利用,通过暂停异步图片加载的线程,来不使 UI 线程卡顿,提高 ListView 在滑动过程的流畅程度。
4. 怎样针对 View 的特定大小,处理获取到图片的大小?
在 ImageLoader 调用 displayImage
方法时,在指定相应的 ImageView 时,也可以传递一个 ImageSize
的参数,用来指定所需显示的图片的大小;若是不传的话,则会获取 ImageView 的 width 及 height,若获取到的值为 0,那么这个相应 ImageSize
的宽与高则会取屏幕的宽与高。
另外,在对图片进行缓存时,生成相应的缓存 Key 的值是根据图片的 uri
和 targetSize
(指定的图片大小)来生成的,所以,不同大小的 ImageView 获取到的 bitmap 则是不同的,即从缓存中拿到的是不同的。
这里,可以看出相应大小的 ImageView 与内存的缓存中的不同的 bitmap 是相对应的。而 diskCache 中则是以 uri 为键值的磁盘文件。另外,由磁盘文件转换为相应的 bitmap 则是对应下面问题的答案。
5. 图片是如何进行压缩的?
我们知道图片加载到内存之中,是以 Bitmap 的形式存在的。而在 Android App 中,内存是非常稀缺的资源。所以当加载大图片时,需要根据当前显示图片的控件,采用相应手段,只在内存中加载出来相应大小的 Bitmap ,来避免 Out Of Memory
的发生。
这里采用 BitmapFactory 来进行图片文件转换至 Bitmap 对象,通过其 decodeStream
方法,若是我们传递的参数 Options,其指定了 inJustDecodeBounds
为 true, 则只会获取图片的大小(并不会生成 Bitmap 对象),其输出值为 Options 对象的 outWidth
和 outHeight
。
根据获取到的图片大小,以及我们要显示图片的 View 的大小,便可计算出我们需要对图片进行缩放的比例,即指定 Options 参数的 inSampleSize
的值。(此时 inJustDecodeBounds 的值为 false)这样获取到的 Bitmap 对象就是进行缩放调整过的图像。
这一步便是 Android 中调整 Bitmap 大小,减少内存消耗关键性的一步。
数据
数据主要体现在对图片的缓存处理。UIL 对图片的缓存为三级缓存,一是内存,二是磁盘,三是网络(远程的服务器)。
在内存中保存的为 Bitmap 对象,其是根据相应的 View 和 View 大小为 Key 值的。即加载的是同一张图片的两个不同大小的 View,会在内存缓存中存在针对这张图片的两个 Bitmap 对象。而磁盘中缓存的则只有这一张图片文件,即从远程服务器中下载到本地的图片文件。
若是本地磁盘中没有响应的图片文件的话,则会通过网络从远程服务器中下载图片至本地。
网络
从网络中获取图片,UIL 使用的是 HttpURLConnection
,来执行图片的获取下载。对应逻辑代码是在 ImageLoader
接口中,其定义了由图片 imageUri,来得到 InputStream 。另外以指定 Scheme 的方式(如 HTTP,FILE,ASSETS,DRAWABLE) 来得到图片的输入流。
总结
UIL 作为图片加载的入门库,其逻辑代码也是写的非常漂亮。结构化清晰,简单明了,对各个模块都由一个接口来定义,极大地丰富细节的实现,像不同的内存、磁盘缓存策略及图片下载获取方式,另外这些策略都可以在 ImageLoader 的配置策略进行修改。总之,这个库是一个不错的学习 Android 图片加载的资料。