《Android开发艺术探索》笔记之Bitmap的加载和Cache<二>
缓存:
目前常用的一种缓存算法是LRU(Least Recently Used),即近期最少使用算法,核心思想是当缓存满时,会优先淘汰近期最少使用的缓存对象。采用LRU算法的缓存有两种:LruCache和DiskLruCache,LruCache用于实现内存缓存,DiskLruCache则是存储设备缓存。
LruCache:
算法原理:把最近使用的对象用强引用存储在LinkedHashMap中,并且把最近使用的对象在缓存值达到预设值之前从内存中移除
强引用:直接的对象引用
软引用:当一个对象只有软引用存在时,系统内存不足时此对象会被gc回收
弱引用:当一个对象只有弱引用存在时,此对象会随时被gc回收
下面是一个使用 LruCache 来缓存图片的例子:
private LruCache mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来计算缓存对象的大小(注意单位,转换成KB,与总容量单位一致)
return bitmap.getByteCount() / 1024;
//或者return bitmap.getRowBytes()*bitmap.getHeight()/1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
DiskLruCache:
首先强调一点:DiskLruCache是非Google官方编写,但获得官方文档的推荐。如果要使用它,得先下载这个类,然后在项目中新建一个libcore.io包,将DiskLruCache.java文件复制到这个包中即可。
DiskLruCache通过将缓存对象写入文件系统从而实现缓存的效果。
DiskLruCache的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为/sdcard/Android/data/
创建一个DiskLruCache的实例:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
open()方法接收四个参数,第一个参数指定的是数据的缓存地址,上面已经介绍过;第二个参数指定当前应用程序的版本号,一般设为1.当版本号发生改变时DiskLruCache会清空之前所有缓存文件。第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1;第四个参数指定最多可以缓存多少字节的数据,当缓存大小超过这个设定值后,DiskLruCache会清除一些缓存从而保证总大小不大于这个设定值。
一个非常标准的open()方法就可以这样写:
private static final long DISK_CACHE_SIZE=1024*1024*50;
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir,1, 1, DISK_CACHE_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
private File getDiskCacheDir(Context context,String uniqueName){
boolean externalStorageAvailable=Environment.getExternalStorageState().equals(Enviroment.MEDIE_MOUNTED);
//判断SD卡是否正常挂载
final String cachePath;
if(externalStorageAvailable){
cachePath=context.getExternalCacheDir().getPath();
//获取到SDCard/Android/data//cache/目录
}else{
cachePath=context.getCacheDir().getPath();
//获取/data/data//cache目录
}
return new File(cachePath+File.separator+uniqueName);
}
先将url的md5值作为key,如下:
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();
}
public 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;
}
String key=hashKeyFormUrl(url);
DiskLruCache.Editor editor=mDiskLruCache.edit(key);
if (editor != null) {
//由于前面在DiskLruCache的open方法中设置了一个节点,
//只能有一个数据,所以下面参数为0
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
和添加类似,先将url转换成key,然后通过DiskLruCache的get方法得到一个Snapshot对象,接着通过Snapshot对象得到缓存的文件输入流,最后得到Bitmap对象。如下:
Bitmap bitmap=null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapshot=mDiskLruCache.get(key);
if (snapshot!=null) {
InputStream inputStream=snapshot.getInputStream(0);
bitmap=BitmapFactory.decodeStream(inputStream);
if(bitmap!=null){
addBitmapToMemoryCache(key,bitmap);}
}
藉此,LruCache和DiskLruCache都介绍完了。