【第五篇】Volley代码修改之图片二级缓存以及相关源码阅读(重写ImageLoader.ImageCache)
前面http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html

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);
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();
}
}
/**
* 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();
}
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
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();
}
}
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)的更多相关文章
- Volley 图片加载相关源码解析
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47721631: 本文出自:[张鸿洋的博客] 一 概述 最近在完善图片加载方面的 ...
- 【原】AFNetworking源码阅读(五)
[原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...
- 【原】SDWebImage源码阅读(五)
[原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...
- 【安卓本卓】Android系统源码篇之(一)源码获取、源码目录结构及源码阅读工具简介
前言 古人常说,“熟读唐诗三百首,不会作诗也会吟”,说明了大量阅读诗歌名篇对学习作诗有非常大的帮助.做开发也一样,Android源码是全世界最优秀的Android工程师编写的代码,也是A ...
- Redis源码阅读(五)集群-故障迁移(上)
Redis源码阅读(五)集群-故障迁移(上) 故障迁移是集群非常重要的功能:直白的说就是在集群中部分节点失效时,能将失效节点负责的键值对迁移到其他节点上,从而保证整个集群系统在部分节点失效后没有丢失数 ...
- Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析
Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...
- Kubernetes 学习(九)Kubernetes 源码阅读之正式篇------核心组件之 Scheduler
0. 前言 继续上一篇博客阅读 Kubernetes 源码,参照<k8s 源码阅读>首先学习 Kubernetes 的一些核心组件,首先是 kube-scheduler 本文严重参考原文: ...
- Scala 深入浅出实战经典 第48讲:Scala类型约束代码实战及其在Spark中的应用源码解析
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
- wpf 模拟3D效果(和手机浏览图片效果相似)(附源码)
原文 wpf 模拟3D效果(和手机浏览图片效果相似)(附源码) pf的3D是一个很有意思的东西,类似于ps的效果,类似于电影动画的效果,因为动画的效果,(对于3D基础的摄像机,光源,之类不介绍,对于依 ...
随机推荐
- document.referrer之隐藏来源
document.referrer document.referrer是用来获取跳转链接的来源,正规的解释是:referrer 属性可返回载入当前文档的文档的 URL. 实际中使用在广告相关业务中较多 ...
- 学习web之路
一些文章是转接过来,有些是自己原创的.希望大家喜欢,By:xiaohaimian' q:963892669
- Struts1和Struts2的区别和对比
Struts1和Struts2的区别和对比: Action 类: • Struts1要求Action类继承一个抽象基类.Struts1的一个普遍问题是使用抽象类编程而不是接口. • Struts 2 ...
- CoreJavaE10V1P3.10 第3章 Java的基本编程结构-3.10 数组(Arrays)
数组是存储同一类型数据的数据结构 数组的声明与初始化 int[] a; int a[]; int[] a = new int[100]; int[] a = new int[100]; for (in ...
- Java中修饰符
下面这张图应该大家都见过,根据图表来记忆最好理解 范围 private friendly(默认) protected public 当前类 √ √ √ √ 当前包中的类 √ √ √ 当前包中的类, ...
- minSdkVersion与targetSdkVersion
targetSdkVersion是Android提供向前兼容的主要依据,在应用的targetSdkVersion没有更新之前,系统不会应用最新的行为变化 比如设置了app的targetSdkVersi ...
- 校门外的树 OpenJudge 1.6.06
06:校门外的树 总时间限制: 1000ms 内存限制: 65536kB 描述 某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米.我们可以把马路看成一个数轴,马路的一端在数轴0 ...
- chrome ipc 网摘
http://my.oschina.net/myspaceNUAA/blog/81632?p=1 http://lihuan623.blog.163.com/blog/static/138595845 ...
- json的细节
之前一直纳闷为什么在js里直接写的json数据可以不用eval()直接解析,而后台传入ajax的json数据需要eval()一下才能解析 原来是我没搞清楚json格式字符串跟json对象 var te ...
- CodeForces 707B Bakery
枚举. 枚举每一条边,如果发现边的一端$f[u]=1$,另一端$f[v]=0$,那么更新答案,取最小值就好了. #pragma comment(linker, "/STACK:1024000 ...