Android图片缓存之Lru算法(二)
前言:
上篇我们总结了bitmap的处理,同时对比了各种处理的效率以及对内存占用大小,。我们得知一个应用如果使用大量图片就会导致oom(out of memory),那该如何处理才能近可能的降低oom发生的概率呢?之前我们一直在使用softreference软引用,softreference是一种现在已经不再推荐使用的方式,因为从 android 2.3 (api level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用变得不再可靠,所以今天我们来认识一种新的缓存处理算法lru,然后学习一下基于lru的lrucache、disklrucache 实现我们的图片缓存。
lru:
lru是least recently used 的缩写,翻译过来就是“最近最少使用”,lru缓存就是使用这种原理实现,简单的说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉,比如我们缓存10000条数据,当数据小于10000时可以随意添加,当超过10000时就需要把新的数据添加进来,同时要把过期数据删除,以确保我们最大缓存10000条,那怎么确定删除哪条过期数据呢,采用lru算法实现的话就是将最老的数据删掉。
基于lrucache实现内存缓存:
1.)初始化memorycache
这里内存缓存的是drawable 而不是bitmap 理由是drawable相对bitmap来说有很大的内存优势
int maxmemory = (int) runtime.getruntime().maxmemory();//获取系统分配给应用的总内存大小 int mcachesize = maxmemory / 8;//设置图片内存缓存占用八分之一 mmemorycache = new lrucache<string, drawable>(mcachesize) { //必须重写此方法,来测量bitmap的大小 @override protected int sizeof(string key, drawable value) { if (value instanceof bitmapdrawable) { bitmap bitmap = ((bitmapdrawable) value).getbitmap(); return bitmap == null ? 0 : bitmap.getbytecount(); } return super.sizeof(key, value); } };
2.)添加一个drawable到内存缓存
/** * 添加drawable到内存缓存 * * @param key * @param drawable */ private void adddrawabletomemorycache(string key, drawable drawable) { if (getdrawablefrommemcache(key) == null && drawable != null) { mmemorycache.put(key, drawable); } }
3.)从内存缓存中获取一个drawable
/** * 从内存缓存中获取一个drawable * * @param key * @return */ public drawable getdrawablefrommemcache(string key) { return mmemorycache.get(key); }
4.)从内存缓存中移除一个drawable
/** * 从内存缓存中移除 * * @param key */ public void removecachefrommemory(string key) { mmemorycache.remove(key); }
5.)清空内存缓存
/** * 清理内存缓存 */ public void cleanmemoryccache() { mmemorycache.evictall(); }
其实lru缓存机制本质上就是存储在一个linkedhashmap存储,为了保障插入的数据顺序,方便清理。
基于disklrucache实现磁盘缓存:
disklrucache类并不是谷歌官方实现,需要自行下载,下载地址:https://github.com/jakewharton/disklrucache
1.)初始化disklrucache
file cachedir = context.getcachedir();//指定的是数据的缓存地址 long diskcachesize = 1024 * 1024 * 30;//最多可以缓存多少字节的数据 int appversion = disklruutils.getappversion(context);//指定当前应用程序的版本号 int valuecount = 1;//指定同一个key可以对应多少个缓存文件 try { mdiskcache = disklrucache.open(cachedir, appversion, valuecount, diskcachesize); } catch (exception ex) { }
2.)写入一个文件到磁盘缓存
/** * 添加bitmap到磁盘缓存 * * @param key * @param value */ private void addbitmaptodiskcache(string key, byte[] value) { outputstream out = null; try { disklrucache.editor editor = mdiskcache.edit(key); if (editor != null) { out = editor.newoutputstream(0); if (value != null && value.length > 0) { out.write(value); out.flush(); editor.commit(); } else { editor.abort(); } } mdiskcache.flush(); } catch (ioexception e) { e.printstacktrace(); } finally { disklruutils.closequietly(out); } }
3.)从磁盘缓存中读取drawable
/** * 从磁盘缓存中获取一个drawable * * @param key * @return */ public drawable getdrawablefromdiskcache(string key) { try { disklrucache.snapshot snapshot = mdiskcache.get(key); if (snapshot != null) { inputstream is = snapshot.getinputstream(0); bitmap bitmap = bitmapfactory.decodestream(is); drawable drawable = disklruutils.bitmap2drawable(bitmap); //从磁盘中读取到之后 加入内存缓存 adddrawabletomemorycache(key, drawable); return drawable; } } catch (ioexception e) { e.printstacktrace(); } return null; }
4.)从磁盘缓存中移除
/** * 从磁盘缓存中移除 * * @param key */ public void removecachefromdisk(string key) { try { mdiskcache.remove(key); } catch (exception e) { } }
5.)清空磁盘缓存
/** * 清理磁盘缓存 */ public void cleandiskcache() { try { mdiskcache.delete(); } catch (exception e) { } }
图片下载过程:
接下来实例中用到了一点rxjava的知识有不了解rxjava的请自行了解一下。
1.)采用异步方式操作磁盘缓存和网络下载, 内存缓存可以在主线程中操作
public void display(final imageview imageview, string imageurl) { //生成唯一key final string key = disklruutils.hashkeyfordisk(imageurl); //先从内存中读取 drawable drawablefrommemcache = getdrawablefrommemcache(key); if (drawablefrommemcache != null) { imageview.setimagedrawable(drawablefrommemcache); return; } observable.just(imageurl) .map(new func1<string, drawable>() { @override public drawable call(string imageurl) { // 参数类型 string //从磁盘中读取 drawable drawablefromdiskcache = getdrawablefromdiskcache(key); if (drawablefromdiskcache != null) { return drawablefromdiskcache; } //网络下载 return download(imageurl); // 返回类型 drawable } }) .subscribeon(schedulers.io()) // 指定 subscribe() 发生在 io 线程 .observeon(androidschedulers.mainthread()) // 指定 subscriber 的回调发生在主线程 .subscribe(new action1<drawable>() { @override public void call(drawable drawable) { // 参数类型 drawable imageview.setimagedrawable(drawable); } }); }
2.)下载图片过程以及处理
private drawable download(string imageurl) { httpurlconnection urlconnection = null; bytearrayoutputstream bos = null; inputstream ins = null; try { final url url = new url(imageurl); urlconnection = (httpurlconnection) url.openconnection(); ins = urlconnection.getinputstream(); bos = new bytearrayoutputstream(); int b; while ((b = ins.read()) != -1) { bos.write(b); } bos.flush(); byte[] bytes = bos.tobytearray(); bitmap bitmap = disklruutils.bytes2bitmap(bytes); string key = disklruutils.hashkeyfordisk(imageurl); drawable drawable = disklruutils.bitmap2drawable(bitmap); //加入内存缓存 adddrawabletomemorycache(key, drawable); //加入磁盘缓存 addbitmaptodiskcache(key, bytes); return drawable; } catch (ioexception e) { e.printstacktrace(); } finally { if (urlconnection != null) { urlconnection.disconnect(); } disklruutils.closequietly(bos); disklruutils.closequietly(ins); } return null; }
附上最终图片缓存单例简单实现全部代码以及disklruutils工具类代码
imageloadmanager.java
public class imageloadmanager { private lrucache<string, drawable> mmemorycache;//内存缓存 private disklrucache mdiskcache;//磁盘缓存 private static imageloadmanager minstance;//获取图片下载单例引用 /** * 构造器 * * @param context */ private imageloadmanager(context context) { int maxmemory = (int) runtime.getruntime().maxmemory();//获取系统分配给应用的总内存大小 int mcachesize = maxmemory / 8;//设置图片内存缓存占用八分之一 mmemorycache = new lrucache<string, drawable>(mcachesize) { //必须重写此方法,来测量bitmap的大小 @override protected int sizeof(string key, drawable value) { if (value instanceof bitmapdrawable) { bitmap bitmap = ((bitmapdrawable) value).getbitmap(); return bitmap == null ? 0 : bitmap.getbytecount(); } return super.sizeof(key, value); } }; file cachedir = context.getcachedir();//指定的是数据的缓存地址 long diskcachesize = 1024 * 1024 * 30;//最多可以缓存多少字节的数据 int appversion = disklruutils.getappversion(context);//指定当前应用程序的版本号 int valuecount = 1;//指定同一个key可以对应多少个缓存文件 try { mdiskcache = disklrucache.open(cachedir, appversion, valuecount, diskcachesize); } catch (exception ex) { } } /** * 获取单例引用 * * @return */ public static imageloadmanager getinstance(context context) { imageloadmanager inst = minstance; if (inst == null) { synchronized (requestmanager.class) { inst = minstance; if (inst == null) { inst = new imageloadmanager(context.getapplicationcontext()); minstance = inst; } } } return inst; } public void display(final imageview imageview, string imageurl) { //生成唯一key final string key = disklruutils.hashkeyfordisk(imageurl); //先从内存中读取 drawable drawablefrommemcache = getdrawablefrommemcache(key); if (drawablefrommemcache != null) { imageview.setimagedrawable(drawablefrommemcache); return; } observable.just(imageurl) .map(new func1<string, drawable>() { @override public drawable call(string imageurl) { // 参数类型 string //从磁盘中读取 drawable drawablefromdiskcache = getdrawablefromdiskcache(key); if (drawablefromdiskcache != null) { return drawablefromdiskcache; } //网络下载 return download(imageurl); // 返回类型 drawable } }) .subscribeon(schedulers.io()) // 指定 subscribe() 发生在 io 线程 .observeon(androidschedulers.mainthread()) // 指定 subscriber 的回调发生在主线程 .subscribe(new action1<drawable>() { @override public void call(drawable drawable) { // 参数类型 drawable imageview.setimagedrawable(drawable); } }); } /** * 添加drawable到内存缓存 * * @param key * @param drawable */ private void adddrawabletomemorycache(string key, drawable drawable) { if (getdrawablefrommemcache(key) == null && drawable != null) { mmemorycache.put(key, drawable); } } /** * 从内存缓存中获取一个drawable * * @param key * @return */ public drawable getdrawablefrommemcache(string key) { return mmemorycache.get(key); } /** * 从磁盘缓存中获取一个drawable * * @param key * @return */ public drawable getdrawablefromdiskcache(string key) { try { disklrucache.snapshot snapshot = mdiskcache.get(key); if (snapshot != null) { inputstream is = snapshot.getinputstream(0); bitmap bitmap = bitmapfactory.decodestream(is); drawable drawable = disklruutils.bitmap2drawable(bitmap); //从磁盘中读取到之后 加入内存缓存 adddrawabletomemorycache(key, drawable); return drawable; } } catch (ioexception e) { e.printstacktrace(); } return null; } /** * 添加bitmap到磁盘缓存 * * @param key * @param value */ private void addbitmaptodiskcache(string key, byte[] value) { outputstream out = null; try { disklrucache.editor editor = mdiskcache.edit(key); if (editor != null) { out = editor.newoutputstream(0); if (value != null && value.length > 0) { out.write(value); out.flush(); editor.commit(); } else { editor.abort(); } } mdiskcache.flush(); } catch (ioexception e) { e.printstacktrace(); } finally { disklruutils.closequietly(out); } } private drawable download(string imageurl) { httpurlconnection urlconnection = null; bytearrayoutputstream bos = null; inputstream ins = null; try { final url url = new url(imageurl); urlconnection = (httpurlconnection) url.openconnection(); ins = urlconnection.getinputstream(); bos = new bytearrayoutputstream(); int b; while ((b = ins.read()) != -1) { bos.write(b); } bos.flush(); byte[] bytes = bos.tobytearray(); bitmap bitmap = disklruutils.bytes2bitmap(bytes); string key = disklruutils.hashkeyfordisk(imageurl); drawable drawable = disklruutils.bitmap2drawable(bitmap); //加入内存缓存 // adddrawabletomemorycache(key, drawable); //加入磁盘缓存 addbitmaptodiskcache(key, bytes); return drawable; } catch (ioexception e) { e.printstacktrace(); } finally { if (urlconnection != null) { urlconnection.disconnect(); } disklruutils.closequietly(bos); disklruutils.closequietly(ins); } return null; } /** * 从缓存中移除 * * @param key */ public void removecache(string key) { removecachefrommemory(key); removecachefromdisk(key); } /** * 从内存缓存中移除 * * @param key */ public void removecachefrommemory(string key) { mmemorycache.remove(key); } /** * 从磁盘缓存中移除 * * @param key */ public void removecachefromdisk(string key) { try { mdiskcache.remove(key); } catch (exception e) { } } /** * 磁盘缓存大小 * * @return */ public long diskcachesize() { return mdiskcache.size(); } /** * 内存缓存大小 * * @return */ public long memorycachesize() { return mmemorycache.size(); } /** * 关闭磁盘缓存 */ public void closediskcache() { try { mdiskcache.close(); } catch (exception e) { } } /** * 清理缓存 */ public void cleancache() { cleanmemoryccache(); cleandiskcache(); } /** * 清理磁盘缓存 */ public void cleandiskcache() { try { mdiskcache.delete(); } catch (exception e) { } } /** * 清理内存缓存 */ public void cleanmemoryccache() { mmemorycache.evictall(); } }
disklruutils.java
final class disklruutils { /** * 关闭输入输出流 */ public static void closequietly(/*auto*/closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (runtimeexception rethrown) { throw rethrown; } catch (exception ignored) { } } } /** * 获取versioncode */ 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; } 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; } public 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(); } /** * bitmap → bytes */ public static byte[] bitmap2bytes(bitmap bm) { if (bm == null) { return null; } bytearrayoutputstream baos = new bytearrayoutputstream(); bm.compress(bitmap.compressformat.png, 100, baos); return baos.tobytearray(); } /** * bytes → bitmap */ public static bitmap bytes2bitmap(byte[] bytes) { return bitmapfactory.decodebytearray(bytes, 0, bytes.length); } /** * drawable → bitmap */ public static bitmap drawable2bitmap(drawable drawable) { if (drawable == null) { return null; } // 取 drawable 的长宽 int w = drawable.getintrinsicwidth(); int h = drawable.getintrinsicheight(); // 取 drawable 的颜色格式 bitmap.config config = drawable.getopacity() != pixelformat.opaque ? bitmap.config.argb_8888 : bitmap.config.rgb_565; // 建立对应 bitmap bitmap bitmap = bitmap.createbitmap(w, h, config); // 建立对应 bitmap 的画布 canvas canvas = new canvas(bitmap); drawable.setbounds(0, 0, w, h); // 把 drawable 内容画到画布中 drawable.draw(canvas); return bitmap; } /* * bitmap → drawable */ public static drawable bitmap2drawable(bitmap bm) { if (bm == null) { return null; } bitmapdrawable bd = new bitmapdrawable(bm); bd.settargetdensity(bm.getdensity()); return new bitmapdrawable(bm); } }
以上就是基于lru图片缓存简单实现,希望对大家的学习有所帮助,也希望大家多多支持。