189 8069 5689

Android中的Message类以及Java对象池的实现-创新互联

在Android的android.os.Message类的文档中有这么一句话:

创新互联建站是一家专注于成都网站制作、成都做网站与策划设计,郸城网站建设哪家好?创新互联建站做网站,专注于网站建设十余年,网设计领域的专业建站公司;建站业务涵盖:郸城等地区。郸城做网站价格咨询:13518219792
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

用图形表示如下:

Android中的Message类以及Java对象池的实现

继续阅读代码,又发现了一个让人困惑的问题,看这个方法:

    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来实现对象池和这种通过链表来实现对象池两种方法,我看不出来各自有何优缺点,这两种方法很相似,实现的功能也相似,唯一的不同在于,前者似乎更容易扩展。也许你自己在其他项目中需要对象池的时候,可以借鉴一下这两种方法。

另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。


分享文章:Android中的Message类以及Java对象池的实现-创新互联
标题URL:http://cdxtjz.cn/article/dhjcgj.html

其他资讯