在android代码里development/samples有一个工具类:ImageDownloader,其作用是从网上下载图片显示到给定的ImageView.如下时原文说明:
This helper class download images from the Internet and binds those with the provided ImageView.
这里来解析一下这个工具类是如何实现的,下面一个一个来解释.
(1)首先,他采用了强引用(StrongReference)和软引用(SoftReference)来保存下载的图片(bitmap),具体做法是:StrongReference来保存一定容量的图片(bitmap),当超过这个容量的时候就将其移入SoftReference来保存.
其中这里的强StrongReference来保存图片(bitmap)实际上是采用LinkedHashMap来实现的! 如下代码所示:
private final static HashMap<String, Bitmap> sHardBitmapCache =
new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {//
private static final long serialVersionUID = -7190622541619388252L;
@Override
protected boolean removeEldestEntry(Map.Entry<String, Bitmap> eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// Entries push-out of hard reference cache are transferred to soft reference cache
sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else {
return false;
}
}
};
上面的代码定义来一个容量为HARD_CACHE_CAPACITY / 2的LinkedHashMap(这里的HARD_CACHE_CAPACITY可以根据你的实际情况来定义,这里定义的是40), 并且该LinkedHashMap时按照取用来排序的,增长率时0.75. 最重要的是从写removeEldestEntry这个方法,这样一来,一旦超过容量就会把最后一个元素移动到sSoftBitmapCache里面.这里的sSoftBitmapCache其实也是一个HashMap. 那sSoftBitmapCache到底是一个什么HashMap呢,这里先卖一个关子.
上面说到的保存图片资源的时使用map的键值对:Map.Entry<String, Bitmap> 这里的String是我们需要下载的图片的路径(也就是网络地址).
(2)每次需要获取一个图片的时候首先检测sHardBitmapCache和sSoftBitmapCache是否已经存在了,否则需要开一个任务来后台下载并处理.这里的后台处理实际上是采用来常用的AsyncTask来实现.下面看看给一个ImageView下载图片的入口方法:
private void forceDownload(String url, ImageView imageView, String cookie) {
// State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
if (url == null) {
imageView.setImageDrawable(null);
return;
}
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url, cookie);
}
}
上面代码中需要解释DownloadedDrawable和BitmapDownloaderTask,其中DownloadedDrawable是一个ColorDrawable, 实际上这里可以自己定义一个预存的图片.
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(Color.BLACK);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
实际上DownloadedDrawable还缓存来他自己的BitmapDownloaderTask,后面就知道什么用了.
BitmapDownloaderTask其实就是一个AsyncTask,在方法doInBackground里面采用AndroidHttpClient进行从网上下载图片数据并保存为Bitmap.然后在onPostExecute里面处理获取的bitmap
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
// Add bitmap to cache
if (bitmap != null) {
synchronized (sHardBitmapCache) {
sHardBitmapCache.put(url, bitmap);
}
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
}
上面的代码首先将获取的bitmap缓存到sHardBitmapCache里面.然后找到对应的ImageView,并设置该bitmap显示.
(3)我们现在思考一个问题: 一般来说手机的运行内存本来就紧张,这里sHardBitmapCache和sSoftBitmapCache由于加载了大量的图片资源,难免没有上面问题,最重要的是sHardBitmapCache和sSoftBitmapCache都是static的变量.所以实际上他们保存的数据时没有必要一直存在的,所以只要不需要的时候就需要clear掉.
(4)最后来看看sSoftBitmapCache到底用什么HashMap可以. 上面说了bitmap的加载实际上采用了AsyncTask的多线程方式, 所以是很可能出现多个修改并发操作的.所以采用ConcurrentHashMap最合适.ConcurrentHashMap采用了锁分离技术可以很好的解决多操作并发进行,并且也是线程安全的.
// Soft cache for bitmap kicked out of hard cache
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache =
new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);