温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2020-06-10 04:24:15 来源:网络 阅读:3534 作者:zhlwish 栏目:移动开发

在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


用图形表示如下:

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

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI