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

提到内存优化,就会想到内存缓存,而提到内存缓存就必须得提到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<K, V>(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<String,Bitmap> cache = new LruCache<String,Bitmap>(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值来确定当前的操作。总结一下就是:

  1. evicted = true,说明它正在被移除;evicted = false,说明它是被remove或put掉了
  2. 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<K, V> 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的工作原理了,总结如下:

  1. LruCache内部通过维护一个LinkedHashMap来存入或移除对象
  2. LruCache的基本操作是线程安全的
  3. LruCache通过判断存入对象占内存总量是否大于预设的缓存大小,大于,则从头部开始移除存入的对象,直到占用内存不超过预设的缓存内存为止
    好了,就这么多了,后续我会写一篇关于LruCache应用的文章,希望大家多多关注。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fastsy

打赏一份隆江猪脚饭吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值