博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android二级缓存之物理存储介质上的缓存DiskLruCache
阅读量:6326 次
发布时间:2019-06-22

本文共 11729 字,大约阅读时间需要 39 分钟。



Android二级缓存之物理存储介质上的缓存DiskLruCache

Android DiskLruCache属于物理性质的缓存,相较于LruCache缓存,则DiskLruCache属于Android二级缓存中的最后一级。通常Android缓存分为两级,第一级是内存缓存,第二级是物理缓存也即DiskLruCache。顾名思义,DiskLruCache就是将数据缓存到Android的物理介质如外部存储器存储卡、内部存储器存储卡上。

关于LruCache缓存即内存缓存,我在之前写过一系列文章,详情请见附录文章2,3。本文介绍Android硬件级的缓存策略:DiskLruCache。
DiskLruCache的Android谷歌官方实现代码链接:
DiskLruCache.java Android谷歌官方源代码实现链接:
事实上,由于DiskLruCache实现原理和过程透明公开,有不少第三方实现,在github上有一个比较流行的DiskLruCache开源实现版本,其项目主页:
本文将基于JakeWharton实现的DiskLruCache开源库为例说明。使用JakeWharton实现的DiskLruCache,需要先将github上的代码下载,下载后,直接复制到自己项目代码java目录下作为自己的源代码直接使用即可。

(1)DiskLruCache的初始化。

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

DiskLruCache使用前首先需要从一个静态方法open创建一个DiskLruCache实例。

一个缓存目录directory,缓存目录directory可以用Android系统提供的默认缓存目录,也可以自己指定一个显而易见的目录。
DiskLruCache在open缓存目录时候,如果前后appVersion不同则销魂缓存。
valueCount类似于指明一个数组的长度,通常是1,是1 的话,那么在后面写缓存newOutputStream时候是newOutputStream(0),因为类似数组下标。长度为1的数组,那么数组中只有一个元素且该元素的下标是0。同样,读的时候也是getInputStream(0)。
maxSize意义简单,指定DiskLruCache缓存的大小,总不能让DiskLruCache无限制缓存吧。所以一般要给DiskLruCache指定一个适当的缓存尺寸和限制,一般是10 * 1024 * 1024,10MB。

我写的初始化例子:

