【第五篇】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基础的摄像机,光源,之类不介绍,对于依 ...
随机推荐
- CSS3的线性渐变(linear-gradient)
CSS3渐变(gradient)可分为线性渐变(linear-gradient)和径向渐变(radial-gradient).今天给大家说一说线性渐变. 以webkit内核浏览器为例, 语法: div ...
- C#开发学习——常用的正则表达式
对于想学习正则表达式的童鞋,一些基础的语法啥的,可以参考 http://www.cnblogs.com/China3S/archive/2013/11/30/3451971.html 下边是一些我们常 ...
- ruby web性能响应时间
可以统计单个web页面加载时间. require 'watir-webdriver' require 'watir-webdriver-performance' b = Watir::Browser. ...
- PHP中file_exists与is_file、is_dir的区别,以及执行效率的比较 转自#冰雪傲骨#
PHP中file_exists与is_file.is_dir的区别,以及执行效率的比较 判断文件是否存在,有2个常用的PHP函数:is_file 和 file_exists, 判断文件夹是否存在, ...
- CSS控制文本在一行内显示,若有多余字符则使用省略号表示
强制文本在一行内显示,多余字符使用省略号 text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
- 认识div在排版中的作用
在网页制作过程过中,可以把一些独立的逻辑部分划分出来,放在一个<div>标签中,这个<div>标签的作用就相当于一个容器. 语法: <div>-</div&g ...
- buildroot 重新编译 package
/************************************************************************* * buildroot 重新编译 package ...
- JNDI中 java:comp/env 的理解
J2EE 上下文环境变量前缀,一般有如下几种:java:/comp/env/jdbcjava:/comp/env/urljava:/comp/env/mailjava:/comp/env/jms在部署 ...
- wpf使用devexpress RibbonControl实现导航窗体
实现如下效果 <Window xmlns:dxr="http://schemas.devexpress.com/winfx/2008/xaml/ribbon" ...
- android app安全问题设置
1.应用签名未校验风险:检测 App 程序启动时是否校验签名证书. 2.应用数据任意备份风险 Android 2.1 以上的系统可为 App 提供应用程序数据的备份和恢复功能,该 由 AndroidM ...