前面http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html

有讲到使用LRU来处理缓存的,但是只是处理内存里面的缓存,没进行文件缓存和处理,那么如何实现Volley在本地的缓存呢
一般硬盘缓存使用com.jakewharton.disklrucache.DiskLruCache这个Lru缓存,具体代码在
重写ImageCache实现图片二级缓存L2LRUImageCache.java
public class L2LRUImageCache implements ImageLoader.ImageCache{
LruCache<String, Bitmap> lruCache;
DiskLruCache diskLruCache;
final int RAM_CACHE_SIZE = 10 * 1024 * 1024;
String DISK_CACHE_DIR = "cache";
//硬盘缓存50M
final long DISK_MAX_SIZE = 50 * 1024 * 1024;
String cacheFullPath;
public L2LRUImageCache(Context context) {
//此处是标准的Lru缓存写法
this.lruCache = new LruCache<String, Bitmap>(RAM_CACHE_SIZE) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
}; //如果sd卡存在,创建缓存目录
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
File cacheDir = context.getExternalFilesDir(DISK_CACHE_DIR);
cacheFullPath=cacheDir.getAbsolutePath();
if(!cacheDir.exists())
{
cacheDir.mkdir();
}
try { diskLruCache = DiskLruCache.open(cacheDir, 1, 1, DISK_MAX_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public Bitmap getBitmap(String url) {
String key=generateKey(url);
//从内存缓存读取
Bitmap bmp = lruCache.get(key);
//内存缓存中没有,读取本地文件缓存
if (bmp == null) {
PLog.d(this,"内存读图失败,从磁盘读"+url);
bmp = getBitmapFromDiskLruCache(key);
//从磁盘读出后,放入内存
if(bmp!=null)
{
lruCache.put(key,bmp);
}
}
//如果文件里面也没有这个文件,就有必要采取网络下载方式进行下载
if(bmp==null)
{
PLog.d(this,"从缓存读图失败,去下载"+url);
}
return bmp;
} //文件缓存到内存缓存和本地缓存
@Override
public void putBitmap(String url, Bitmap bitmap) {
//文件缓存中的key为md5后的url链接
String key=generateKey(url);
lruCache.put(key, bitmap);
putBitmapToDiskLruCache(key,bitmap);
} //清理内存缓存,以及缓存目录里面的文件
@Override
public void clear() {
lruCache.evictAll();
FileUtils.deleteFile(cacheFullPath);
} //图片放入文件缓存中区
private void putBitmapToDiskLruCache(String key, Bitmap bitmap) {
if(diskLruCache!=null) {
try {
DiskLruCache.Editor editor = diskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
editor.commit();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//获取图片本地缓存
private Bitmap getBitmapFromDiskLruCache(String key) {
if(diskLruCache!=null) {
try {
DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
if (snapshot != null) {
InputStream inputStream = snapshot.getInputStream(0);
if (inputStream != null) {
Bitmap bmp = BitmapFactory.decodeStream(inputStream);
inputStream.close();
return bmp;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 因为DiskLruCache对key有限制,只能是[a-z0-9_-]{1,64},所以用md5生成key
* @param url
* @return
*/
private String generateKey(String url)
{
return MD5Utils.getMD532(url);
}
}

接下来考虑Volley正常加载图片时怎么加载的,如下:

 ImageListener listener = ImageLoader.getImageListener(ivImage,
R.drawable.ic_launcher, R.drawable.ic_launcher);
imageLoader.get(string, listener);
那么调用L2LRUImageCache 进行二级缓存和缓存文件读取并加载到组件上面的逻辑也就在重写imageLoader的逻辑里面:
原生的volley里面 imageLoader.get(string, listener);进入如下代码
 public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
// only fulfill requests that were initiated from the main thread.
throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// The bitmap did not exist in the cache, fetch it!
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Request request,Bitmap response,boolean isFromCache) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(Request request,VolleyError error) {
onGetImageError(cacheKey, error);
}
});
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}

其中throwIfNotOnMainThread为检查是否在主线程,代码如下:

private void throwIfNotOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
}
}

可见Imageloader加载图片必须运行在主线程。

然后getCacheKey获取key信息:如下解释是为1级缓存创建缓存key,创建方法如代码所述:

 /**
* Creates a cache key for use with the L1 cache.
* @param url The URL of the request.
* @param maxWidth The max-width of the output.
* @param maxHeight The max-height of the output.
*/
private static String getCacheKey(String url, int maxWidth, int maxHeight) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append(url).toString();
}
}
Bitmap cachedBitmap = mCache.getBitmap(cacheKey)然后就是去1级缓存里面去都去缓存内容:
如下ImageCache是个接口,推荐使用LRUCache来实现1级缓存:
 /**
* Simple cache adapter interface. If provided to the ImageLoader, it
* will be used as an L1 cache before dispatch to Volley. Implementations
* must not block. Implementation with an LruCache is recommended.
*/
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
public void clear();
}
如果1级缓存不为null,就cachedBitmap回调给图片加载的response;
如果1级缓存为null的话,就去加载默认图片:
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
其中imageListener携带加载失败和默认图片的设置信息.
 
后面代码就是如果这个图片url没有请求过就会去请求,通过网络的形式从服务器端拉去图片信息,并对成功失败进行处理。
 
为了实现加载网络图片二级缓存和从从二级缓存中读取,必须重现原来的ImageLoader类,对缓存读取,加载进行重写:
AsyncImageLoader.java
public class AsyncImageLoader extends ImageLoader{
/**
* 在取的请求,可能没取到
*/
ConcurrentHashMap<String, ReadImageRequest> readImageRequestConcurrentHashMap = new ConcurrentHashMap<>();
// 读数据线程池,限制两个线程
private ExecutorService readExecutorService = new ThreadPoolExecutor(0, 2, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
//UI线程的Handler
Handler mainHandler;
private static AsyncImageLoader instance;
//独立请求列队
private static RequestQueue requestQueue;
private AsyncImageLoader(RequestQueue queue, ImageCache imageCache) {
super(queue, imageCache);
mainHandler = new Handler(Looper.getMainLooper());
}
/**
* 返回默认的ImageLoader,使用两级缓存,单独的请求队列
* @return
*/
public static AsyncImageLoader getDefaultImageLoader()
{
if(instance==null) {
requestQueue=Volley.newRequestQueue(PApplication.getInstance());
requestQueue.start();
instance = new AsyncImageLoader(requestQueue, new L2LRUImageCache(PApplication.getInstance()));
}
return instance;
}
/**
* 销毁,停止所有未处理请求
*/
public void destory()
{
requestQueue.stop();
instance=null;
}
@Override
public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) {
// TODO Auto-generated method stub
throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
ImageContainer imageContainer = new ImageContainer(null, requestUrl, cacheKey, imageListener);
ReadImageRequest readImageRequest =readImageRequestConcurrentHashMap.get(cacheKey);
if(readImageRequest ==null){
readImageRequest =new ReadImageRequest(imageContainer, cacheKey);
readImageRequestConcurrentHashMap.put(cacheKey, readImageRequest);
//去读缓存,读不到会自动转到请求网络
readExecutorService.execute(new ReadCache(imageContainer, cacheKey,maxWidth,maxHeight));
}else{
//如果该请求已经存在,添加ImageContainer,不再发请求
readImageRequest.addContainer(imageContainer);
}
return imageContainer;
}
private void throwIfNotOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ImageLoader must be invoked from the main thread.");
}
}
/**
* 创建缓存的key
*
* @param url
* @param maxWidth
* @param maxHeight
* @return
*/
private static String getCacheKey(String url, int maxWidth, int maxHeight) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth).append("#H").append(maxHeight)
.append(url).toString();
}
/**
* 读取缓存,读不到会转发给网络
*/
class ReadCache implements Runnable {
ImageContainer container;
String cacheKey;
int maxWidth, maxHeight;
public ReadCache(ImageContainer container, String cacheKey,int maxWidth,int maxHeight) {
this.container = container;
this.cacheKey = cacheKey;
this.maxWidth=maxWidth;
this.maxHeight=maxHeight;
}
@Override
public void run() {
// TODO Auto-generated method stub
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
if (cacheRequest != null) {
cacheRequest.setCacheBitmap(cachedBitmap);
readSuccess(cacheKey);
}
} else {
// 读不到缓存,去下载
mainHandler.post(new GetImageUseNetWork(container,cacheKey,maxWidth,maxHeight));
}
}
}
/**
* 读取缓存或下载图片成功,分发结果
* @param cacheKey
*/
private void readSuccess(String cacheKey)
{
ReadImageRequest successedCacheRequest = readImageRequestConcurrentHashMap.remove(cacheKey);
if(successedCacheRequest!=null) {
successedCacheRequest.deliver();
}
}
private void readFailure(String cacheKey,VolleyError error) {
ReadImageRequest successedCacheRequest = readImageRequestConcurrentHashMap.remove(cacheKey);
if(successedCacheRequest!=null) {
successedCacheRequest.deliverError(error);
}
}
class GetImageUseNetWork implements Runnable {
ImageContainer imageContainer;
String cacheKey;
int maxWidth,maxHeight;
public GetImageUseNetWork(ImageContainer imageContainer, String cacheKey,int maxWidth,int maxHeight) {
this.imageContainer = imageContainer;
this.cacheKey = cacheKey;
this.maxWidth=maxWidth;
this.maxHeight=maxHeight;
}
@Override
public void run() {
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request<?> newRequest = new ImageRequest(imageContainer.getRequestUrl(), new Response.Listener<Bitmap>() {
@Override
public void onResponse(Request request,Bitmap response,boolean isFromCache) {
PLog.d(this,"onResponse");
Bitmap bmpCompressed=ImageUtil.scaleBitmap(response, maxWidth, maxHeight);
ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
if (cacheRequest != null) {
cacheRequest.setCacheBitmap(bmpCompressed);
//放到缓存里
mCache.putBitmap(cacheKey, bmpCompressed);
readSuccess(cacheKey);
}
}
}, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(Request request,VolleyError error) {
PLog.d(this,"onErrorResponse");
onGetImageError(cacheKey, error);
readFailure(cacheKey,error);
}
});
mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
mRequestQueue.add(newRequest);
}
}
/**
* 清除缓存
*/
public void clearCache()
{
mCache.clear();
}
}
1,可以设置线程池的大小,这里设置为2条线程;
2,getDefaultImageLoader里面调用上面的图片缓存的代码L2LRUImageCache;
3,重点的读取和缓存还是在get方法里面。
 readExecutorService.execute(new ReadCache(imageContainer, cacheKey,maxWidth,maxHeight)); 

