vacantcell缓存分析
http://blog.csdn.net/droidpioneer/article/details/6758057
操作过Android手机的朋友应该对在待机界面拖动图标的交互效果都有比较深刻的印象,比如说,当把图标拖动起来,图标会悬浮并随着拖动变换位置,当拖动释放后,图标会自动的寻找附近合适的空白网格位置。Android在实现这个效果的过程中采用了很多编程技巧,本文着重论述的VacantCell缓存就很具有代表性。
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 + "]";
- }
- }
- 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对象的时候,我们考虑采用类似的缓存机制,这在某些场合下能有效的解决频繁分配对象导致的内存不足的问题。