VacantCell缓存主要是对同一种类型对象的缓存机制,这种机制的目的是复用已经分配过但已过期的对象,从而避免频繁的new新的对象。我们知道对象分配是一件比较耗费系统资源的事情,在需要频繁、大量的分配对象的时候Android平台很可能出现堆内存不足进而导致系统变慢、应用报错重启等严重问题。用户在待机界面拖动图标是很频繁的事情,而Android的屏幕中空白网格的寻址算法就采用了这种VacantCell缓存来避免频繁new大量新的对象。
VacantCell类的实现其实很简单,就不到50行实现代码:
static final class VacantCell { int cellX; int cellY; int spanX; int spanY; // We can create up to 523 vacant cells on a 4x4 grid, 100 seems // like a reasonable compromise given the size of a VacantCell and // the fact that the user is not likely to touch an empty 4x4 grid // very often private static final int POOL_LIMIT = 100; private static final Object sLock = new Object(); private static int sAcquiredCount = 0; private static VacantCell sRoot; private VacantCell next; static VacantCell acquire() { synchronized (sLock) { if (sRoot == null) { return new VacantCell(); } VacantCell info = sRoot; sRoot = info.next; sAcquiredCount--; return info; } } void release() { synchronized (sLock) { if (sAcquiredCount < POOL_LIMIT) { sAcquiredCount++; next = sRoot; sRoot = this; } } } @Override public String toString() { return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX + ", spanY=" + spanY + "]"; } }
可以看到,VacantCell实际上是一个Java静态内部类,其外部类CellLayout.java的代码路径是\packages\apps\Launcher2\src\com\android\launcher2\CellLayout.java(具体如何下载Android gingerbread Launcher单个模块的下载方法见博文Android源码下载——用git clone实现单个目录下载)。
下面我们具体分析下VacantCell的缓冲机制是如何实现的。
cellX、cellY、spanX、spanY几个成员是空白网格的纵横索引号和占据相应单元网格数,我们这里可以不用关心。
POOL_LIMIT定义了可以缓存的VacantCell的最大值,正如注释所说,最多可以分配多达523个VacantCell对象,但是对于4X4的屏幕网格定义来说,100个的缓冲数应该是足够的了。也就是说,这个100的数值是一个经验值,我们可以根据实际情况作灵活修改。
sLock是一个Java Object对象,主要是拿来作同步控制的,我们可以不用关心。
sAcquiredCount是一个非常重要的变量,其实际上就是一个计数器,实时的统计当前缓存了多少个VacantCell对象值。根据这个计数器和POOL_LIMIT可以控制缓存对象不超过POOL_LIMIT的上限。
sRoot和next是缓存VacantCell对象链式存储的两个关键引用,分别表示VacantCell对象链表的表头和特定VacantCell对象链接的下一对象的引用。
好了,下面介绍VacantCell缓存的两个关键静态方法acquire()和非静态方法release()。
当需要VacantCell新对象的时候,Launcher会调用静态内部类的VacantCell的静态方法acquire()以获取新的VacantCell对象。请注意,这里获取新对象不是直接new一个VacantCell对象,而是acquire的。我们看acquire方法的内部,当sRoot为null的时候,是直接new VacantCell并直接return的,因为这个时候实际上还没有缓存可用;当sRoot不为null,这意味着已经有缓存的VacantCell对象了,那么直接从链表中取出sRoot指向的对象,并将sRoot所在对象指向的next作为新的表头sRoot,同时把sAcquiredCount计数减1。这实际上是一种典型的从链表中删除表头的操作,相信熟悉数据结构的朋友对此都不会陌生。
如果说acquire()方法提供的是如何利用VacantCell链式缓存,那么release()方法解决的就是如何构建VacantCell链式缓存的问题。我们看到,只有sAcquiredCount < POOL_LIMIT的前提下才会将VacantCell对象加入链式缓存。将当前VacantCell对象加入链式存储也很简单,将当前VacantCell对象的next指向原来的表头,将静态sRoot应用指向当前对象,同时sAcquiredCount加1。也就是说,这个操作其实就是把当前VacantCell对象插入VacantCell链表的表头。
那么如何使用这个VacantCell对象缓存呢?第一,当需要新的VacantCell对象的时候,通过调用VacantCell的静态方法acquire()来获取对象,而不是直接new VacantCell对象;第二,当一个VacantCell对象已经过期,不再需要的时候,调用该对象的release()方法将这个不再需要的对象加入缓存。
需要指出的是,如果直接new VacantCell对象也不会有直接的问题,只不过这意味着没有采用VacantCell缓存机制;如果一个对象不需要时不显示调用release()方法也不会有直接的问题,这意味着这个对象不会加入进VacantCell缓存链表,当没有链表各节点对象的引用关联,这个对象最终将被Android的Dalvik虚拟机当做垃圾自动回收。
本文详细分析了Android Launcher模块VacantCell缓存的实现原理,当我们需要频繁构造、释放大量的相同类型的Java对象的时候,我们考虑采用类似的缓存机制,这在某些场合下能有效的解决频繁分配对象导致的内存不足的问题。
/*****************************************************************************************************************************************/
本文为原创文章,转载请务必注明出处:http://blog.csdn.net/droidpioneer/article/details/6787571
安卓开发用机友情链接:http://vpclub.octech.com.cn/ztewd/9495.html
/******************************************************************************************************************************************/