• 三分钟学会缓存工具DiskLruCache


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

    DiskLruCache所有的数据都存储在/storage/emulated/0/Android/data/应用包名/cache/XXX文件夹中(你也可以修改,但不建议这样做,原因请继续往下看),这个是android系统默认的应用缓存位置,如果应用被删除,这个文件也会一起被删除,避免应用删除后有残留数据的问题。同时,由于数据没有存储在硬盘里,所以不会影响系统性能,在sd卡里,你可以存储任意多数据。
    由于DiskLruCache是被final修饰的,因此不可以直接通过new获得它的实例,我们使用它的open方法获得它的一个实例:
    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
    open方法需要四个参数,第一个是缓存文件文件的位置,通过下面的方法可得到:

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

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

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

    第三个参数表示同一个key可以对应多少个缓存文件,一般情况下我们都是传1,这样key和缓存文件一一对应,查找和移除都会比较方便。
    第四个参数表示最大可以缓存多少字节的数据。
    打开了DiskLruCache之后,我们可以看看怎么向DiskLruCache中缓存数据:
    先来看看从网上down一张图片:

        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;
        }

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

    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;
            }
        }
    }

    各位看官在使用的时候记得把md5String[]中大写的字母改为小写,因为key中如果有大写字母验证会不通过。当然,你也可以修改DiskLruCache的源码从而让它支持大写字母,修改的地方:
    这里写图片描述
    现在万事俱备,我们来把图片缓存起来,由于联网是好事操作,所以要在新线程中完成,完整的方法如下:

        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();
        }

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

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

        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();
            } 
        }

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

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

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

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

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

    下来是查看缓存大小:
    这里写图片描述
    像凤凰新闻客户端中显示缓存大小,这个数值我们可以通过size()方法直接拿到:

        private void getCacheSize() {
            tv.setText(mDiskLruCache.size()+"");
        }

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

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

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

        @Override
        protected void onDestroy() {
            super.onDestroy();
            /**
             * 这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。
             * 关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法,
             * 通常只应该在Activity的onDestroy()方法中去调用close()方法。
             */
            try {
                mDiskLruCache.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

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

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

    版权声明:本文为博主原创文章,未经博主允许不得转载。若有错误地方,还望批评指正,不胜感激。

  • 相关阅读:
    a sample of if_nametoindex
    ssh 报 You don't exist, go away
    VMware网卡类型说明及修改
    warning: dereferencing typepunned pointer will break strictaliasing rules(20120613 13:11:02)
    关于字节序和比特序 Little Endian Big Endian
    C语言 运行codeblocks 没有反应
    邻接矩阵作为主要存储结构
    菜鸟学习 MFC
    中国特色工作流引擎设计考虑因素
    如何实现通过汉字的拼音或首拼快速检索(含部分源码)
  • 原文地址:https://www.cnblogs.com/lenve/p/4770527.html
Copyright © 2020-2023  润新知