Android ImageFetcher
Posted
Google完善文档后,也提供了一大批优秀的源代码。
ImageFetcher就是其中之一,这货非常简洁的实现了用ImageView来异步加载网页图片,同时对图片进行二级缓存的功能。
稍微改改就可以轻松适配任何图片资源(例如本地,例如需特殊条件(如宽高,比例,透明度等)等)了。
主体代码构成:
外部调用如下:
- New:
ImageFetcher imageFetcher = new ImageFetcher(context, width, height);
就可以了。
当然这里一般都会改动;
例如为了区分cache目录,会把unique folder name作为参数传入进去)。
例如为了替换默认图,把图标传进去。
例如为了适配任何图片资源,增加自定义的ImageProvider,也在初始化的时候传进去。
- Load bitmap:
imageFetcher.loadImage(pathString, (ImageView) view.findViewById(R.id.thumbnail));
这样这个ImageView就会在图片通过异步下载好(并缓存)以后,自己加载显示出来了~
- Close cache:
imageFetcher.setPauseWork(false);
imageFetcher.closeCache();
就是这么简单任性~
内部逻辑重点的几个:
-
对Download下来的图片,使用DiskLruCache来本地缓存,使用LruCache作为内存缓存;
-
使用AsyncTask(WeakReference)来异步download图片;结束后对ImageView(WeakReference)进行setImageDrawable(AsyncDrawable);这里的AsyncDrawable内部有正在处理自身的AsyncTask的WeakReference,所以可以避免重复execute的情况;每次都会先cancel之前的。
-
AsyncTask跑在Executor(线程池)中。
主要原理就是这样,其中一些细节可以自己翻源码~~ 非常方便使用和扩展~ 基本已经成为搭项目时必备库之一。
但是这里有一点,需要注意!
谨慎使用ImageFetcher.setPauseWork
官方Sample给出了两个用法,看起来前后有一点矛盾:
一个是在Grid滑动的时候:
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
// Pause fetcher to ensure smoother scrolling when flinging
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
// Before Honeycomb pause image loading on scroll to help with performance
if (!Utils.hasHoneycomb()) {
mImageFetcher.setPauseWork(true);
}
} else {
mImageFetcher.setPauseWork(false);
}
}
滑动的时候为了避免UI错误刷新(这要说到Grid中实际绘制的Item如何重复利用了,算是基本概念,不再深入;同样会想起来曾经写过的“滚筒洗衣机”,里面用%
操作拿Item index的公式,也是有趣的回忆~ LOL~),来Pause住ImageFetcher,无疑问。
再就是onPause的时候,调用的是:
@Override
public void onPause() {
super.onPause();
mImageFetcher.setPauseWork(false);
mImageFetcher.setExitTasksEarly(true);
mImageFetcher.flushCache();
}
居然是***setPauseWork(false)
***!难道不是setPauseWork(true)
更正确么?
但如果你那么做了,恭喜你,你陷入了Fetcher的坑。
setPauseWork为true后,后台即将开始跑的AsyncTask会wait(类比lock)住,来等setPauseWork(false)中的notifyAll(类比unlock);
如果在onPause的时候,是setPauseWork(true)
,这样导致的问题就是,这个AsyncTask一直wait下去,占用住了线程池(Executor)的一块资源;假设这个时候还有AsyncTask在线程池(Executor)中排队等着运行的话,那么就永远等不到了;因为线程池很容易就会没有可用资源。
而且一般情况下,线程池都是static,全局共享的。这就导致了其他也在使用线程池来调度的AsyncTask,均无法正常运行的情况。
从app表现上来看,一般就是Grid的thumbnail再也获取不到了;除非退出程序再启动。
其实setPauseWork做的事情,类似对整个Fetcher的lock和unlock,从这个角度理解,就很容易明白为什么需要在onPause的时候setPauseWork(false)了;不得不承认,叫setPauseWork确实和onPause看起来有点字面含义冲突。
此坑不深,翻翻源码和Sample就明白为什么了。也许直接改成lock/unlock更合适吧~ LOL~
再加一点;
由于ImageFetcher本身是为了loading network image,所以这里不仅仅是二级缓存,其实本地缓存就做了两套,一个是thumb的缓存(ImageWorker),一个是origin的缓存(ImageFetcher);
一般界面pause后,会调用closeCache();
closeCache()是一个异步操作(单独起了一个AsyncTask),在实际closeCache的过程中,会lock并开始I/O;而且这里对于已经打开的两套缓存,是独立的两个锁;
而AsyncTask是有一个线程池的;
那么问题来了,init/close频繁的情况下(例如界面切换频繁),容易出现lock长耗时而导致imageView不刷新的情况。
解决办法,尝试改成一个锁(这种单线程顺序执行的case用两个锁干嘛啊)。