Android 缓存策略LruCache和DiskLruCache学习
2017-07-08 00:12
393 查看
Android 缓存策略LruCache和DiskLruCache学习
作者 英勇青铜5 关注
2016.09.05 19:18* 字数 1521 阅读 883评论
7喜欢 21
学习资料:
Android 开发艺术探索
其实就是完完全全摘抄,读书笔记 : )
LruCache和
DiskLruCache是采用了
LRU(Least Recently Used)近期最少使用算法的两种缓存。
LruCache内存缓存,
DiskLruCache存储设备缓存
1.LruCache 内存缓存
LruCache是一个泛型类,内部是一个
LinkedHashMap以强引用的方式存储缓存对象,提供了
get和
put方法进行对缓存对象的操作。当缓存满时,移除近期最少使用的缓存对象,添加新的缓存对象
强引用:直接的对象引用
软引用:当一个对象只有软引用存在时,系统内存不足时,会被gc回收
弱引用:当一个对象只有弱引用存在时,该对象会随时被gc回收
LruCache是线程安全的
public class LruCahe<K,V>{ private final LinkedHashMap<K,V> map; ... }
使用final是所谓的安全发布(safe publication)的一种方式
至于
final修饰后的变量为啥是线程安全的,网上也搜索了一下,看了下,暂时并不理解,先记住,随着以后的学习深入再继续了解
: )
关于java中final关键字与线程安全性
Java 并发编程(二)对象的不变性和安全的发布对象
以后注意
final的使用
LruCache典型的初始化,重写
sizeOf()方法
int MaxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);// kB int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String,Bitmap>(cacheSize){ @Override protected int sizeOf(String key,Bitmap bitmap){ //bitmap.getByteCount() = bitmap.getRowBytes() * bitmap.getHeight(); return bitmap.getRowBytes() * bitmap.getHeight() / 1024;// KB } }
计算缓存对象大小的单位和总容量的单位要保持一致
一些特殊时候,还需要重写
entryRemoved()方法。
LruCache移除旧缓存对象时会调用这个方法,根据需求可以在这个方法中完成一些资源回收工作
获取一个缓存对象,
mMemoryCache.get(key)
添加一个缓存对象,
mMemoryCache.put(key,bitmap)
2.DiskLruCache 磁盘缓存
DiskLruCache并不是
Android SDK中的类。不明白为啥,官方只进行推荐,为何不加入
SDK中
2.1创建
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;// 50MB File diskCacheDir = new File(mContext,"bitmap"); if(!diskCacheDir.exists()){ diskCacheDir.mkdirs(); } mDiskLruCache = DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
diskCacheDir缓存文件夹,具体指
sdcard/Android/data/package_name/cache
1应用版本号,一般写为1
1单个节点所对应的数据的个数,一般写1
DISK_CACHE_SIZE缓存大小
2.2添加
DishLruCache缓存添加的操作通过
Eidtor完成,
Editor为一个缓存对象的编辑对象。
首先需要获取图片的
url所对应的
key,根据
key利用
edit()来获取
Editor对象。若此时,这个缓存正在被编辑,
edit()会返回
null。
DiskLruCache不允许同时编辑同一个缓存对象。之所以把
url转换成
key,因为图片的
url中可能存在特殊字符,会影响使用,一般将
url的
md5值作为
key
private String hashKeyFromUrl(String url){ String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(url.getBytes()); cacheKey = byteToHexString(mDigest.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(); }
将
url转成
key,利用这
key值获取
Editor对象。若这个
key的
Editor对象不存在,
edit()方法就创建一个新的出来。通过
Editor对象可以获取一个输出流对象。
DiskLruCache的
open()方法中,一个节点只能有一个数据,
edit.newOutputStream(DISK_CACHE_INDEX)参数设置为
0
String key = hashKeyFromUrl(url); DiskLruCache.Editor editor =mDiskLruCache.edit(key); if (editor != null){ OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX); }
有了这个文件输出流,从网络加载一个图片后,通过这个
OutputStream outputStream写入文件系统
private boolean downLoadUrlToStream(String urlString, OutputStream outputStream) { HttpURLConnection urlConnection = null; BufferedOutputStream bos = null; BufferedInputStream bis = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); bis = new BufferedInputStream(urlConnection.getInputStream(),8 * 1024); bos = new BufferedOutputStream(outputStream,8 * 1024); int b ; while((b = bis.read())!= -1){ bos.write(b); } return true; } catch (IOException e) { e.printStackTrace(); }finally { if (urlConnection != null){ urlConnection.disconnect(); } closeIn(bis) ; closeOut(bos); } return false; }
上面的代码并没有将图片写入文件系统,还需要通过
Editor.commit()提交写入操作,若写入失败,调用
abort()方法,进行回退整个操作
if (downLoadUrlToStream(url,outputStream)){ editor.commit();//提交 }else { editor.abort();//重复操作 }
这时,图片已经正确写入文件系统,接下来的图片获取就不需要请求网络
2.3 缓存查找
查找过程,也需要将url转换为
key,然后通过
DiskLruCache的
get方法得到一个
Snapshot对象,再通过
Snapshot对象可得到缓存的文件输入流,有了输入流就可以得到
Bitmap对象
为了避免
oom,会使用
ImageResizer进行缩放。若直接对
FileInputStream进行操作,缩放会出现问题。
FileInputStream是有序的文件流,两次
decodeStream调用会影响文件流的位置属性。可以通过文件流得到其所对应的文件描述符,利用
BitmapFactory.decodeFileDescriptor()方法进行缩放
Bitmap bitmap = null; String key = hashKeyFromUrl(url); DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); if (snapshot != null){ FileInputStream fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); FileDescriptor fileDescriptor = fis.getFD(); bitmap = imageResizer.decodeBitmapFromFileDescriptor(fileDescriptor,targetWidth,targetHeight); if (bitmap != null){ addBitmapToMemoryCache(key,bitmap); } }
在查找得到
Bitmap后,把
key,bitmap添加到内存缓存中
3.ImageLoader的实现
主要思路:拿到图片请求地址
url后,先把
url变作对应的
key;
利用
key在内存缓存中查找,查找到了就进行加载显示图片;若没有查到就进行
3
在磁盘缓存中查找,在若查到了,就加载到内存缓存,后加载显示图片;若没有查找到,进行
4
进行网络求,拿到数据图片,把图片写进磁盘缓存成功后,再加入到内存缓存中,并根据实际所需显示大小进行合理缩放显示
类比较长,查看顺序:构造方法->bindBitmap(),之后顺着方法内的方法,结合4个步骤,并不难理解
public class ImageLoader { private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;// 50MB private Context mContext; private LruCache<String, Bitmap> mMemoryCache; private DiskLruCache mDiskLruCache; private boolean mIsDiskLruCacheCreated = false;//用来标记mDiskLruCache是否创建成功 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT+ 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final long KEEP_ALIVE = 10; private final int DISK_CACHE_INDEX = 0; private static final int MESSAGE_POST_RESULT = 101; private ImageResizer imageResizer = new ImageResizer(); private static final ThreadFactory mThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { return new Thread(r,"ImageLoader#"+mCount.getAndIncrement()); } }; /** * 创建线程池 */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(),mThreadFactory ); /** * 创建Handler */ private Handler mHandler = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == MESSAGE_POST_RESULT){ LoaderResult loadResult = (LoaderResult) msg.obj; ImageView iv = loadResult.iv; String url = (String) iv.getTag(); if (url.equals(loadResult.uri)){//防止加载列表形式时,滑动复用的错位 iv.setImageBitmap(loadResult.bitmap); } } } }; private ImageLoader(Context mContext) { this.mContext = mContext.getApplicationContext(); init(); } /** * 创建一个ImageLoader */ public static ImageLoader build(Context context) { return new ImageLoader(context); } /** * 初始化 * LruCache<String,Bitmap> mMemoryCache * DiskLruCache mDiskLruCache */ private void init() { // LruCache<String,Bitmap> mMemoryCache int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { //return bitmap.getRowBytes() * bitmap.getHeight() / 1024; return bitmap.getByteCount() / 1024; } }; // DiskLruCache mDiskLruCache File diskCacheDir = getDiskCacheDir(mContext, "bitmap"); if (!diskCacheDir.exists()) { diskCacheDir.mkdirs(); } if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) { try { mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE); mIsDiskLruCacheCreated = true; } catch (IOException e) { e.printStackTrace(); } } } /** * 加载原始大小的图 */ public void bindBitmap(String uri,ImageView iv){ bindBitmap(uri,iv,0,0); } /** * 异步加载网络图片 指定大小 */ public void bindBitmap(final String uri, final ImageView iv, final int targetWidth, final int targetHeight){ iv.setTag(uri); Bitmap bitmap = loadBitmapFormMemCache(uri); if (bitmap != null){ iv.setImageBitmap(bitmap); return; } Runnable loadBitmapTask = new Runnable() { @Override public void run() { Bitmap bitmap = loadBitmap(uri,targetWidth,targetHeight); if (bitmap != null){ LoaderResult result = new LoaderResult(iv,uri,bitmap); Message message = mHandler.obtainMessage(); message.obj = result; message.what = MESSAGE_POST_RESULT; mHandler.sendMessage(message); } } }; THREAD_POOL_EXECUTOR.execute(loadBitmapTask); } /** * 同步加载网络图片 */ private Bitmap loadBitmap(String url, int targetWidth, int targetHeight) { Bitmap bitmap = loadBitmapFormMemCache(url); if (bitmap != null) { return bitmap; } try { bitmap = loadBitmapFromDiskCache(url, targetWidth, targetHeight); if (bitmap != null) { return bitmap; } bitmap = loadBitmapFromHttp(url, targetWidth, targetHeight); } catch (IOException e) { e.printStackTrace(); } if (bitmap == null && !mIsDiskLruCacheCreated) {//缓存文件夹创建失败 bitmap = downLoadFromUrl(url); } return bitmap; } /** * 向缓存中添加Bitmap */ private void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 通过key拿到bitmap */ private Bitmap getBitmapFromMemoryCache(String key) { return mMemoryCache.get(key); } private Bitmap loadBitmapFormMemCache(String url) { final String key = hashKeyFromUrl(url); return getBitmapFromMemoryCache(key); } /** * 从网络进行请求 */ private Bitmap loadBitmapFromHttp(String url, int targetWidth, int targetHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { throw new RuntimeException("UI 线程不能进行网络访问"); } if (mDiskLruCache == null) { return null; } String key = hashKeyFromUrl(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX); if (downLoadUrlToStream(url, outputStream)) { editor.commit(); } else { editor.abort();//重复操作 } mDiskLruCache.flush();//刷新 } return loadBitmapFromDiskCache(url, targetWidth, targetHeight); } /** * 从硬盘缓存中读取Bitmap */ private Bitmap loadBitmapFromDiskCache(String url, int targetWidth, int targetHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { throw new RuntimeException("硬盘读取Bitmap在UI线程,UI 线程不进行耗时操作"); } if (mDiskLruCache == null) { return null; } Bitmap bitmap = null; String key = hashKeyFromUrl(url); DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); if (snapshot != null) { FileInputStream fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); FileDescriptor fileDescriptor = fis.getFD(); bitmap = imageResizer.decodeBitmapFromFileDescriptor(fileDescriptor, targetWidth, targetHeight); if (bitmap != null) { addBitmapToMemoryCache(key, bitmap); } } return bitmap; } /** * 将数据请求到流之中 */ private boolean downLoadUrlToStream(String urlString, OutputStream outputStream) { HttpURLConnection urlConnection = null; BufferedOutputStream bos = null; BufferedInputStream bis = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); bis = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024); bos = new BufferedOutputStream(outputStream, 8 * 1024); int b; while ((b = bis.read()) != -1) { bos.write(b); } return true; } catch (IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } closeIn(bis); closeOut(bos); } return false; } /** * 直接通过网络请求图片 也不做任何的缩放处理 */ private Bitmap downLoadFromUrl(String urlString) { Bitmap bitmap = null; HttpURLConnection urlConnection = null; BufferedInputStream bis = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); bis = new BufferedInputStream(urlConnection.getInputStream()); bitmap = BitmapFactory.decodeStream(bis); } catch (IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } closeIn(bis); } return bitmap; } /** * 得到MD5值key */ private String hashKeyFromUrl(String url) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(url.getBytes()); cacheKey = byteToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(url.hashCode()); } return cacheKey; } /** * 将byte转换成16进制字符串 */ 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(); } /** * 得到缓存文件夹 */ private File getDiskCacheDir(Context mContext, String uniqueName) { //判断储存卡是否可以用 boolean externalStorageAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); final String cachePath; if (externalStorageAvailable) { cachePath = mContext.getExternalCacheDir().getPath();//储存卡 } else { cachePath = mContext.getCacheDir().getPath();//手机自身内存 } return new File(cachePath + File.separator + uniqueName); } /** * 得到可用空间大小 */ private long getUsableSpace(File file) { return file.getUsableSpace(); } /** * 关闭输入流 */ private void closeIn(BufferedInputStream in) { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } finally { in = null; } } } /** * 关闭输输出流 */ private void closeOut(BufferedOutputStream out) { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } finally { out = null; } } } private static class LoaderResult { public ImageView iv ; public String uri; public Bitmap bitmap; public LoaderResult(ImageView iv, String uri, Bitmap bitmap) { this.iv = iv; this.uri = uri; this.bitmap = bitmap; } } }
经过测试,是可以正常加载出图片的,缓存文件也正常,主要是学习过程思路
3.1补充 Closeable接口
在ImageLoader中,代码最后写了
closeIn(),closeOut()方法,完全没必要这样写。在
jdk1.6之后,所有的流都实现了
Closeable接口,可以用这个接口来关闭所有的流
/** * 关闭流 */ private void closeStream(Closeable closeable){ if (closeable != null){ try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } }
在
downLoadFromUrl()和
downLoadUrlToStream()方法中需要关闭流,就可以直接
closeStream(bis),closeStream(bos)来进行关闭流操作
4.最后
这个ImgageLoader还非常简陋,和
Glide和
Picasso根本无法相比。并不会在实际工作开发中使用,还是使用
Glide或者
Picasso。主要是学习基础实现原理,学习了下
Android中缓存的部分知识。之前面试时,被问到过
ImageLoader原理。
最近的学习,感觉不会的东西太多了,得理一理学习的顺序。
注意多锻炼身体。 共勉:)
Android基础学习
© 著作权归作者所有
举报文章
关注英勇青铜5
写了 138621 字,被 633 人关注,获得了 1088 个喜欢
Android 开发 水平:青铜5
如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!
相关文章推荐
- Android中的缓存策略--DiskLruCache
- Android本地缓存DiskLruCache完整详细学习示例
- Android中的缓存策略--DiskLruCache
- 某宅的 Android 学习笔记(四)——用 DiskLruCache 实现本地缓存
- Android本地缓存DiskLruCache完整详细学习示例
- Android的缓存策略:LruCache和DiskLruCache
- Android 实现内存+SD卡 图片缓存策略 (LurCache+DiskLruCache)
- Android的DiskLruCache硬盘缓存技术
- Android缓存之磁盘缓存.对DiskLruCache进行封装便于存取.
- Android缓存分为LruCache 和DiskLruCache两种
- Android DiskLruCache缓存完全解析(网易新闻)
- android 文件缓存 DiskLruCache
- 【DiskLruCache完全解析】Android AdapterView图片硬盘缓存的最佳方案
- Android DiskLruCache磁盘缓存
- Android OkHttp与物理存储介质缓存:DiskLruCache(2)
- Android进阶图片处理之DiskLruCache解析 硬盘缓存方案
- Android 缓存工具DiskLruCache用法
- Android-Universal-Image-Loader 学习笔记(四)图片缓存策略
- Android之本地缓存——LruCache(内存缓存)与DiskLruCache(硬盘缓存)统一框架
- android网络相册(带磁盘缓存DiskLruCache 和内存缓存LruCache)