然后看线程池里面读取缓存的逻辑:

            // TODO Auto-generated method stub
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
if (cacheRequest != null) {
cacheRequest.setCacheBitmap(cachedBitmap);
readSuccess(cacheKey);
}
} else {
// 读不到缓存,去下载
mainHandler.post(new GetImageUseNetWork(container,cacheKey,maxWidth,maxHeight));
}

mCache.getBitmap(cacheKey)是从一级缓存中区读取bitmap,如果一级缓存里面没有,就去下载,调用下面逻辑,拉取下来后,对图片进行裁剪,并将图片放入缓存里面去,而mCache.putBitmap(cacheKey,bmpCompressed);就是调用L2LRUImageCache 的putbitmap放来将缓存内容放入文件和内存中去。

 BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request<?> newRequest = new ImageRequest(imageContainer.getRequestUrl(), new Response.Listener<Bitmap>() {
@Override
public void onResponse(Request request,Bitmap response,boolean isFromCache) {
PLog.d(this,"onResponse");
Bitmap bmpCompressed=ImageUtil.scaleBitmap(response, maxWidth, maxHeight);
ReadImageRequest cacheRequest = readImageRequestConcurrentHashMap.get(cacheKey);
if (cacheRequest != null) {
cacheRequest.setCacheBitmap(bmpCompressed);
//放到缓存里
mCache.putBitmap(cacheKey, bmpCompressed);
readSuccess(cacheKey);
}
}
}, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
@Override
public void onErrorResponse(Request request,VolleyError error) {
PLog.d(this,"onErrorResponse");
onGetImageError(cacheKey, error);
readFailure(cacheKey,error);
}
});
mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
mRequestQueue.add(newRequest);

