概述:
Google又提供了一套硬盘缓存的解决方案:DiskLruCache(非Google官方编写,但获得官方认证)。
那么我们先来看一下有哪些应用程序已经使用了DiskLruCache技术。在我所接触的应用范围里,Dropbox、Twitter、网易新闻等都是使用DiskLruCache来进行硬盘缓存的,其中Dropbox和Twitter大多数人应该都没用过,那么我们就从大家最熟悉的网易新闻开始着手分析,来对DiskLruCache有一个最初的认识吧。
使用:
DiskLruCache没有限制数据的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data//cache 这个路径。选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。
1.依赖
implementation 'com.jakewharton:disklrucache:2.0.2'
2.初始化
参数:
- directory 第一个是缓存文件文件的位置
- 是应用程序的版本号
- 表示同一个key可以对应多少个缓存文件,一般情况下我们都是传1
- 参数表示最大可以缓存多少字节的数据
public DiskCache(File directory, long maxSize) {
try {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
mDiskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.保存数据
/**
* 保存bitmap到磁盘
* 通过图片url 然后md5 加密的内容,作为key 来保存bitmap 对象
*
* @param url 图片请求url --》 md5 去除特殊字符
* @param bitmap
*/
public void saveBitmap(final String url) {
new Thread() {
@Override
public void run() {
super.run();
try {
String key = Utils.hashKeyForDisk(url);
//editor 操作数据保存逻辑
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OutputStream outputStream = editor.newOutputStream(0);
// 下载图片
if (downloadImage(url, outputStream)) {
editor.commit();
} else {
editor.abort();
}
//别忘了关闭流和提交编辑
outputStream.close();
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
4.获取数据
/**
* 通过url 获取md5加密后的key。然后通过key 获取bitmap 对象
*
* @param url
* @return
*/
public Bitmap getBitmap(String url) {
//使用DiskLruCache获取缓存,需要传入key,而key是imageUrl加密后的字符串,
Bitmap bitmap = null;
String key = Utils.hashKeyForDisk(url);
//通过key获取的只是一个快照
DiskLruCache.Snapshot snapshot = null;
try {
snapshot = mDiskLruCache.get(key);
} catch (IOException e) {
e.printStackTrace();
}
if (snapshot != null) {
InputStream inputStream = snapshot.getInputStream(0);//类似写缓存时候,传入的是缓存的编号
//可以使用bitmapFactory
bitmap = BitmapFactory.decodeStream(inputStream);
}
return bitmap;
}
缓存文件
每个图片对应一个乱码形式名称的文件
- journal文件解读 对图片操作的日志文件
libcore.io.DiskLruCache
1
100
1
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
- journal文件头
第一行:固定字符串libcore.io.DiskLruCache
第二行:DiskLruCache的版本号,这个值恒为1。
第三行:应用程序的版本号。每当版本号改变,缓存路径下存储的所有数据都会被清空,因为DiskLruCache认为应用更新,所有的数据都应重新获取。
第四行:指每个key对应几个文件,一般为1。
第五行:空行 - journal文件内容
DIRTY:第六行以DIRTY前缀开始,后面跟着缓存文件的key,表示一个entry正在被写入。
CLEAN:当写入成功,就会写入一条CLEAN记录,后面的数字记录文件的长度,如果一个key可以对应多个文件,那么就会有多个数字
REMOVE:表示写入失败,或者调用remove(key)方法的时候都会写入一条REMOVE记录
涉及的类
Utils .java
public class Utils {
private static final String TAG = "Utils";
/**
* 获取缓存文件夹,这里优先选择SD卡下面的android/data/packageName/cache/路径,若没有SD卡,就选择data/data/packageName/cache
*
* @param context 上下文环境
* @param uniqueName 缓存文件夹名称
* @return 返回缓存文件
*/
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();
}
File file = new File(cachePath + File.separator + uniqueName);
Log.d(TAG, "getDiskCacheDir: file="+file.getAbsolutePath());
return file;
}
/**
* 获取本App的版本号
*
* @param context context上下文
* @return 返回版本号
*/
public static int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
/**
* 给字符串来个md5加密,
* @param key 需要加密的string
* @return 返回加密后的string ,或者加密失败,就返回string的哈希值
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
//md5加密
MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
//若md5加密失败,就用哈希值
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* 字节数组转为十六进制字符串
* @param bytes 字节数组
* @return 返回十六进制字符串
*/
private static String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length()==1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
DiskCache.java
public class DiskCache {
private static final String TAG = "DiskCache";
DiskLruCache mDiskLruCache;
// 初始化
public DiskCache(File directory, long maxSize) {
try {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
mDiskLruCache = DiskLruCache.open(directory, 1, 1, maxSize);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 保存bitmap到磁盘
* 通过图片url 然后md5 加密的内容,作为key 来保存bitmap 对象
*
* @param url 图片请求url --》 md5 去除特殊字符
* @param bitmap
*/
public void saveBitmap(final String url, Bitmap bitmap) {
new Thread() {
@Override
public void run() {
super.run();
try {
String key = Utils.hashKeyForDisk(url);
//editor 操作数据保存逻辑
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OutputStream outputStream = editor.newOutputStream(0);
// 下载图片
if (downloadImage(url, outputStream)) {
editor.commit();
} else {
editor.abort();
}
//别忘了关闭流和提交编辑
outputStream.close();
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
/**
* 通过url 获取md5加密后的key。然后通过key 获取bitmap 对象
*
* @param url
* @return
*/
public Bitmap getBitmap(String url) {
//使用DiskLruCache获取缓存,需要传入key,而key是imageUrl加密后的字符串,
Bitmap bitmap = null;
String key = Utils.hashKeyForDisk(url);
//通过key获取的只是一个快照
DiskLruCache.Snapshot snapshot = null;
try {
snapshot = mDiskLruCache.get(key);
} catch (IOException e) {
e.printStackTrace();
}
if (snapshot != null) {
InputStream inputStream = snapshot.getInputStream(0);//类似写缓存时候,传入的是缓存的编号
//可以使用bitmapFactory
bitmap = BitmapFactory.decodeStream(inputStream);
}
return bitmap;
}
/**
* 下载图片
*
* @param imgUrl 图片网址链接
* @param outputStream 输出流对象
* @return 返回时候完成下载成功
*/
private boolean downloadImage(String imgUrl, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
URL url = new URL(imgUrl);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 2 * 1024);//Buffer输入流,8M大小的缓存
out = new BufferedOutputStream(outputStream, 2 * 1024);
int b;//正在读取的byte
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//关闭资源
finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
}