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

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

关于LruCache缓存即内存缓存,我在之前写过一系列文章,详情请见附录文章2,3。本文介绍Android硬件级的缓存策略:DiskLruCache。

DiskLruCache的Android谷歌官方实现代码链接:

DiskLruCache.java Android谷歌官方源代码实现链接:https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java

事实上,由于DiskLruCache实现原理和过程透明公开,有不少第三方实现,在github上有一个比较流行的DiskLruCache开源实现版本,其项目主页:https://github.com/JakeWharton/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/<application package>/cache
* 而后者获取到的是 /data/data/<application package>/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网络操作和存储设备的读写,不要忘记加相关权限:

<!-- SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 向SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"></uses-permission>

附录文章:

1,《基于Java LinkedList,实现Android大数据缓存策略》链接地址:http://blog.csdn.net/zhangphil/article/details/44116885

2,《使用新式LruCache取代SoftReference缓存图片,Android异步加载图片》链接地址:http://blog.csdn.net/zhangphil/article/details/43667415

3,《使用Android新式LruCache缓存图片,基于线程池异步加载图片》链接地址:http://blog.csdn.net/zhangphil/article/details/44082287

4,《Java MD5(字符串)》链接地址:http://blog.csdn.net/zhangphil/article/details/44152077

5,《从一个URL下载原始数据,基于byte字节》链接地址:http://blog.csdn.net/zhangphil/article/details/43794837

6,《Android获取App版本号和版本名》链接地址:http://blog.csdn.net/zhangphil/article/details/43795099



Android二级缓存之物理存储介质上的缓存DiskLruCache的更多相关文章

  1. Android OkHttp与物理存储介质缓存:DiskLruCache(2)

     Android OkHttp与物理存储介质缓存:DiskLruCache(2) 本文在附录文章8,9的基础之上,把Android OkHttp与DiskLruCache相结合,综合此两项技术,实 ...

  2. android上的缓存、缓存算法和缓存框架

      1.使用缓存的目的 缓存是存取数据的临时地,因为取原始数据代价太大了,加了缓存,可以取得快些.缓存可以认为是原始数据的子集,它是从原始数据里复制出来的,并且为了能被取回,被加上了标志. 在andr ...

  3. Android Binder机制原理(史上最强理解,没有之一)(转)

    原文地址: http://blog.csdn.net/universus/article/details/6211589 Binder是Android系统进程间通信(IPC)方式之一.Linux已经拥 ...

  4. IOS 区分缓存 内存 物理存储 逻辑存储

    1. 存储器分为内部存储器(内存)和外部存储器(外存). ①内存 内存是电脑内部临时存放数据的地方,供CPU直接读取,存放在其中的数据要靠电来维持,一旦断电就会丢失.因此,在操作电脑时,应及时地将需要 ...

  5. 我的Android进阶之旅】GitHub 上排名前 100 的 Android 开源库进行简单的介绍

    GitHub Android Libraries Top 100 简介 本文转载于:https://github.com/Freelander/Android_Data/blob/master/And ...

  6. Hibernate缓存简介和对比、一级缓存、二级缓存详解

    一.hibernate缓存简介 缓存的范围分为3类:  1.事务范围(单Session即一级缓存)     事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象 ...

  7. Android okHttp网络请求之文件上传下载

    前言: 前面介绍了基于okHttp的get.post基本使用(http://www.cnblogs.com/whoislcj/p/5526431.html),今天来实现一下基于okHttp的文件上传. ...

  8. wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)

    wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...

  9. HTML5定稿了,终于有一种编程语言开发的程序可以在Android和IOS两种设备上运行了

    2007 年 W3C (万维网联盟)立项 HTML5,直至 2014 年 10 月底,这个长达八年的规范终于正式封稿. 过去这些年,HTML5 颠覆了 PC 互联网的格局,优化了移动互联网的体验,接下 ...

随机推荐

  1. how-to-fix-fs-re-evaluating-native-module-sources-is-not-supported-graceful

    http://stackoverflow.com/questions/37346512/how-to-fix-fs-re-evaluating-native-module-sources-is-not ...

  2. [转]Business Model Canvas(商业模式画布):创业公司做头脑风暴和可行性测试的一大利器

    本文转自:http://www.36kr.com/p/214438.html 本文来自First Round Review,他们准备的文章既讲故事,还同时向创业者提供可操作的建议,以助力打造优秀的公司 ...

  3. Java实现求二叉树的路径和

    题: 解: 这道题考的是如何找出一个二叉树里所有的序列. 我的思路是先从根节点开始遍历,找出所有的子节点,因为每个子节点只有一个父节点,再根据每个子节点向上遍历找出所有的序列,再判断序列的总和. 这样 ...

  4. Android使用Gson(相当于C#的Newtonsoft.Json)非常好用

    C#转Java有一段时间了,之前做ASP.NET WebAPI微软竟将第三方类库Newtonsoft.Json作为VS新建MVC和WebAPI项目默认必备的Json工具Nuget包,可想而知这个包有多 ...

  5. 前端缓存-IndexedDB

    IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作.IndexedDB 允许储存大量数据,提供查找接口,还能建立索引.这些都是 LocalStorage 所不具备的.就数据库类 ...

  6. T4870 水灾(sliker.cpp/c/pas) 1000MS 64MB

    题目描述 大雨应经下了几天雨,却还是没有停的样子.土豪CCY刚从外地赚完1e元回来,知道不久除了自己别墅,其他的地方都将会被洪水淹没. CCY所在的城市可以用一个N*M(N,M<=50)的地图表 ...

  7. SugarCRM安装踩雷(一)

    安装SugarCRM前置条件: 1.找对平台.正确版本的安装包 2.APACHE + MYSQL + TOMCAT环境先确保OK 坑1: 进入安装参数设置步骤的MYSQL用户密码——这里根据Mysql ...

  8. 了解java内存回收机制-博客导读

    此文作为读优质博客前的导读文 1.如何判断对象是否该回收 该对象是否被引用,是否处于不可达状态 2.对象的引用机制 强引用.软引用.弱引用.虚引用 3.垃圾回收机制如何回收.算法. 串行回收.并行回收 ...

  9. QT入门学习2

    QT获取窗口几何布局有2类函数: 1.包含框架:x().y().frameGemetry().pos().move()... 2.不包含框架:geometry().width().height().w ...

  10. iOS Programming Touch Events and UIResponder

    iOS Programming Touch Events and UIResponder  1 Touch Events  As a subclass of UIResponder, a UIView ...