android代码解析之图片缓存(ImageDownloader)

在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);

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。