DiskLruCache硬盘缓存技术详解
上次讲了使用内存缓存LruCache去加载很多图片而不造成OOM,而这种缓存的特点是在应用程序运行时管理内存中的资源(图片)的存储和释放,如果LruCache中有一张图片被释放了,再次加载该图片时需要重新从网络上下载下来,这就显得废流量不说,而且费时,网络不好的状况下用户需要等待,而且在没有网络的情况下不会显示任何数据。
那么怎样才能解决这种情况呢?答案就是加入硬盘缓存DiskLruCache。
1、什么是硬盘缓存呢?
顾名思义,就是把从网络上加载的数据存储在本地硬盘上,当再次加载这些数据时候,通过一系列判断本地是否有该数据,就不会从先网络上加载,而是从本地硬盘缓存中拿取数据,这样即使在没有网络情况下,也可以把数据显示出来。举个例子:比如网易新闻app,我们打开客户端后开始浏览新闻,之后发现在手机没有联网的情况下,之前浏览的界面还是能正常的显示出来,这显然就是用到了硬盘缓存DiskLruCache技术,其实硬盘缓存技术在诸多app中运用了,比如一些视频类app、小说类app。。。然而,DiskLruCache并不是Google官方编写的,不过获得了Google的认可,我们要使用它需要在Google官网上去下载这个类:android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java。
2、硬盘缓存中缓存的数据存储在哪里呢?
/**
* @param cacheDirName - 缓存的最终目录文件夹名称
* @return - 获取硬盘缓存的目录
*/
private File getDiskLruCacheDir(Context context, String cacheDirName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
File file = new File(cachePath + "/" + cacheDirName);
if (!file.exists()) {
file.mkdirs();
}
return file;
}
3、硬盘缓存中缓存的数据格式是什么样子的呢?
1、怎么创建DiskLruCache对象呢?
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
需要传入四个参数,意思分别为:
- directory - 我们设置的缓存目录,最好用刚刚我在第二点上写的那个方法得到的缓存目录。
- appVersion - 应用程序的版本号,可以这样得到:
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); int versionID = info.versionCode;
- valueCount - 就是一个key可以对应几个缓存文件,一般传1。
- maxSize - 就是缓存空间的大小,传入的是字节,当缓存空间存储的缓存内容超过该大小后,DiskLruCache会自动清除一些缓存文件,来腾出空间进行缓存。一般我们可以根据需要缓存的内容来定,大多情况下可以设为10*1024*1024,也就是10M。
DiskLruCache mBitmapDiskLruCache = null;
File file = getDiskLruCacheDir(this, "bitmap");
int versionID = getAppVersionNum(this);
try {
mBitmapDiskLruCache = DiskLruCache.open(file, versionID, 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
getDiskLruCacheDir():
private File getDiskLruCacheDir(Context context, String cacheDirName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
File file = new File(cachePath + "/" + cacheDirName);
if (!file.exists()) {
file.mkdirs();
}
return file;
}
getAppVersionNum():
private int getAppVersionNum(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
好了,DiskLruCache对象就创建好了,接下来就是根据我们创建的DiskLruCache对象来对进行数据的一系列操作了。
2、将数据写入硬盘缓存中
将数据写入硬盘缓存中,我们需要用到的一个方法就是edit()方法:
public Editor edit(String key)
可以看到我们需要传入一个key值,这就是缓存文件的名称了,这个key值一般情况下我们会采用md5(url)的形式,就是把数据(图片等)对应的url进行md5进行编码加密后即可。
public class String2MD5Tools {
/**
* @param key
* @return
* 对key进行MD5加密并返回加密过的散列值
*/
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();
}
}
然后通过调用hashKeyForDisk就可以对url进行编码了:
String key = String2MD5Tools.hashKeyForDisk(url);
好了,回到edit()方法上,该方法返回的是一个DiskLruCache.Editor类型的对象,Editor是DiskLruCache的一个成员内部类:
DiskLruCache.Editor editor = mBitmapDiskLruCache.edit(String2MD5Tools.hashKeyForDisk(url));
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);//得到输出流,往里面写数据
bitmap = downloadBitmap(url, outputStream);//把刚刚下载的图片存入硬盘缓存中,对应的key值为md5(url)
if (bitmap != null) {
editor.commit();//提交表示写入缓存成功
}else{
editor.abort();//表示放弃此次写入
}
}
其中downloadBitmap()方法就是一个普通的从网络上下载图片的方法:
/**
* 从网络上下载图片,下载时候把下载的图片写入OutputStream中
* @param urlStr
* @return
*/
private Bitmap downloadBitmap(String urlStr, OutputStream outputStream) {
HttpURLConnection connection = null;
BufferedOutputStream bufferedOutputStream;
Bitmap bitmap = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream mInputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(mInputStream);
bufferedOutputStream = new BufferedOutputStream(outputStream);
int len=-1;
byte[] by = new byte[1024];
while ((len = mInputStream.read(by)) != -1) {
bufferedOutputStream.write(by, 0, len);
}
bufferedOutputStream.flush();
bufferedOutputStream.close();
mInputStream.close();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return bitmap;
}
当然,上述写入数据到缓存的过程需要放到另外一个线程中去执行,原因肯定不用我多说,因为有请求网络的操作。
3、从硬盘缓存中读取数据
public synchronized Snapshot get(String key)
可知它需要传入一个key值,这里我想都知道怎么传入这个key值吧,就是我们通过md5(url)编码后的key值,该方法是根据我们传入的key值来查找对应了缓存文件名称,如果找到了key和缓存文件名一样那就返回一个Snapshot类型的对象,如果没找到就返回null。
DiskLruCache.Snapshot snapshot = mBitmapDiskLruCache.get(String2MD5Tools.hashKeyForDisk(url));
if (snapshot != null) {//如果硬盘缓存中存在该缓存文件
InputStream inputStream = snapshot.getInputStream(0);//获得该缓存文件的输入流
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);//把输入流解析成Bitmap
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
}else{
//硬盘缓存中不存在图片缓存则开启一个线程去下载图片
//do sthing
}
4、从硬盘缓存中删除指定key值的缓存文件
public synchronized boolean remove(String key)
代码很简单,传入指定的key即可删除对应名称的缓存文件:
boolean flag = mBitmapDiskCache.remove(String2MD5Tools.hashKeyForDisk(url));
if(flag){
//删除成功
}else{
//删除失败
}
5、获取当前缓存目录下所使用的内存大小和清空缓存数据
获取缓存目录下总缓存文件的大小是通过size()方法返回,返回的是long类型的字节数,我们可以这样用:
long totalCacheSize = mBitmapDiskLruCache.size();
像很多应用都会在设置界面下显示该app的缓存大小,其实就是通过它得到的,然后我们可以手动清除它,清除全部的缓存数据是使用:
mBitmapDiskLruCache.delete();
来清空缓存数据。
6、同步缓存文件的操作记录至journal和关闭缓存
我们在上面讲了写入缓存数据、读取缓存数据、移除缓存数据等,那么上面的这些方法是通过实际操作来达到效果的,而记录同步缓存文件的操作信息则由journal文件来完成的,DiskLruCache能够正常的工作完全是依赖journal文件,所以在上述操作做完后同步操作记录尤为重要,同步操作记录是通过:
mBitmapDiskLruCache.flush();
完成的,通常我们会在OnPause()方法中调用它,表示操作全部结束后进行同步记录。
mBitmapDiskLruCache.close();
通常我们会在onDestroy()方法中调用它,那么完整的代码为:
@Override
protected void onPause() {
super.onPause();
try {
if (mBitmapDiskLruCache != null) {
mBitmapDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mBitmapDiskLruCache != null) {
mBitmapDiskLruCache.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
好了,DiskLruCache的原理就讲完了!接下来给个demo,该demo是同时缓存了图片和文本,然后在断网情况下打开数据正常显示,我们来看看效果:
好了,贴下主要代码:
public class MainActivity extends ActionBarActivity {
private ImageView mImageView;
private TextView mTextView;
private String url = "http://img.my.csdn.net/uploads/201507/21/1437459521_5133.jpg";
private DiskLruCache mBitmapDiskLruCache ;
private DiskLruCache mTextDiskLruCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView);
mTextView = (TextView) findViewById(R.id.textView);
initBitmapDiskLruCache();
initTextDiskLruCache();
loadBitmap(url, mBitmapDiskLruCache,mTextDiskLruCache);
}
//初始化硬盘缓存,得到缓存对象
public void initBitmapDiskLruCache() {
File file = getDiskLruCacheDir(this, "bitmap");
int versionID = getAppVersionNum(this);
try {
mBitmapDiskLruCache = DiskLruCache.open(file, versionID, 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
public void initTextDiskLruCache() {
File file = getDiskLruCacheDir(this, "text");
int versionID = getAppVersionNum(this);
try {
mTextDiskLruCache = DiskLruCache.open(file, versionID, 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加载图片和文本数据,先判断硬盘缓存中有木有,有则从硬盘缓存中取出设置,没有则开启线程下载数据
* @param url
* @param mBitmapDiskLruCache
* @param mTextDiskLruCache
*/
public void loadBitmap(String url, DiskLruCache mBitmapDiskLruCache,DiskLruCache mTextDiskLruCache) {
try {
//从硬盘缓存中加载图片
DiskLruCache.Snapshot snapshot = mBitmapDiskLruCache.get(String2MD5Tools.hashKeyForDisk(url));
if (snapshot != null) {//如果硬盘缓存中存在该缓存文件
InputStream inputStream = snapshot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
}
}else{
//硬盘缓存中不存在图片缓存则开启一个线程去下载图片
DownloadTask task = new DownloadTask(mImageView, mTextView,mBitmapDiskLruCache,mTextDiskLruCache);
task.execute(url);
}
DiskLruCache.Snapshot snapshot1 = mTextDiskLruCache.get(String2MD5Tools.hashKeyForDisk(url));
if(snapshot1!=null){
InputStream inputStream = snapshot1.getInputStream(0);
if(inputStream!=null){
int len=-1;
byte[] by = new byte[1024];
StringBuilder builder = new StringBuilder();
while ((len=inputStream.read(by))!=-1){
builder.append(new String(by,0,len));
}
mTextView.setText(builder.toString());
}
}else{
//硬盘缓存中不存在文本缓存则开启一个线程去下载
DownloadTask task = new DownloadTask(mImageView, mTextView,mBitmapDiskLruCache,mTextDiskLruCache);
task.execute(url);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param context
* @return - 得到应用的版本号
*/
private int getAppVersionNum(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
/**
* @param cacheDirName - 缓存的最终目录文件夹名称
* @return - 获取硬盘缓存的目录
*/
private File getDiskLruCacheDir(Context context, String cacheDirName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
File file = new File(cachePath + "/" + cacheDirName);
if (!file.exists()) {
file.mkdirs();
}
return file;
}
@Override
protected void onPause() {
super.onPause();
try {
if (mBitmapDiskLruCache != null) {
mBitmapDiskLruCache.flush();
}
if(mTextDiskLruCache!=null){
mTextDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mBitmapDiskLruCache != null) {
mBitmapDiskLruCache.close();
}
if(mTextDiskLruCache!=null){
mTextDiskLruCache.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
异步下载类:
public class DownloadTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
private TextView mTextView;
private String url;
private DiskLruCache mBitmapDiskLruCache;
private DiskLruCache mTextDiskLruCache;
public DownloadTask(ImageView imageView,TextView mTextView, DiskLruCache mBitmapDiskLruCache,DiskLruCache mTextDiskLruCache) {
this.imageView = imageView;
this.mTextView = mTextView;
this.mBitmapDiskLruCache = mBitmapDiskLruCache;
this.mTextDiskLruCache = mTextDiskLruCache;
}
@Override
protected Bitmap doInBackground(String... params) {
url = params[0];
Bitmap bitmap=null;
boolean flag = false;
try {
//将下载的图片写入硬盘缓存中,bitmap目录下
DiskLruCache.Editor editor = mBitmapDiskLruCache.edit(String2MD5Tools.hashKeyForDisk(url));
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
flag = downloadBitmap(url,outputStream);
if(flag){
editor.commit();//提交表示写入缓存成功
}else{
editor.abort();//表示放弃此次写入
}
}
//从硬盘缓存中获取图片
DiskLruCache.Snapshot snapshot = mBitmapDiskLruCache.get(String2MD5Tools.hashKeyForDisk(url));
if(snapshot!=null){
InputStream inputStream = snapshot.getInputStream(0);
if(inputStream!=null){
bitmap = BitmapFactory.decodeStream(inputStream);//
}
}
//将图片的文本数据写入硬盘缓存,text目录下
DiskLruCache.Editor editor1 = mTextDiskLruCache.edit(String2MD5Tools.hashKeyForDisk(url));
if(editor1!=null){
OutputStream outputStream = editor1.newOutputStream(0);
if(bitmap!=null){
outputStream.write(("height=" + bitmap.getHeight() + ",width=" + bitmap.getWidth()).toString().getBytes());
editor1.commit();
}else{
editor1.abort();
}
}
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if(imageView!=null&&bitmap!=null){
imageView.setImageBitmap(bitmap);
}
if(mTextView!=null&&bitmap!=null){
mTextView.setText(("height="+bitmap.getHeight()+",width="+bitmap.getWidth()).toString());
}
}
/**
* 从网络上下载图片
* @param urlStr
* @return
*/
private boolean downloadBitmap(String urlStr,OutputStream outputStream) {
HttpURLConnection connection = null;
Bitmap bitmap = null;
BufferedOutputStream bos;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream mInputStream = connection.getInputStream();
bos = new BufferedOutputStream(outputStream);
int len=-1;
byte[] by = new byte[1024];
while((len=mInputStream.read(by))!=-1){
bos.write(by,0,len);
bos.flush();
}
mInputStream.close();
return true;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return false;
}
}
DiskLruCache硬盘缓存技术详解的更多相关文章
- ThinkPHP 缓存技术详解 使用大S方法
如果没有缓存的网站是百万级或者千万级的访问量,会给数据库或者服务器造成很大的压力,通过缓存,大幅减少服务器和数据库的负荷,假如我们把读取数据的过程分为三个层,第一个是访问层,第一个是缓存层,第三个是数 ...
- 干货|java缓存技术详解
一.缓存是什么? 请点击此处输入图片描述 Cache ①高速缓冲存储器,其中复制了频繁使用的数据以利于快速访问. ②位于速度相差较大的两种硬件/软件之间,用于协调两者数据传输速度差异的结构 二.缓存有 ...
- php 面试必备:各种缓存技术详解
这门课程以电商网站为例,通过具体场景模块实战,让你更系统的掌握缓存原理.使用场景等相关知识,帮助你构建完整的缓存知识体系,胜任实际开发中缓存的处理,提升代码性能! 从原理到场景 系统讲解PHP缓 ...
- Hibernate的缓存技术详解
转载注明出处:http://www.cnblogs.com/xiaoming0601/p/5882980.html 一.什么是缓存: 并不是指计算机的内存或者CPU的一二级缓存:缓存是指为了降低应用程 ...
- Zookeeper系列二:分布式架构详解、分布式技术详解、分布式事务
一.分布式架构详解 1.分布式发展历程 1.1 单点集中式 特点:App.DB.FileServer都部署在一台机器上.并且访问请求量较少 1.2 应用服务和数据服务拆分 特点:App.DB.Fi ...
- 《CDN技术详解》 - CDN知多少?
开发时间久了,就会接触到性能和并发方面的问题,如果说,在自己还是菜鸟的时候完全不用理会这种问题或者说有其他的高手去处理这类问题,那么,随着经验的丰富起来,自己必须要独立去处理了.或者,知道思路也行,毕 ...
- CDN学习笔记二(技术详解)
一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精华放上网.公诸同 ...
- CDN技术详解及实现原理
CDN技术详解 一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精 ...
- 腾讯技术分享:GIF动图技术详解及手机QQ动态表情压缩技术实践
本文来自腾讯前端开发工程师“ wendygogogo”的技术分享,作者自评:“在Web前端摸爬滚打的码农一枚,对技术充满热情的菜鸟,致力为手Q的建设添砖加瓦.” 1.GIF格式的历史 GIF ( Gr ...
随机推荐
- ejabberd mod_echo 解析
ejabberd mod_echo 解析(金庆的专栏 2016.8)按开发入门的说明,mod_echo是最简单的模块之一.https://docs.ejabberd.im/developer/当然 m ...
- Spark调度模式-FIFO和FAIR
Spark中的调度模式主要有两种:FIFO和FAIR.默认情况下Spark的调度模式是FIFO(先进先出),谁先提交谁先执行,后面的任务需要等待前面的任务执行.而FAIR(公平调度)模式支持在调度池中 ...
- Linux下yum安装MySQL yum安装MySQL指定版本
yum安装MySQL 1. 查看有没有安装过 yum list installed MySQL* (有存在要卸载yum remove MySQL*) rpm -qa | grep my ...
- Python读取JSON数据,并解决字符集不匹配问题
今天来谈一谈Python解析JSON数据,并写入到本地文件的一个小例子. – 思路如下 从一个返回JSON天气数据的网站获取到目标JSON数据串 使用Python解析出需要的部分 写入到本地文件,供其 ...
- Android性能优化之Listview(ViewHolder重用机制)
相信大家在很多时候都会用到ListView这个控件,因为确实是用的很多很多,但是有木有遇到过当数据很多很多的时候,往下滑ListView时有时候会卡顿,这就需要我们来优化它了. ListView优化主 ...
- 12 SharedPreferences
SharedPreferences 创建方式 SharedPreferences preferences = getPreferences(Context context ,int mode); 参数 ...
- Unable to access the IIS metabase.You do not have sufficient privilege
今天在用vs打开以前老代码的时候报如下问题,无法打开工程了,从提示来不大可能是因为vs的版本引起的,本身我用的是最新版的vs. 网上查了下解决方法如下:找到你电脑中的如下路径"C:\Wind ...
- android插件开发机制
插件机制实质上就是由主体程序定义接口,然后由插件去实现这些接口,以达到功能模块化.Android系统是基于Linux内核的,其安全机制也继承了Linux的特性,再加上android framework ...
- UNIX网络编程——客户/服务器程序设计示范(一)
下面给出的是客户程序用于测试我们的服务器程序的各个变体. #include "unp.h" #define MAXN 16384 /* max # bytes to request ...
- 浅谈C语言 extern 指针与数组
/* * d.c * * Created on: Nov 15, 2011 * Author: root */ #include "apue.h" int a[] = {3,2}; ...