返回
前端
分类

这个是android系统默认的应用缓存位置,上篇文章讲解了使用LruCache策略在内存中缓存图片

日期: 2020-03-16 18:02 浏览次数 : 163

DiskLruCache是一个十分好用的android缓存工具,我们可以从GitHub上下载其源码:https://github.com/JakeWharton/DiskLruCache

     上篇文章讲解了使用LruCache策略在内存中缓存图片,如果你还未了解,请先看Android 缓存浅谈(一) LruCache。

DiskLruCache所有的数据都存储在/storage/emulated/0/Android/data/应用包名/cache/XXX文件夹中(你也可以修改,但不建议这样做,原因请继续往下看),这个是android系统默认的应用缓存位置,如果应用被删除,这个文件也会一起被删除,避免应用删除后有残留数据的问题。同时,由于数据没有存储在硬盘里,所以不会影响系统性能,在sd卡里,你可以存储任意多数据。

     在Android应用开发中,为了提高UI的流畅性、响应速度,提供更高的用户体验,开发者常常会绞尽脑汁地思考如何实现高效加载图片,而DiskLruCache实现正是开发者常用的图片缓存技术之一。Disk LRU Cache,顾名思义,硬件缓存,就是一个在文件系统中使用有限空间进行高速缓存。每个缓存项都有一个字符串键和一个固定大小的值。

由于DiskLruCache是被final修饰的,因此不可以直接通过new获得它的实例,我们使用它的open方法获得它的一个实例:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

     今天这篇文章 是讲解使用DiskLruCache做二级缓存。DiskLruCache是用来做磁盘缓存的良药。关于更加仔细的描述,请看郭神的这篇文章,Android DiskLruCache完全解析,硬盘缓存的最佳方案。本篇文章是讲解自己对DiskLruCache的认识。

open方法需要四个参数,第一个是缓存文件文件的位置,通过下面的方法可得到:

一、下载DiskLruCache。

  private File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        //如果sd卡存在并且没有被移除
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

      DiskLruCache虽然得到了Google认证,但是SDK中还没有加入,所以我们使用DiskLruCache需要自己去下载,下载地址(该地址需要翻墙,并且经常打不开),因此,我们可以在JakeWharton/DiskLruCache下载DiskLruCache源码(DiskLruCache是JakeWharton大神的杰作),另外,我提供了jar包,jar包下载地址。

第二个参数是应用程序的版本号,要传入版本号是因为如果应用升级缓存会被清除掉。通过下面的方法可以获得程序的版本号:

这个是android系统默认的应用缓存位置,上篇文章讲解了使用LruCache策略在内存中缓存图片。二、DiskLruCache使用。

private int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(
                    context.getPackageName(), 0);
            return info.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

2.1、创建DiskLruCache。

第三个参数表示同一个key可以对应多少个缓存文件,一般情况下我们都是传1,这样key和缓存文件一一对应,查找和移除都会比较方便。

       DiskLruCache不能通过构造方法来创建,它通过open方法来穿件自身。

第四个参数表示最大可以缓存多少字节的数据。

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

打开了DiskLruCache之后,我们可以看看怎么向DiskLruCache中缓存数据:

参数说明:

先来看看从网上down一张图片:

必赢备用网址 ,(1). File directory 指定数据的缓存地址。获取路径详见2.1.1。

