您的位置:首页 > 移动开发 > Android开发

某宅的 Android 学习笔记(四)——用 DiskLruCache 实现本地缓存

2016-08-29 22:06 525 查看

为什么要用DiskLruCache?

 离线数据对于依赖网络加载数据的APP来说具有很重要的意义,当无网络或者是网络状况不好时,APP依然具备部分功能是一种很好的用户体验。假设网易新闻这类新闻客户端,数据完全存储在缓存中而不使用DiskLruCache技术存储,那么当客户端被销毁,缓存被释放,意味着再次打开APP将是一片空白。DiskLruCache是Google官方推荐的一套硬盘缓存的API,虽然Android中并没有包含她,但是能得到官方推荐,必然有着过人之处。

将DiskLruCache加入项目

 首先我们需要下载DiskLruCache的文件,github。不过如果用的是AndroidStudio的话,只需要在build.gradle中添加如下代码即可:

compile 'com.jakewharton:disklrucache:2.0.2'


 接着重新构建gradle文件就可以了。

使用DiskLruCache

1.创建DiskLruCache实例

 DiskLruCache是不能直接new的,如果要创建一个DiskLruCache的实例,需要调用它的open()方法。open()方法接收四个参数,第一个参数指定的是数据的缓存地址,第二个参数指定当前应用程序的版本号,第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1,第四个参数指定最多可以缓存多少字节的数据。

 那么缓存地址如何获得呢?当然可以直接制定,但是为了用户体验考虑,不建议这么做。一般会放在/sdcard/Android/data//cache 下,因为选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。

 但是考虑到不是所有的手机都一定会有SD卡,这里还是需要做一下判断。

/**
* 获取缓存目录
*
* @param context
* @param uniqueName 用于区分缓存内容
* @return
*/
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);
}


 获取版本号。每当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。

/**
* 获取应用版本号
*
* @param context
* @return
*/
private int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}


 参数准备好了,我们就可以得到DiskLruCache的实例啦:

private DiskLruCache diskLruCache;
//获取 DiskLruCache 的实例
try {
File cacheFile = getDiskCacheDir(listView.getContext(), "bitmap");
if (!cacheFile.exists()) {
cacheFile.mkdirs();
}
diskLruCache = DiskLruCache.open(cacheFile, getAppVersion(listView.getContext()), 1,
10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}


2.获取DiskLruCache.Editor与DiskLruCache.Snapshot

 DiskLruCache.Editor可以得到一个输出流outputStream,我们可以通过这个输出流将网络请求得到的数据写入本地。网络请求方法如下:

/**
* 开启网络请求
* 根据url来下载图片
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection connection = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
final URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(connection.getInputStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}

}
return false;
}


 获取Editor的edit()方法接收一个参数key,这个key将会成为缓存文件的文件名,并且必须要和图片的URL是一一对应的。但是图片URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。不过可以将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则。

/**
* 为了保证文件命名合法,使用 MD5 编码 url
*/
private String hashKeyForDisk(String url) {
String cacheKey;
try {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(url.getBytes());
cacheKey = byteToHexString(digest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}

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


 这样只需要传入key,就能得到Editor对象了。不过要读取缓存,我们还需要Snapshot对象。要得到Snapshot很简单:

DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);


 接着调用它的getInputStream()方法就可以得到缓存文件的输入流了。同样地,getInputStream()方法也需要传一个index参数,这里传入0就好。

 图片加载的方法就可以写成这样:(这里用了AsyncTask,由于要开启网络请求,所以必须放在子线程中处理)

@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
String key = hashKeyForDisk(url);
Bitmap bitmap = null;
InputStream in = null;

try {
//获取 snapshot 对象
snapshot = diskLruCache.get(key);
//如果为空,则需要从网络下载图片,并存入缓存
if (snapshot == null) {
editor = diskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(url, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
//同步文件记录
diskLruCache.flush();
//下载完成后,重新获取 snapshot 对象
snapshot = diskLruCache.get(key);
}
//从 snapshot 中获取 bitmap
if (snapshot != null) {
in = snapshot.getInputStream(0);
bitmap = BitmapFactory.decodeStream(in);
}
//将 bitmap 存入内存缓存中
if (bitmap != null) {
addBitmapToLruCache(url, bitmap);
}

return bitmap;

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

return null;
}


 首次启动时的图片加载方法:

/**
* 若缓存中存在bitmap,直接设置
* 反之设置默认的本地图片
*/
public void showImg(ImageView imageView, String url) {
//优先从缓存获取图片
Bitmap bitmap = getBitmapFromLruCache(url);
try {
if (bitmap == null) {
snapshot = diskLruCache.get(hashKeyForDisk(url));
if(snapshot != null){
bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
}
}
} catch (IOException e) {
e.printStackTrace();
}
//若bitmap存在,直接设置
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
//初始化ImageView的图片,未加载网络图片时显示
imageView.setImageResource(R
a0d3
.mipmap.ic_launcher);
}
}


 示例如下,可以看到成功从本地取出了缓存的图片。



最后附上自己项目的地址:https://github.com/Zhai-Wang/KanZhiHu,欢迎各路大神指正!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