lru缓存 Android,Android图片框架Picasso LRU缓存详解

Picasso这个图片框架默认实现了内存中的LRU缓存,但是没有默认实现磁盘缓存(关于磁盘缓存的配置可以看我之前写的一篇博客),我在使用Picasso替换原来的xUtils框架的时候发现内存开销要比之前高好多,于是着手分析Picasso的LRU缓存策略,代码比较好读,下面简单的分析一下。

Picasso加载一个图片的流程一般是这样的:

url->检查LRU缓存中有没有对应的bitmap->调用HTTP框架准备下载该图片资源->http框架检查有没有磁盘缓存->http框架访问网络下载数据并进行缓存

这里面的动作主要是由一个叫BitmapHunter的类完成的。

Picasso有一个接口叫Cache,有一个实现叫LruCache,这个实现类里面是用一个LinkedHashMap来进行缓存,key是图片url,value是bitmap,并不是其他框架爱用的WeakReference方案。

这个实现类里面有几个控制内存使用量的成员,如下:

private final int maxSize;//最大堆内存占用,单位字节

private int size;//当前已经缓存到堆内存中所有bitmap所占的字节数

private int putCount;//将bitmap存入LRU缓存的总次数

private int evictionCount;//因为内存不足而将bitmap移出LRU缓存的总次数

private int hitCount;//从LRU缓存中读取bitmap的总次数

private int missCount;//没有从LRU缓存中根据url找到相应的bitmap的总次数

来看一下添加一个bitmap到缓存的代码

@Override public void set(String key, Bitmap bitmap) {

if (key == null || bitmap == null) {

throw new NullPointerException("key == null || bitmap == null");

}

Bitmap previous;

synchronized (this) {//每次只能读写一个bitmap,因为LinkedHashMap是非线程安全的

putCount++;//存bitmap计数器加一

size += Utils.getBitmapBytes(bitmap);//获取一个bitmap所占内存的字节数

previous = map.put(key, bitmap);//将bitmap存入到hashmap中去,以url为key,如果previous为空说明之前没有存储过该url,否则之前存储过

if (previous != null) {//如果之前已经存储过这个url了

size -= Utils.getBitmapBytes(previous);

}

}

trimToSize(maxSize);//看看内存占用是否过大,如果太大的话就从LRU缓存中移出一部分bitmap

}

最重要的方法就是这个trimToSize(),它是用来回收bitmap缓存的,让我们来着重研究一下

private void trimToSize(int maxSize) {

while (true) {//一直执行销毁动作,直到当前占用的内存字节数小于规定的最大占用量

String key;

Bitmap value;

synchronized (this) {//由于LinkedHashMap线程非安全,并且只有逐个释放才能准确比较剩余LRU大小,所以要同步执行

if (size < 0 || (map.isEmpty() && size != 0)) {

throw new IllegalStateException(

getClass().getName() + ".sizeOf() is reporting inconsistent results!");

}

if (size <= maxSize || map.isEmpty()) {

break;

}

 //LinkedHashMap可以看作是一个先入先出的栈,回收内存的时候先从栈底开始回收,也就是回收好久没用过的bitmap

Map.Entry toEvict = map.entrySet().iterator().next();

key = toEvict.getKey();

value = toEvict.getValue();

map.remove(key);//将bitmap移出LRU缓存

size -= Utils.getBitmapBytes(value);//将当前总堆内存占用量计数器减去移出的bitmap大小

evictionCount++;//回收计数器加一

}

}

}

这个LRU缓存的最核心方法就这样分析完了,其实原理很简单,就是每放一个bitmap进LRU缓存都会记一下这个bitmap的大小,并计算当前LRU的总大小,如果发现总大小太大,就从栈底一个一个的把长时间没用的bitmap给回收掉

那么Picasso如何规定最大内存占用量的呢,让我们来看代码

/** Create a cache using an appropriate portion of the available RAM as the maximum size. */

public LruCache(Context context) {

this(Utils.calculateMemoryCacheSize(context));

}这个LRU缓存类在构造的时候就规定了最大内存占用指标,关键就是这个Utils.calculateMemoryCacheSize()方法,我们来看看它是怎么规定的

static int calculateMemoryCacheSize(Context context) {

ActivityManager am = getService(context, ACTIVITY_SERVICE);

boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;

int memoryClass = am.getMemoryClass();

if (largeHeap && SDK_INT >= HONEYCOMB) {

memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);

}

// Target ~15% of the available heap.

return 1024 * 1024 * memoryClass / 7;

} 这里是使用Context获得一个ActitityManager,然后用其获得获得一个以MB为单位的APP可占最大堆内存占用大小,然后使用这个最大APP占用的七分之一来做当前图片LRU缓存的最大可用大小,这个最大可用大小当然会随着手机配置的提高而变大,目前我这边测得的数据是:

红米note3: 19MB(3G内存)

Sony L50:22MB(3G内存)

红米2A:17MB(2G内存)

以经验来看,这样的内存分配并不大,经常出现在一个listView或者RecyclerView中,滑到底部后再滑回来,上面的元素的bitmap已经没有的情况,以RGB_8888为例,一个像素占用的大小为32字节,那么一个1920*1080的桌面背景图片所占得堆内存大小是1920*1080*32 = 63MB,对于这样图,LRU几乎是不会缓存的。

关于销毁指定LRU缓存:

手动销毁Picasso提供的默认LRU实现只能做到根据图片url进行销毁,而不能根据某个Activity或者Fragment进行销毁,如果想实现按照页面销毁的话,需要自己重写这个LruCache的实现。下面来看一下根据url进行销毁的源码:

@Override public final synchronized void clearKeyUri(String uri) {

boolean sizeChanged = false;

int uriLength = uri.length();

for (Iterator> i = map.entrySet().iterator(); i.hasNext();) {

Map.Entry entry = i.next();

String key = entry.getKey();

Bitmap value = entry.getValue();

int newlineIndex = key.indexOf(KEY_SEPARATOR);

if (newlineIndex == uriLength && key.substring(0, newlineIndex).equals(uri)) {//加快寻找速度

i.remove();//将相应的url所对bitmap移出LRU缓存

size -= Utils.getBitmapBytes(value);//将当前总堆内存占用计数器减小被移出的bitmap大小

sizeChanged = true;

}

}

if (sizeChanged) {

trimToSize(maxSize);//移出后执行以下内存检查,如果还是过大就继续销毁栈底的bitmap

}

}

我们在这里发现,Picasso默认的LRU缓存方案并不是我们需要的或者适合自己项目的方案,最好的方法是根据自己APP特点和业务需要重写LruCache,然后换掉Picasso默认的实现方案,方法如下:

Picasso.Builder builder = new Picasso.Builder(getContext());

builder.memoryCache(new CustomLruCache());//设置自定义的缓存方案

Picasso mPicasso = builder.build();//注意自定义Picasso实例要做成全局单例静态,否则缓存会失效同样方法可以自定义下载器,拦截器,线程池等等功能。

分析完这些实现,我们发现Picasso的强大之处并不在于针对某些应用场景提供完美的解决方案,而是它提供了一套完善的接口,让我们自由的根据自己APP的实际情况去自定义我们自己的策略,要想用好Picasso,光用的默认的方法是不行的,更重要的是了解图片下载、缓存、呈现的一系列需求并自定义自己的方案,然后借助Picasso来加载咱们自己的设定。

0b1331709591d260c1c78e86d0c51c18.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本人测试过,挺好用的,省去了好多麻烦:afinal 0.3 主要更新如下: 1、更新FinalBitmap模块,解决线程并发没有回收线程的问题 2、重写了FinalHttp模块 具体 change log 如下: FinalBitmap添加三个方法 public void onResume() public void onPause() public void onDestroy() 在activity生命周期方法中调用给方法释放内存和暂停图片加载线程 FinalHttp添加方法如下(目前AjaxCallBack泛型只支持String和file。有时间将会扩展JSONObject,xmlDom,byte[],bitmap等): public HttpClient getHttpClient() public HttpContext getHttpContext() public void configCookieStore(CookieStore cookieStore) //配置cookie public void configUserAgent(String userAgent) public void configTimeout(int timeout)//配置超时时间 public void configSSLSocketFactory(SSLSocketFactory sslSocketFactory) //配置https请求 public void configRequestExecutionRetryCount(int count)//配置网络异常自动重复连接请求次数 public void addHeader(String header, String value) //添加http请求头 //------------------get 请求----------------------- public void get( String url, AjaxCallBack<? extends Object> callBack) public void get( String url, AjaxParams params, AjaxCallBack<? extends Object> callBack) public void get( String url, Header[] headers, AjaxParams params, AjaxCallBack<? extends Object> callBack) public Object getSync( String url) //同步get请求,请在子线程执行这个操作,否则非常有可能报ANR public Object getSync( String url, AjaxParams params) public Object getSync( String url, Header[] headers, AjaxParams params) //------------------post 请求----------------------- public void post(String url, AjaxCallBack<? extends Object> callBack) public void post(String url, AjaxParams params, AjaxCallBack<? extends Object> callBack) public void post( String url, HttpEntity entity, String contentType, AjaxCallBack<? extends Object> callBack) public void post( String url, Header[] headers, AjaxParams params, String contentType,AjaxCallBack<? extends Object> callBack) public void post( String url, Header[] headers, HttpEntity entity, String contentType,AjaxCallBack<? extends Object> callBack) public Object postSync(String url) //同步post请求,请在子线程执行这个操作,否则非常有可能报ANR public Object postSync(String url, AjaxParams params) public Object postSync( String url, HttpEntity entity, String contentType) public Object postSync( String url, Header[] headers, AjaxParams params, String contentType) public Object postSync( String url, Header[] headers, HttpEntity entity, String contentType) //------------------put 请求----------------------- public void put(String url, AjaxCallBack<? extends Object> callBack) public void put( String url, AjaxParams params, AjaxCallBack<? extends Object> callBack) public void put( String url, HttpEntity entity, String contentType, AjaxCallBack<? extends Object> callBack) public void put(String url,Header[] headers, HttpEntity entity, String contentType, AjaxCallBack<? extends Object> callBack) public Object putSync(String url) //同步put请求,请在子线程执行这个操作,否则非常有可能报ANR public Object putSync( String url, AjaxParams params) public Object putSync(String url, HttpEntity entity, String contentType) public Object putSync(String url,Header[] headers, HttpEntity entity, String contentType) //------------------delete 请求----------------------- public void delete( String url, AjaxCallBack<? extends Object> callBack) public void delete( String url, Header[] headers, AjaxCallBack<? extends Object> callBack) public Object deleteSync(String url) //同步delete请求,请在子线程执行这个操作,否则非常有可能报ANR public Object deleteSync( String url, Header[] headers) //---------------------下载--------------------------------------- public void download(String url,String target,AjaxCallBack<File> callback) public void download( String url,AjaxParams params, String target, AjaxCallBack<? extends Object> callback) 附送请求demo和下载demo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值