private boolean downloadImg(final String urlStr,
            final OutputStream outputStream) {
        HttpURLConnection conn = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(conn.getInputStream(), 8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int len = 0;
            while ((len = in.read()) != -1) {
                out.write(len);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (conn != null)
                conn.disconnect();
            try {
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (in != null)
                    in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

(2). int appVersion 指定当前应用程序的版本号。当appVersion改变时,之前的缓存都会被清除,所以如非必要,我们为其指定一个1。获取应用程序的版本号详见2.1.2。

这是一个简单的联网down图片代码,拿到图片后就可以缓存到本地了,但是对于每一个存储资源都需要有一个key,这个key要是唯一的,而且这个key 最长120个字符,且只能包括a-z,0-9,下划线以及减号,一次我们可以采用Java中的UUID来得到key,也可以使用MD5加密网址得到一个 key,我这里采用md5,方法如下:

(3). int valueCount 是Key所对应的文件数,我们通常选择一一对应的简单关系,这样比较方便控制,当然我们也可以一对多的关系,通常写入1,表示一一对应的关系。

public class MD5Util {

    public final static String md5(String pwd) {
        //用于加密的字符
        char md5String[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'A', 'B', 'C', 'D', 'E', 'F' };
        try {
            //使用平台的默认字符集将此 String 编码为 byte序列,并将结果存储到一个新的 byte数组中
            byte[] btInput = pwd.getBytes();

            // 获得指定摘要算法的 MessageDigest对象,此处为MD5
            //MessageDigest类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。
            //信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。 
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            //System.out.println(mdInst);  
            //MD5 Message Digest from SUN, <initialized>

            //MessageDigest对象通过使用 update方法处理数据, 使用指定的byte数组更新摘要
            mdInst.update(btInput);
            //System.out.println(mdInst);  
            //MD5 Message Digest from SUN, <in progress>

            // 摘要更新之后,通过调用digest()执行哈希计算,获得密文
            byte[] md = mdInst.digest();
            //System.out.println(md);

            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            //System.out.println(j);
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {   //  i = 0
                byte byte0 = md[i];  //95
                str[k++] = md5String[byte0 >>> 4 & 0xf];    //    5  
                str[k++] = md5String[byte0 & 0xf];   //   F
            }

            //返回经过加密后的字符串
            return new String(str);

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

(4). long maxSize 缓存大小。

各位看官在使用的时候记得把md5String[]中大写的字母改为小写,因为key中如果有大写字母验证会不通过。当然,你也可以修改DiskLruCache的源码从而让它支持大写字母,修改的地方:

2.1.1、设置DiskLruCache的存储路径。

必赢备用网址 1

public static File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

现在万事俱备,我们来把图片缓存起来,由于联网是好事操作,所以要在新线程中完成,完整的方法如下:

PS:

   private void cacheImg() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                String key = MD5Util.md5(IMGIP);
                try {
                    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    if (editor != null) {
                        OutputStream out = editor.newOutputStream(0);
                        if (downloadImg(IMGIP, out)) {
                            //提交
                            editor.commit();
                        } else {
                            //撤销操作
                            editor.abort();
                        }
                    }
                    /**
                     * 这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。
                     * 这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。
                     * 并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,
                     * 只会额外增加同步journal文件的时间。
                     * 比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了
                     */
                    mDiskLruCache.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
Context.getExternalCacheDir().getPath(); 获取到的是 /sdcard/Android/data/<application package>/cache 这个路径 
Context.getCacheDir().getPath();  获取到的是 /data/data/<application package>/cache 这个路径 

editor.newOutputStream(0);方法有一个参数,查看源码我们知道这个参数必须大于0并且小于valueCount,前文中valueCount我们已经设置为1了,所以这里只能取值0。这个时候打开你的缓存文件夹,/storage/emulated/0/Android/data/应用包名/cache/XXX,里边已经有了我们缓存的数据了:

建议尽可能将缓存数据保存到/sdcard/Android/data/<application package>/cache 这个路径,好处是,当应用卸载时,该目录底下的数据会清空。更多 有关存储路径,可以查看这篇文章,Android File(一) 存储以及File操作介绍。

必赢备用网址 2

2.1.2、获取应用程序的版本号。

好了,数据存下来了,接下来就是读取,每一个缓存文件都对应一个key,读取就是根据这个key来读取:

public static int getAppVersion(Context context) {
        try {
            return context.getPackageManager().getPackageInfo(
                    context.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }
 private void showImg() {
        String key = MD5Util.md5(IMGIP);  
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            if(snapShot!=null){
                InputStream is = snapShot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                im.setImageBitmap(bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }

2.2、添加到缓存。

读取的时候我们最先拿到的是一个Snapshot 对象,再根据我们之前传入的参数0拿到缓存文件的流,最后把流转换为图片。

      将图片缓存到磁盘,需要使用DiskLruCache.Editor对象。Editor 表示一个缓存对象的编辑对象。同样Editor也不是new出来的,是通过DiskLruCache获取的。

到这里大家可能就明白了,之前的editor.newOutputStream(0);方法为什么会有一个0的参数了,相当于一个标识,读取时也传入参数0才能读到我们想要的数据。(加入我们的key与缓存文件不是一一对应,也就是我们一开始的open方法中传入的不是valueCount的值不是 1,那么一个key对应多个缓存文件我们要怎么区分?就是通过这种方式,有兴趣的同学查看源码就一目了然了)。

    public DiskLruCache.Editor edit(String key) throws IOException {
        return this.edit(key, -1L);
    }

下来就是清除缓存了,看方法:

      首先获取图片 url 所对应的 key,然后根据 key 通过 editor() 来获取 Editor 对象,如果这个缓存正在被编辑,那么 editor()会返回 null,即 DiskLruCache 不允许同时编辑一个缓存对象。

private void clearCache() {
        String key = MD5Util.md5(IMGIP);
        try {
            mDiskLruCache.remove(key);
        } catch (IOException e) {
            e.printStackTrace();
        }  
    }

      对于每一个存储资源都需要有一个key,这个key要是唯一的,并且和数据一一对应。现在我们存放的是图片,自然会想到了图片独一无二的url。图片Url路径,可能包含一些特殊字符,不符合文件名的标准,无法直接命名为文件名,如果将给这个url进行编码,让其比较规整,推荐使用MD5编码。下面展示 MD5代码,(该代码是网上的一段示例代码)

根据缓存文件的key,调用remove方法,将该缓存文件移除。

public static String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    private static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

下来是查看缓存大小:

下面,看看如何编写具体的写入缓存代码,(该代码是网上的代码片段)

必赢备用网址 3
像凤凰新闻客户端中显示缓存大小,这个数值我们可以通过size()方法直接拿到:

首先看看addBitmapToDiskLruCache()方法,

   private void getCacheSize() {
        tv.setText(mDiskLruCache.size()+"");
    }
/**
     * 该代码需要在子线程中进行 将图片添加到磁盘缓存
     * @param imageUrl 图片的下载地址
     * @param diskLruCache 缓存对象
     * @return
     */
    public static boolean addBitmapToDiskLruCache(String imageUrl,
            DiskLruCache diskLruCache) {
        boolean result = false;
        String key = Md5Utils.hashKeyForDisk(imageUrl); // 通过md5加密了这个URL,生成一个key
        try {
            Editor editor = diskLruCache.edit(key);// 产生一个editor对象
            if (editor != null) {
                // 创建一个新的输出流 ,创建DiskLruCache时设置一个节点只有一个数据,所以这里的index直接设为0即可
                OutputStream outputStream = editor.newOutputStream(0);
                // 通过地址获取图片数据写入到输出流
                if (DownLoadBitmapUtils.downloadUrlToStream(imageUrl,
                        outputStream)) {
                    // 写入成功,提交
                    editor.commit();
                    result = true;
                } else {
                    // 写入失败,中止
                    editor.abort();
                    result = false;
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         return result;
    }

大家应该看到了凤凰新闻还有一个功能就是清除缓存,这个功能直接调用delete方法就能实现:

首先把传递参数即图片Url,通过Md5生成一个key,然后得到一个editor对象,接着获取editor对象的输出流,通过地址获取图片数据写入到该输出流,如果返回‘true’,则调用editor.commit();提交,否则,调用editor.abort();中止此次操作。

 private void deleteAll() {
        /**
         * 这个方法用于将所有的缓存数据全部删除
         * 其实只需要调用一下DiskLruCache的delete()方法就可以实现了。
         * 会删除包括日志文件在内的所有文件
         */
        try {
            mDiskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

接下来看看downloadUrlToStream()方法,

所有功能都完成之后,我们要记得在onDestory方法中关闭DiskLruCache。

/**
     * 建立HTTP请求,并获取图片流对象。
     * @param urlString 图片下载路径
     * @param outputStream  Editor的输出流
     * @return
     */
    public static boolean downloadUrlToStream(String urlString,
            OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(),
                    8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
 @Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。
         * 关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法,
         * 通常只应该在Activity的onDestroy()方法中去调用close()方法。
         */
        try {
            mDiskLruCache.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

获取图片的流写入到参数输出流中。代码都有注释,多看几遍,相信就能理解。

到此,我想大家已经基本会用这个东东了吧。

2.3、读取缓存。

最后奉上本文源码下载地址http://pan.baidu.com/s/1kTzSHtd

     取出磁盘缓存需要使用到Snapshot对象。同样Snapshot 也不是new出来的,是通过DiskLruCache获取的。

     首先需要将URL转换成Key,然后通过DiskLruCache的get方法得到一个Snapshot对象,在通过Snapshot对象得到缓存文件的输入流,再把输入流转换成Bitamp对象。

public synchronized DiskLruCache.Snapshot get(String key) throws IOException

具体代码如下所示,

/**该代码需要在子线程中进行
     * 从缓存中获取Bitmap对象
     * 
     * @param imageUrl
     * @return
     */
    public static Bitmap getCacheBitmap(String imageUrl,DiskLruCache diskLruCache) {
        String key = Md5Utils.hashKeyForDisk(imageUrl);// 把Url转换成KEY
        try {
            DiskLruCache.Snapshot snapShot = diskLruCache.get(key);// 通过key获取Snapshot对象
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);// 通过Snapshot对象获取缓存文件的输入流
                Bitmap bitmap = BitmapFactory.decodeStream(is);// 把输入流转换成Bitmap对象
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

读取的时候我们最先拿到的是一个Snapshot 对象,再根据我们之前传入的参数0拿到缓存文件的流,最后把流转换为图片。到这里大家可能就明白了,之前的editor.newOutputStream(0);方法为什么会有一个0的参数了,相当于一个标识,读取时也传入参数0才能读到我们想要的数据。(假如我们的key与缓存文件不是一一对应,也就是我们一开始的open方法中传入的不是valueCount的值不是 1,那么一个key对应多个缓存文件我们要怎么区分?就是通过这种方式,有兴趣的同学查看源码就一目了然了)。

2.4、DiskLruCache的其他常用方法。

(1). size(),获取缓存大小。

/**
     * 获取缓存大小
     * @return
     */
    public long getDiskLruCacheSize(DiskLruCache diskLruCache){
        return diskLruCache.size();
    }

这个方法会返回当前缓存路径下所有缓存数据的总字节数,以byte为单位。

(2). remove()和delete(), 清除缓存。

/**
     * 清除某一个缓存
     * 
     * @param diskLruCache
     * @return
     */
    public void clearDiskLruCacheBykey(DiskLruCache diskLruCache,
            String imageUrl) {
        try {
            String key = Md5Utils.hashKeyForDisk(imageUrl);
            diskLruCache.remove(key);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 清除所有缓存
     * 
     * @param diskLruCache
     * @return
     */
    public void clearAllDiskLruCache(DiskLruCache diskLruCache) {
        try {
            diskLruCache.delete();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

(3). flush(), 这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。前面在讲解写入缓存操作的时候有调用过一次这个方法,但其实并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间。比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。

/**
     * 并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间,
     * 推荐在activity的onPause中调用
     * @param diskLruCache
     */
    public void flushDiskLruCache(DiskLruCache diskLruCache) {
        try {
            diskLruCache.flush();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

(4). close(),关闭DiskLruCache。

/**
     * 这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法
     * @param diskLruCache
     */
    public void closeDiskLruCache(DiskLruCache diskLruCache) {
        try {
            diskLruCache.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法,通常只应该在Activity的onDestroy()方法中去调用close()方法。

三、实战。

1.新建Android项目,新建布局文件等等。

以上这几步,更加详细的可以参考Android 缓存浅谈(一) LruCache。

  1. 实现DiskLruCache功能。

必赢备用网址 4

这是工程的包以及类截图,重点说明DiskLruCacheUtils类,下面看该类的具体代码,

package cn.xinxing.test.utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.ImageView;
import android.widget.ListView;

import com.jakewharton.disklrucache.DiskLruCache;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import cn.xinxing.test.R;
import cn.xinxing.test.constant.Images;
import cn.xinxing.test.model.LoaderResult;

/**
 * 磁盘缓存工具类
 */
public class DiskLruCacheUtils {


    private DiskLruCache mDiskLruCache;
    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 50; // 磁盘缓存的大小为50M
    private static final String DISK_CACHE_SUBDIR = "bitmap"; // 设置缓存的文件名bitmap
    private static final int APP_VERSION = 1;
    private static final int VALUES_COUNT = 1;

    private static final int CPU_COUNT = Runtime.getRuntime()
            .availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;// 核心线程数
    private ExecutorService pool;// 线程池
    private Future future;
    private static final String TAG = "DiskLruCacheUtils";
    public static final int MESSAGE_POST_RESULT = 1;
    private ListView mListView;// ListView的实例
    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView imageView = result.imageView;
            String uri = (String) imageView.getTag();
            if (uri.equals(result.uri)) {
                imageView.setImageBitmap(result.bitmap);
            } else {
                imageView.setImageResource(R.mipmap.ic_launcher);
            }
        }

        ;
    };


    public DiskLruCacheUtils(Context context, ListView listView) {
        mListView = listView;
        pool = Executors.newFixedThreadPool(CORE_POOL_SIZE);
        initDiskLruCache(context);
    }

    /**
     * 初始化
     * @param context
     */
    public void initDiskLruCache(Context context) {
        try {
            File cacheDir = FileUtils.getDiskCacheDir(context, DISK_CACHE_SUBDIR);
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            mDiskLruCache = DiskLruCache.open(cacheDir, APP_VERSION, VALUES_COUNT, DISK_CACHE_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 显示图片
     *
     * @param imageView
     * @param imageUrl
     */
    public void showImage(final ImageView imageView, final String imageUrl) {
        imageView.setTag(imageUrl);
        // 从缓存中获取
        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                if (!Thread.currentThread().isInterrupted()) {
                    Bitmap bitmap = loadBitmapFromDishLruCache(imageUrl);
                    if (bitmap != null) {
                        LoaderResult result = new LoaderResult(imageView, imageUrl,
                                bitmap);
                        mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result)
                                .sendToTarget();
                    }
                }
            }
        };
        future = pool.submit(loadBitmapTask);
    }

    /**
     * 加载Bitmap对象。
     *
     * @param start 第一个可见的ImageView的下标
     * @param end   最后一个可见的ImageView的下标
     */
    public void showIamges(int start, int end) {
        for (int i = start; i < end; i++) {
            String imageUrl = Images.imageUrls[i];
            //从缓存中取图片
            ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
            loadImage(imageUrl, imageView);
        }
    }

    /**
     * 加载图片
     * @param imageUrl 图片的下载路径
     * @param imageView
     */
    public void loadImage(final String imageUrl, final ImageView imageView) {
        imageView.setTag(imageUrl);
        // 从缓存中获取
        Runnable loadBitmapTask = new Runnable() {

            @Override
            public void run() {
                if (!Thread.currentThread().isInterrupted()) {
                    Bitmap bitmap = loadBitmap(imageUrl);
                    if (bitmap != null) {
                        LoaderResult result = new LoaderResult(imageView, imageUrl,
                                bitmap);
                        mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result)
                                .sendToTarget();
                    }
                    Log.e(TAG,"---->Thread run");
                }
            }
        };
        future = pool.submit(loadBitmapTask);
    }

    /**
     * 取消所有任务
     */
    public void cancelAllTask() {
        future.cancel(true);
    }

    /**
     * 从缓存中加载图片
     * @param imageUrl
     * @return
     */
    private Bitmap loadBitmapFromDishLruCache(String imageUrl) {
        Bitmap bitmap;
        //从缓存中获取
        bitmap = BitmapCacheUtils.getCacheBitmap(imageUrl, mDiskLruCache);
        return bitmap;
    }

    /**
     * 获取图片
     * @param imageUrl
     * @return
     */
    private Bitmap loadBitmap(String imageUrl) {
        Bitmap bitmap;
        //从缓存中获取
        bitmap = BitmapCacheUtils.getCacheBitmap(imageUrl, mDiskLruCache);
        if (bitmap != null) {
            return bitmap;
        }
        try {
            bitmap = loadBitmapFromHttp(imageUrl);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return bitmap;
    }


    /**
     * 磁盘缓存的添加
     *
     * @param imageUrl
     * @return
     * @throws IOException
     */
    private Bitmap loadBitmapFromHttp(String imageUrl) throws IOException {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException("can not visit network from UI Thread.");
        }
        if (mDiskLruCache == null) {
            return null;
        }
        if (BitmapCacheUtils.addBitmapToDiskLruCache(imageUrl, mDiskLruCache)) {
            return BitmapCacheUtils.getCacheBitmap(imageUrl, mDiskLruCache);
        }
        return null;
    }
}

首先初始化,创建缓存目录以及线程池,然后加载图片时,先从缓存中获取(要在子线程中进行),如果缓存中有,则显示图片,如果没有则去下载并加入到缓存中,然后从缓存中获取,再显示。

使用DiskLruCacheUtils时,使用了线程池机制,因为在列表中可能会同时加载多个图片,如果只是一直创建线程,那么对app的性能以及体验都是考验,所以,建议使用线程池机制。

有关线程池,请参考这篇文章Android(线程二) 线程池详解 。

PS:代码下载连接!
总结:

     DiskLruCache的使用比LruCache稍微复杂一点,但是这一点都不影响它的性能。目前市面上大部分涉及缓存的App以及开源项目例如Android-Universal-Image-Loader,都有DiskLruCache的影子,所以值得推荐。本篇中的大部分代码都是从网上直接复制的,不要发明重复的轮子。必赢备用网址 5

PS: 参考文章:   详细解读DiskLruCache。