代码摘自:https://github.com/pocketdigi/PLib/tree/androidstudio/src/main/java/com/pocketdigi/plib/volley,可以参考优化volley对图片的二级缓存

【第五篇】Volley代码修改之图片二级缓存以及相关源码阅读(重写ImageLoader.ImageCache)的更多相关文章

  1. Volley 图片加载相关源码解析

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47721631: 本文出自:[张鸿洋的博客] 一 概述 最近在完善图片加载方面的 ...

  2. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  3. 【原】SDWebImage源码阅读(五)

    [原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...

  4. 【安卓本卓】Android系统源码篇之(一)源码获取、源码目录结构及源码阅读工具简介

    前言        古人常说,“熟读唐诗三百首,不会作诗也会吟”,说明了大量阅读诗歌名篇对学习作诗有非常大的帮助.做开发也一样,Android源码是全世界最优秀的Android工程师编写的代码,也是A ...

  5. Redis源码阅读(五)集群-故障迁移(上)

    Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...

  6. Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析

    Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...

  7. Kubernetes 学习(九)Kubernetes 源码阅读之正式篇------核心组件之 Scheduler

    0. 前言 继续上一篇博客阅读 Kubernetes 源码,参照<k8s 源码阅读>首先学习 Kubernetes 的一些核心组件,首先是 kube-scheduler 本文严重参考原文: ...

  8. Scala 深入浅出实战经典 第48讲:Scala类型约束代码实战及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  9. wpf 模拟3D效果(和手机浏览图片效果相似)(附源码)

    原文 wpf 模拟3D效果(和手机浏览图片效果相似)(附源码) pf的3D是一个很有意思的东西,类似于ps的效果,类似于电影动画的效果,因为动画的效果,(对于3D基础的摄像机,光源,之类不介绍,对于依 ...

随机推荐

  1. C#中的StringBuilder

    1. 使用StringBuilder的好处 由于string对象是不可更改的,我们每次使用string类的方法时,都会在内存中重新创建一个新的string对象,这时候就要为该对象分配内存空间了.如果在 ...

  2. MS Word 目录排版

    昨天整理一份把网页的内容复制粘贴到Word里的文件,碰到了这样一个问题: 网页上面也会有一级标题,二级标题,三级标题等.当我们在写博客的时候,也会去使用这些.这也就导致复制过来之后,直接生成的目录很乱 ...

  3. 中国IT武林大会暨中国首席技术官2016年度人物颁奖盛典概况

    在"大众创业.万众创新"的互联网时代,深入实施创新驱动发展战略,建设创新型国家,必须大力推动"互联网+科技"的发展.由中国首席技术官联盟.CCTV证券频道< ...

  4. Linux学习-文件和目录管理 

    Linux文件和目录管理  文件系统架构  1.Linux文件系统具有层级性     1)文件或者目录起始于根目录"/"成为树状结构    2)最顶层由/开始   2 ...

  5. C++类继承中,基类/当前对象属性/当前对象的构造顺序

    [1]中提到,规范的派生类构造函数三个要点: 首先创建基类对象 应通过成员初始化列表,创建基类对象 应该初始化本派生类新增的成员变量 那在构造派生类实例的过程中,其基类(以及多继承的时候多个基类)/当 ...

  6. DUIlib使用Fastreport--自定义的数据

    报表根据数据源的可以分为拉模式和推模式,拉模式就是在报表中添加数据源组件从数据库中拉取数据,我们上篇报表的简单使用就是拉模式.而推模式就是在程序中构造数据托给报表显示.这篇我们这要说的是推模式. 在程 ...

  7. POJ 3507 Judging Olympia

    小技巧 判断 全部为零 用sign和所有元素依次取或 排除最大项和最小项 直接排序后取中间的四个元素 http://poj.org/problem?id=3507 1 #include <ios ...

  8. HDU 4403 A very hard Aoshu problem

    暴力$dfs$. 先看数据范围,字符串最长只有$15$,也就是说枚举每个字符后面是否放置“$+$”号的复杂度为${2^{15}}$. 每次枚举到一种情况,看哪些位置能放“$=$”号,每个位置都试一下, ...

  9. 第一百一十八节,JavaScript,动态加载脚本和样式

    JavaScript,动态加载脚本和样式 一动态脚本 当网站需求变大,脚本的需求也逐步变大.我们就不得不引入太多的JS脚本而降低了整站的性能,所以就出现了动态脚本的概念,在适时的时候加载相应的脚本. ...

  10. Mysql的转义字符

    Mysql的转义字符是"\",即反斜杠,在INSERT语句中,如果被插入的文本中包含反斜杠,那么反斜杠会被吃掉.例如: INSERT INTO tb (id,json) VALUE ...