在Android的android.os.Message类的文档中有这么一句话:
While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.
大意是说,虽然Message类的构造方法是public的,你可以直接通过new来创建一个新的对象,但是最好还是通过Message.obtain()或者Handler.obtainMessage()来创建一个新的Message对象,因为这两个方法会重用之前创建的但是已经不再使用了的对象实例。
这句话不禁引起了我的兴趣,因为之前我写过一篇博客《Pool, SimplePool与SynchronizedPool》,在这篇博客里面仔细分析了Android是如何实现一个对象池的。里面提到VelocityTracker就是用SynchronizedPool来实现对象重用的,代码如下:
VelocityTracker tracker = VelocityTracker.obtain(); tracker.recycle();
Message类看起来也略有相似,不过经过阅读Message类的源代码,发现我错了,Message类使用了另一种巧妙的方法来实现对象重用。
好了,不卖关子了,Message类使用了一个链表来实现对象池,而且是一个前端链表,即在前端插入和删除的链表,避免了插入和删除的时候遍历整个链表。是不是有点出人意料?
首先看一下这段代码,去除了Message中其他的携带消息信息的字段。已经很明显可以看出来是一个链表了吧。
public final class Message implements Parcelable { // 省略其他代码 Message next; // (1) private static final Object sPoolSync = new Object(); // (2) private static Message sPool; // (3) private static int sPoolSize = 0; // (4) private static final int MAX_POOL_SIZE = 50; // 省略其他代码 }
(1) 声明了next指针
(2) 对象锁,Message对象池是线程安全的,这样就需要在向对象池申请和归还对象时使用锁
(3) 这里是关键,一个静态的Message对象,这就是这个链表的头指针了,因为是类变量,因此,整个JVM中只有一个
(4) 当前对象池的大小,后面还限制了这个Message对象池中的对象个数最大为50
用图形表示如下:
继续阅读代码,又发现了一个让人困惑的问题,看这个方法:
public static Message obtain() { synchronized (sPoolSync) { // (1) if (sPool != null) { Message m = sPool; // (2) sPool = m.next; // (3) m.next = null; // (4) sPoolSize--; // (5) return m; } } return new Message(); }
这也很容易理解:
(1) 获取锁,这里毫无疑义
(2) 当头指针指向的对象不为null时,将这个对象赋值给m
(3) 将头指针指向m的next指针
(4) 将m的next指向null,到这里位置,我们从以sPool为头指针的链表中取出了第一个元素
(5) 将链表size减1
看起来没错,疑问是,当第一次获取对象的时候sPool肯定为null,那么这个if语句肯定不会执行,会直接执行最后一句return new Message(),直接创建一个对象?这样不是毫无意义了么?
看到这里,似乎感觉发现了一个Android的bug,会有这么明显的bug么?当然不会,静下心来继续读代码,读到这里的时候豁然开朗了:
public void recycle() { clearForRecycle(); // (1) synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { // (2) next = sPool; // (3) sPool = this; // (4) sPoolSize++; } } }
(1) 重置Message中携带的消息
(2) 检查当前对象池的大小是不是已经超过了最大值,如果当前队列已经满了,就不管这个对象了,让JVM的GC回收好了,这里保证了性能的同时兼顾了内存消耗
(3) 将当前对象next指针指向头指针sPool指向的对象
(4) 将头指针sPool指向当前对象,然后将对象池大小加1
到这里就明白了:这篇博客开头的那句话其实背后还有一些潜台词,那就是你必须显式的调用一下recycle()将当前的Message对象归还到对象池,这个对象池才能发挥其效果,不调用recycle()方法,对象不会归还,会被JVM GC回收。
也就是说下面两句话必须是成对出现的,不用obtain()而调用recycle()会导致不停的创建Message对象直到超过MAX_POOL_SIZE的限制而被对象池扔掉;通过obtain()申请对象而不用recycle()归还会导致对象池被消耗干净而不停申请新对象。
Message msg = Message.obtain(); msg.recycle();
所以文档再完整还是不如看代码。
对于通过SynchronizedPool来实现对象池和这种通过链表来实现对象池两种方法,我看不出来各自有何优缺点,这两种方法很相似,实现的功能也相似,唯一的不同在于,前者似乎更容易扩展。也许你自己在其他项目中需要对象池的时候,可以借鉴一下这两种方法。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。