Android Memory Cache: LruCache

现有的apps总是会遇见图片加载的问题,特别是大批量图片的加载。为了减少内存的使用,会采用循环利用那些views的方式。当一个view滑出屏幕的时候,就被利用来重新显示新出现的view。垃圾回收器认为那些view中的图片不再需要,就会释放不再显示的图片。但是从操作的流畅性来考虑,开发者需要避免这样频繁地处理图片的加载和回收问题。而内存或者磁盘的cache就会帮助解决这个问题。


在缓存上,主要有两种级别的Cache: LruCache和DiskLruCache。前者是基于内存的,后者是基于磁盘的。我们就可以很容易地分析出LruCache说占用的空间一般比DiskLruCache小,而读取的时间LruCache显然会比DiskLruDisk来得短。(至于具体上的读取时间,有待验证??)这是一种平衡,需要我们去衡量。其中包括命中率、两种介质中的读写时间以及使用频率等因素。在这篇文章中,我们先介绍一下面向内存的LruCache,后续再分析DiskLruCache。

LruCache有两个版本,android.util.LruCache和android.support.v4.util.LruCache。前者是在level 12中才引入的,后者则在之前的版本中使用。两者其实没有太大的区别。都是使用LinkedHashMap来作为基础的内存容器。LinkedHashMap是非线程安全的Map基类实现,但是保证了迭代地顺序,并且允许null的keys或values。对于结构的改变,需要采用同步机制来保证一致性。LinkedHashMap使用双向链表来存储各个插入项,同时按照插入的顺序来遍历。重新插入的并不会影响之前的顺序。在迭代器的构建方面,不允许迭代时对Map进行结构上的改变。当需要返回一个新的迭代器(iterator)时,如果已有一个iterator对map进行了结构变化,那么就会抛出ConcurrentModificationException。迭代时只有删除方法是被支持的。所以在进行迭代操作时一定要慎重的分析潜在的需求。


android.util.LruCache中的get(key)方法中首先会查看是否有key对应的value,如果有则直接返回。如果没有那么就会调用create(key)方法来创建一个新的value。虽然提供了线程同步的支持,但是在create(key)过程中并没有进行同步。所以存在一种状况是create(key)完成后,发现已经有新对应的value了。那么就会舍弃掉新创建的,返回已有的value。除此之外还需要做的是清除刚刚create出来的value。这个主要是通过调用entryRemoved()方法来实现。如果create(key)创建的value加入到map中,那么就需要对map进行trimToSize(),也就是按照既定的maxSize进行entry调整,删除多余的entry。而上述中提到的create()方法和entryRemoved()方法默认都没有提供实现。需要扩展它的子类根据具体的应用场景来提供实现。所以LruCache还是不能直接使用的。还有默认情况下,LruCache是按照entry的个数来控制的,而不是内存的大小。但是我们可以通过LruCache中的sizeOf(key, value)来修改这个策略。只要这个方法返回的int表示的是value的内存大小,而不是默认情况下的1,同时maxSize表示的也是内存大小就可以了。LruCache中还对hitCount、missCount、createCount、putCount以及evictionCount进行了统计。snapshot()方法则是对map进行了拷贝,生成一个新的LinkedHashMap。采用LinkedHashMap就保证了LRU算法。每次get和put操作都会将最新操作元素放到队列的最前面。


android.support.v4.util.LruCache在源代码实现上基本和android.util.LruCache一样。可能是在后续的版本中,android发现了cache的重要性,所以将其移到正式的包中。所以我们还是尽量使用android.util.LruCache,而android.support.v4.util.LruCache只是为了向前兼容。

没有更多推荐了,返回首页