频道栏目
首页 > 资讯 > 其他 > 正文

Andrid内存优化之你必须知道的核心类LruCache

17-04-21        来源:[db:作者]  
收藏   我要投稿

提到内存优化,就会想到内存缓存,而提到内存缓存就必须得提到android提供的Lru缓存方案,它的核心就是LruCache类,因此,从源码的角度去看看它的工作原理。

在android3.1.x(API 12)之前,我们用到的是android.util包下的LruCache,在此之后,我们可以用android.support.v4.util包下的LruChache,其实这两个包下的LruCache代码一样,只是为了兼容。

通常我们要得到LruCache对象,一般这样获取:

int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024);

int cacheMemorySize = maxMemory/8;

LruCache cache = new LruCache(cacheMemorySize);

那我们先从它的构造函数看起:

public LruCache(int maxSize) {

if (maxSize <= 0) {

throw new IllegalArgumentException("maxSize <= 0");

}

this.maxSize = maxSize;

this.map = new LinkedHashMap(0, 0.75f, true);

}

这里先判断分配的缓存大小是否大于0,不是则抛出异常;然后就是关键的地方,初始化了一个LinkedHashMap,它就是用来缓存我们需要保存起来的对象。

当我们要把需要缓存从网络下载下来的图片时,我们会这么做:

if(cache.get(url) == null){

cache.put(url,bitmap);

}

当LruCache中没有缓存过这个图片时,我们才将图片存入,我们来看看LruCache的put()方法是如何实现的:

public final V put(K key, V value) {

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

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

}

V previous;

synchronized (this) {

putCount++;

size += safeSizeOf(key, value);

previous = map.put(key, value);

if (previous != null) {

size -= safeSizeOf(key, previous);

}

}

if (previous != null) {

entryRemoved(false, key, previous, value);

}

trimToSize(maxSize);

return previous;

}

如果url或bitmap为null,则抛出异常。接着进入同步代码块,定义了一个int型计数器putCount,每往其中存入数据,putCount+1;接着计算需要缓存内存大小的总和size.他通过safeSizeOf(K key, V value)方法获取

private int safeSizeOf(K key, V value) {

int result = sizeOf(key, value);

if (result < 0) {

throw new IllegalStateException("Negative size: " + key + "=" + value);

}

return result;

}

这里看到它是直接调用的sizeof()方法的

protected int sizeOf(K key, V value) {

return 1;

}

可以看到,这个方法默认返回是1,所以如果想要得到确切的缓存大小,应该在开始初始化LruCache时覆写这个方法,将计算得到的图片占用内存大小回调到回调方法中。所以,开始初始化LruCache的时候我们应该改成这样:

LruCache cache = new LruCache(cacheMemorySize){

@Override

protected int sizeOf(String key, Bitmap value) {

return value.getByteCount()/1024;

}

}

这样,就把图片所占用的内存大小返回给了size。接着,判断当前bitmap是否已经存入过,存过,则将这个图片的内存占用大小从size中去除,这样就避免了重复计算同一张图片的内存占用大小。这样,同步代码快就走完了,接着往下看:

if (previous != null) {

entryRemoved(false, key, previous, value);

}

判断当前bitmap是否存入过,是则进入entryRemoved()方法

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

这是个空的方法,当item被明确要求移除腾出空间时,重写这个方法,它会在item被回收时被remove方法调用,而在被替换时,会被put方法调用。我们可以通过判断传入的evicted值来确定当前的操作。总结一下就是:

evicted = true,说明它正在被移除;evicted = false,说明它是被remove或put掉了 newValue是key所对应的新的value,如过value不为null,这时动作是通过put实现的。如果value为null,则这个动作是通过remove实现的。

好了,entryRemove()方法分析完了,接着往下走,来到trimToSize(maxSize)

public void trimToSize(int maxSize) {

while (true) {

K key;

V value;

synchronized (this) {

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

throw new IllegalStateException(getClass().getName()

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

}

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

break;

}

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

key = toEvict.getKey();

value = toEvict.getValue();

map.remove(key);

size -= safeSizeOf(key, value);

evictionCount++;

}

entryRemoved(true, key, value, null);

}

}

这个方法一进来就是一个while循环,然后进入同步代码块,这里就做了一件事,当存入的对象占用内存大小大于我们预设的缓存大小时,遍历map集合,从头开始移除map里面的对象,每移除一个对象,size就减去这个item所占内存的大小(其实就是这个bitmap所占的内存),同时调用一次entryRemoved()方法。这样,put方法就分析完了;

最后,我们来看看LruCache的get方法:

public final V get(K key) {

if (key == null) {

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

}

V mapValue;

synchronized (this) {

mapValue = map.get(key);

if (mapValue != null) {

hitCount++;

return mapValue;

}

missCount++;

}

这个方法做的是很简单,如果key为null,抛出异常。反之,从map中取出value返回。

好了,看到这里,我们基本搞清楚了LruCache的工作原理了,总结如下:

LruCache内部通过维护一个LinkedHashMap来存入或移除对象 LruCache的基本操作是线程安全的 LruCache通过判断存入对象占内存总量是否大于预设的缓存大小,大于,则从头部开始移除存入的对象,直到占用内存不超过预设的缓存内存为止

好了,就这么多了,后续我会写一篇关于LruCache应用的文章,希望大家多多关注。

 

相关TAG标签
上一篇:AndroidStudio获取SHA1
下一篇:ContentProvider 的规范编写
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站