private void makeDiskLruCache() {        try {            File cacheDir = getDiskCacheDir(this, UNIQUENAME);            if (!cacheDir.exists()) {                Log.d(TAG, "缓存目录不存在,创建之...");                cacheDir.mkdirs();            } else                Log.d(TAG, "缓存目录已存在,不需创建.");            //第二个参数我选取APP的版本code。DiskLruCache如果发现第二个参数version不同则销毁缓存            //第三个参数为1,在写缓存的流时候,newOutputStream(0),0为索引,类似数组的下标            mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE);        } catch (Exception e) {            e.printStackTrace();        }    }

(2)往DiskLruCache写缓存的一般过程。

DiskLruCache的缓存是<K,V>结构。缓存写入DiskLruCache,首先要从DiskLruCache获得一个DiskLruCache.Editor,用DiskLruCache.Editor的editor传递参数key进去,再获得一个OutputStream,拿到这个OutputStream,就可以把DiskLruCache当作一个接收数据的输出流往里面写数据。写完不要忘记commit。一个DiskLruCache写缓存的代码片段:

//把byte字节写入缓存DiskLruCache    private void writeToDiskLruCache(String url, byte[] buf) throws Exception {        Log.d(TAG, url + " : 开始写入缓存...");        //DiskLruCache缓存需要一个key,我先把url转换成md5字符串,        //然后以md5字符串作为key键        String key=urlToKey(url);        DiskLruCache.Editor editor = mDiskLruCache.edit(key);        OutputStream os = editor.newOutputStream(0);        os.write(buf);        os.flush();        editor.commit();        mDiskLruCache.flush();        Log.d(TAG, url + " : 写入缓存完成.");    }

(3)从DiskLruCache读缓存的一般过程。

直接的以之前写缓存时候的key键从DiskLruCache中读取:DiskLruCache.get(key),获得一个快照DiskLruCache.Snapshot,如果这个DiskLruCache.Snapshot为null,则说明没有缓存,如果有,则说明已经缓存,然后从DiskLruCache.Snapshot组建一个输入流把缓存数据恢复出来即可。读缓存的代码:

//从DiskLruCache中读取缓存    private Bitmap readBitmapFromDiskLruCache(String url) {        DiskLruCache.Snapshot snapShot = null;        try {            //把url转换成一个md5字符串,然后以这个md5字符串作为key            String key = urlToKey(url);            snapShot = mDiskLruCache.get(key);        } catch (Exception e) {            e.printStackTrace();        }        if (snapShot != null) {            Log.d(TAG, "发现缓存:" + url);            InputStream is = snapShot.getInputStream(0);            Bitmap bitmap = BitmapFactory.decodeStream(is);            Log.d(TAG, "从缓存中读取Bitmap.");            return bitmap;        } else            return null;    }

再写一个完整的简单例子说明。一个ImageView,ImageView需要加载一个网路图片,该图片是我的csdn博客头像。例子中,代码启动后,在为ImageView加载网络图片时候,会首先检查本地DiskLruCache中是否有缓存,如果有则直接使用缓存,如果没有,则重新开启一个线程下载图片资源,图片下载完成后,一方面要设置到ImageView中,同时要把图片数据写入DiskLruCache缓存中。

package zhangphil.app;import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.widget.ImageView;import com.jakewharton.disklrucache.DiskLruCache;import java.io.BufferedInputStream;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class MainActivity extends AppCompatActivity {    private Handler handler;    private ExecutorService pool;    // 默认的线程池容量    private int DEFAULT_TASK_NUMBER = 10;    private final int WHAT = 0xe001;    private String TAG = "zhangphil_tag";    private String UNIQUENAME = "zhangphil_cache";    private DiskLruCache mDiskLruCache = null;    //缓存大小    private int DISK_CACHE_MAX_SIZE = 10 * 1024 * 1024;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //初始化DiskLruCache,创建DiskLruCache实例        makeDiskLruCache();        //创建容量为 asyncTaskNumber 的线程池。        pool = Executors.newFixedThreadPool(DEFAULT_TASK_NUMBER);        final ImageView image = (ImageView) findViewById(R.id.image);        handler = new Handler() {            @Override            public void handleMessage(Message msg) {                switch (msg.what) {                    case WHAT:                        image.setImageBitmap((Bitmap) msg.obj);                }            }        };        //一个测试的URL连接,从这个链接下载一个图片加载到ImageView中        String image_url = "http://avatar.csdn.net/9/7/A/1_zhangphil.jpg";        getBitmap(image_url);    }    private void getBitmap(String url) {        //首先从DiskLruCache读取缓存,缓存是否有该url的图片缓存        Bitmap bmp = readBitmapFromDiskLruCache(url);        if (bmp == null) {            //如果缓存中没有,则创建一个线程下载            Thread t = new DownloadThread(url);            //把线程放到线程池中下载            pool.execute(t);        } else {            //在DiskLruCache中发现缓存,直接复用            sendResult(bmp);        }    }    //从DiskLruCache中读取缓存    private Bitmap readBitmapFromDiskLruCache(String url) {        DiskLruCache.Snapshot snapShot = null;        try {            //把url转换成一个md5字符串,然后以这个md5字符串作为key            String key = urlToKey(url);            snapShot = mDiskLruCache.get(key);        } catch (Exception e) {            e.printStackTrace();        }        if (snapShot != null) {            Log.d(TAG, "发现缓存:" + url);            InputStream is = snapShot.getInputStream(0);            Bitmap bitmap = BitmapFactory.decodeStream(is);            Log.d(TAG, "从缓存中读取Bitmap.");            return bitmap;        } else            return null;    }    //把byte字节写入缓存DiskLruCache    private void writeToDiskLruCache(String url, byte[] buf) throws Exception {        Log.d(TAG, url + " : 开始写入缓存...");        //DiskLruCache缓存需要一个key,我先把url转换成md5字符串,        //然后以md5字符串作为key键        String key=urlToKey(url);        DiskLruCache.Editor editor = mDiskLruCache.edit(key);        OutputStream os = editor.newOutputStream(0);        os.write(buf);        os.flush();        editor.commit();        mDiskLruCache.flush();        Log.d(TAG, url + " : 写入缓存完成.");    }    private void makeDiskLruCache() {        try {            File cacheDir = getDiskCacheDir(this, UNIQUENAME);            if (!cacheDir.exists()) {                Log.d(TAG, "缓存目录不存在,创建之...");                cacheDir.mkdirs();            } else                Log.d(TAG, "缓存目录已存在,不需创建.");            //第二个参数我选取APP的版本code。DiskLruCache如果发现第二个参数version不同则销毁缓存            //第三个参数为1,在写缓存的流时候,newOutputStream(0),0为索引,类似数组的下标            mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE);        } catch (Exception e) {            e.printStackTrace();        }    }    // 开辟一个下载线程    private class DownloadThread extends Thread {        private String url;        public DownloadThread(String url) {            this.url = url;        }        @Override        public void run() {            try {                byte[] imageBytes = loadRawDataFromURL(url);                // 数据下载完毕,把新的bitmap数据写入DiskLruCache缓存                writeToDiskLruCache(url, imageBytes);                Bitmap bmp = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);                sendResult(bmp);            } catch (Exception e) {                e.printStackTrace();            }        }    }    // 发送消息通知:bitmap已经下载完成。    private void sendResult(Bitmap bitmap) {        Message message = handler.obtainMessage();        message.what = WHAT;        message.obj = bitmap;        handler.sendMessage(message);    }    //从一个url下载原始数据,本例是一个图片资源。    public byte[] loadRawDataFromURL(String u) throws Exception {        Log.d(TAG, "开始下载 " + u);        URL url = new URL(u);        HttpURLConnection conn = (HttpURLConnection) url.openConnection();        InputStream is = conn.getInputStream();        BufferedInputStream bis = new BufferedInputStream(is);        ByteArrayOutputStream baos = new ByteArrayOutputStream();        final int BUFFER_SIZE = 2048;        final int EOF = -1;        int c;        byte[] buf = new byte[BUFFER_SIZE];        while (true) {            c = bis.read(buf);            if (c == EOF)                break;            baos.write(buf, 0, c);        }        conn.disconnect();        is.close();        byte[] data = baos.toByteArray();        baos.flush();        Log.d(TAG, "下载完成! " + u);        return data;    }    /*    *    * 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,    * 否则就调用getCacheDir()方法来获取缓存路径。    * 前者获取到的就是 /sdcard/Android/data/
/cache * 而后者获取到的是 /data/data/
/cache 。 * * */ public File getDiskCacheDir(Context context, String uniqueName) { String cachePath = null; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getCacheDir().getPath(); } File dir = new File(cachePath + File.separator + uniqueName); Log.d(TAG, "缓存目录:" + dir.getAbsolutePath()); return dir; } //版本名 public static String getVersionName(Context context) { return getPackageInfo(context).versionName; } //版本号 public static int getVersionCode(Context context) { return getPackageInfo(context).versionCode; } private static PackageInfo getPackageInfo(Context context) { PackageInfo pi = null; try { PackageManager pm = context.getPackageManager(); pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS); return pi; } catch (Exception e) { e.printStackTrace(); } return pi; } public String urlToKey(String url) { return getMD5(url); } /* * 传入一个字符串String msg,返回Java MD5加密后的16进制的字符串结果。 * 结果形如:c0e84e870874dd37ed0d164c7986f03a */ public static String getMD5(String msg) { MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } md.reset(); md.update(msg.getBytes()); byte[] bytes = md.digest(); String result = ""; for (byte b : bytes) { // byte转换成16进制 result += String.format("%02x", b); } return result; }}

涉及到Android网络操作和存储设备的读写,不要忘记加相关权限:

附录文章:

1,《基于Java LinkedList,实现Android大数据缓存策略》链接地址:
2,《使用新式LruCache取代SoftReference缓存图片,Android异步加载图片》链接地址:
3,《使用Android新式LruCache缓存图片,基于线程池异步加载图片》链接地址:
4,《Java MD5(字符串)》链接地址:
5,《从一个URL下载原始数据,基于byte字节》链接地址:

6,《Android获取App版本号和版本名》链接地址:



转载地址:http://fhmaa.baihongyu.com/

你可能感兴趣的文章
unity游戏开发之NGUI的UISprite染色
查看>>
HDOJ find the safest road 1596【最短路变形】
查看>>
高度决定视野眼界决定世界
查看>>
shell脚本路径写法的注意点
查看>>
Testng生成的测试报告乱码解决办法
查看>>
vim快速入门
查看>>
大杂烩 -- 单向链表是否存在环或是否相交
查看>>
关键字检索高亮标出-javasript/jQuery代码实现
查看>>
Vijos P1785 同学排序【模拟】
查看>>
人物关系网络图可视化
查看>>
关于ADO.Net SqlConnection的性能优化
查看>>
docker安装及加速配置
查看>>
MRF能量优化
查看>>
什么是.Net, IL, CLI, BCL, FCL, CTS, CLS, CLR, JIT
查看>>
Atlas Control ToolKit 发布
查看>>
世界是数字的
查看>>
Dundas 系列
查看>>
Windows的命令行查看,修改,删除,添加环境变量
查看>>
iOS 图文混排
查看>>
GC是什么? 为什么要有GC?
查看>>