为什么会用到缓存呢?主要是流量耗不起啊,国内的公共场所的WiFi的普及率不高,因此必须考虑流量的问题,说白了,就是用户体验啊,每次都网络请求,消耗资源不说,网速不好的情况下还会有网络延时,用户体验不好。
Android中的缓存,从方式上来说,一般有网络缓存,磁盘缓存即SD卡缓存,内存缓存。网络缓存需要服务端的配合,用于加快网络请求的响应速度。磁盘缓存一般用DiskLruCache,当然也可以用SqlLite数据库,以及sharedpreference等作持久化处理。这里主要说下两种常用的缓存方法,LruCache、DiskLruCache。前者用于内存缓存,后者用于设备缓存,一般两者结合起来效果更好。
其实缓存的实现并不难,每一中缓存都会有三个基本操作,添加、获取、删除。了解这些了,就会有思路了。
再说LruCache、DiskLruCache,可以看到,两者都有Lru,那么Lru是什么呢?这是目前常用的一种缓存算法:近期最少使用算法,核心思想很简单,就是当缓存满时,会优先删除那些近期最少使用的缓存。那么现在分别了解下这两种缓存吧。
LruCache
LruCache内部用到的是LinkedHashMap,LinkedHashMap与HashMap的不同住处在于LinkedHashMap 维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。也就说它的插入和访问是有顺序的。另外LruCache是线程安全的。至于使用的话就很简单了。
// 初始化 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight() / 1024;
}
};
总缓存大小一般会设置为当前进程可用内存的1/8,当然这个数是可以自己设置的,这个数是推荐的。sizeOf方法是为了计算缓存对象的大小。如果有必要也可以重写entryRemoved来完成某些资源回收工作。
再看缓存的添加与删除,
//添加缓存 mMemoryCache.put(key,bitmap); //获取缓存 mMemoryCache.get(key); //删除缓存 mMemoryCache.remove(key);
DiskLruCache
DiskLruCache用与磁盘缓存,被官方推荐使用。下面来看看它的使用。
自从用了Gradle后,引入项目方便多了,谁用谁知道。
compile 'com.jakewharton:disklrucache:2.0.2'
创建DiskLruCache:
DiskLruCache mDiskLruCache = null; try {
File cacheDir = getDiskCacheDir(context, "bitmap"); if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
解释下DiskLruCache.open的参数,第一个表示存储的路径,第二个表示应用的版本号,注意这里当版本号发生改变时会清空之前所有的缓存文件,而在实际开发中这个性质用的不多,所以直接写1。第三个表示单个节点对应的数据的个数,设置为1就可以了,第四个表示缓存的总大小,当超出这个值时,会清除一些缓存保证总大小不大于这个设定的值。
添加缓存:
第一步,网络下载图片(文件也是一样的步骤的)并通过outputStream写入到本地
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null; try { final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024); int b; while ((b = in.read()) != -1) {
out.write(b);
} return true;
} catch (final IOException e) {
e.printStackTrace();
} finally { if (urlConnection != null) {
urlConnection.disconnect();
} try { if (out != null) {
out.close();
} if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
} return false;
}
第二步,处理缓存的key,直接用url作为key值时最有快捷的方式,但是url里会有特殊字符,不符合Android的命名规范,最好的办法就是把url进行MD5摘要。
public 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 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();
}
第三步 创建DiskLruCache.Editor的实例,写入数据
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
editor.commit()方法用来提交写入操作,editor.abort()回退整个操作。
读取缓存:
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) {
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(0);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight); if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
需要说明下的是为了避免加载图片时导致OOM,不建议直接加在Bitmap,通常我们会通过BitmapFactory.Options来加载一张缩放的图片,但是这中方法对于FileInputStream有问题,因为FileInputStream是有序的文件流,而两次的从的 decodeStream调用影响了文件流的位置属性,导致第二次decodeStream时得到的为null。为了解决这个问题,可以先得到对应的文件描述符,然后通过BitmapFactory.decodeFileDescriptor()来加载图片。
移除缓存:
mDiskLruCache.remove(key);
来自:http://www.jianshu.com/p/96a7865fdab4
- 本文固定链接: https://zxbcw.cn/post/5407/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)