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

来源:互联网 发布:linux ftp服务开启 编辑:程序博客网 时间:2024/05/22 15:56

提到内存优化,就会想到内存缓存,而提到内存缓存就必须得提到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应用的文章,希望大家多多关注。
1 0