Handler 的 Message 实例怎么获得?为什么不是直接 new?

15 篇文章 0 订阅
5 篇文章 0 订阅

在这里插入图片描述

Message#obtain() 大家都不陌生,也能说出来是原理是 Pool 的复用,但鲜少留意细节,本文带你了解一下其深层次的原理。

基本都是调用 obtain() 来获得

使用 Message 的方式有很多种,无论是 Handler#obtainMessage() 还是 Message#obtain(),甚至是 Handler#postRunnable(),本质上都是调用 Message 的静态方法 obtain()。

    public final Message obtainMessage() {
        return Message.obtain(this);
    }
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        ...
    }

Message() 也是 public 的,但更建议 obtain()

obtain() 不是无脑的 new 一个实例,而是从缓存 Message 的 Pool 中取出第一个,在 Handler 频繁使用,Message 多且繁杂的场景中复用 Message 减少内存消耗,提高效率。

    public static Message obtain() {
        synchronized (sPoolSync) { // 给 Pool 加的同步锁
            if (sPool != null) {
                Message m = sPool; // 将 Head 即 sPool 返回
                sPool = m.next; // 返回之前将 Head 更新
                m.next = null; // 同时将原有的 next 指向清空
                m.flags = 0; // clear in-use flag
                sPoolSize--; // 将记录 Pool 的长度自减
                return m;
            }
        }
        return new Message(); // 没有缓存过 Message 的话 new 一个
    }

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }

备注:虽然命名为 Pool,但本质是维护 Head(sPool)和更新 next 指向的单链表。

recycle 时候会缓存到 Pool

等 Message 执行完之后会 recycle 它:具体处理是将它缓存到 sPool 属性,同时记录的 poolSize 增加,在缓存前会先更新 next 指向的下个 Message 实例。

    /*package*/ Message next;
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    void recycleUnchecked() {
        ...
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) { // Pool 有 50 的上限
                next = sPool; // 将之前存的 Head 作为 next
                sPool = this; // 将当前的 Message 作为 Head
                sPoolSize++; // 长度自加
            }
        }
    }

验证 Pool size 的上限

Message 包含着申请、执行、Recycle 和缓存几种操作。Handler 的使用一般较为繁杂,一个 Message 缓存完之后另一个才开始申请的情况几乎很少,更多的是前一个 Message 在等待执行或执行中的时候其他多个 Message 又开始申请。这样子的话,Pool 中的 Message 会越来越不够用,则开始持续 new。

循环往复,Pool 的 size 会越来越大,存放的 Message 会越来越多,下一次 Message 的申请就从这个 Pool 里逐个领取。但 Pool 的 size 并非一直累加,但 recycle 的时候发现 size 已经超过约定的 50 上限的时候,不再将当前的 Message 实例缓存,没有了静态应用,等待他的则是 GC 的回收。

我们来验证一下这种情形:比如向 Handler 同时 send 50 个 Delay Message 和 1 个 Delay 更久的 Message,等前 50 个 Message 执行完的时候打印一下 Pool 的 Size,第 51 个 Message 执行完的时候也打印一下 Size。最后呢再申请一个 Message,查看它运行前和结束两个时间点 Pool 的 Size 情况。

    private fun startHandlerThread() {
        val handlerThread = HandlerThread("Test Handler thread")
        handlerThread.start()

        testHandler = Handler( handlerThread.looper ) { msg ->
            when (msg.what) {
                50 -> mainHandler?.post {
                        getMessagePoolSize(msg).let { Log.d("MainActivity", "50 finished and pool size:$it") }
                }
                51 -> mainHandler?.post {
                    getMessagePoolSize(msg).let { Log.d("MainActivity", "51 finished and pool size:$it") }
                    sendMessage(testHandler, 52)
                    getMessagePoolSize(msg).let { Log.d("MainActivity", "52 waiting and pool size:$it") }
                }
                52 -> mainHandler?.post {
                    getMessagePoolSize(msg).let { Log.d("MainActivity", "52 finished and pool size:$it") }
                }
            }
            true
        }

        sendMessages(testHandler)
        sendMessage(testHandler, 51)
    }

发送多个 Message 和单个 Message 的代码,以及打印 Message 的 Pool Size。

注意:打印 Size 的反射方法可能遭遇非公开 API 的拦截,执行之前记得输入命令(adb shell settings put global hidden_api_policy 0)临时关闭非公开 API 的检查。

    private fun sendMessages(handler: Handler) {
        for (i in 1..50) {
            Message.obtain().let {
                it.what = i
                handler.sendMessageDelayed(it, 2000L)
            }
        }
    }

    private fun sendMessage(handler: Handler, what: Int) {
        Message.obtain().let {
            it.what = what
            handler.sendMessageDelayed(it, 2500L)
        }
    }

    private fun getMessagePoolSize(message: Message): Int {
        var size = 0
        try {
            val clazz = Class.forName("android.os.Message")
            val field = clazz.getDeclaredField("sPoolSize")
            field.isAccessible = true
            size = field[message] as Int
        } catch (e: Exception) { Log.e("MainActivity", "getMessagePoolSize exception:$e") }
        return size
    }

刚开始的时候 Handler 尚未缓存任何 Message,所以一直 new 了 51 个实例,等前 50 个 Message 都执行好之后向 Pool 缓存了 50 个 Message,第 51 个 Message 执行完的时候就不会再往里面缓存了,所以此刻的 Size 固定为 50。后续申请 Message 的话,则从池子里取一个,用完再放回去。

从日志也可能看出来:

    D/MainActivity: Current thread:Thread[Test Handler thread,5,main] Message[1] handle
    D/MainActivity: Current thread:Thread[Test Handler thread,5,main] Message[2] handle
    ...
    D/MainActivity: Current thread:Thread[Test Handler thread,5,main] Message[49] handle
    D/MainActivity: Current thread:Thread[Test Handler thread,5,main] Message[50] handle
    D/MainActivity: 50 finished and pool size:50
    D/MainActivity: Current thread:Thread[Test Handler thread,5,main] Message[51] handle
    D/MainActivity: 51 finished and pool size:50
    D/MainActivity: 52 waiting and pool size:49
    D/MainActivity: Current thread:Thread[Test Handler thread,5,main] Message[52] handle
    D/MainActivity: 52 finished and pool size:50

Pool 连续缓存了 50 个 Message 的情况非常少见,即便真得发生了这种情况,50 个 Message 也足够复用了。在这种极少的情况之下又发生了一下子又申请超过 50 个 Message 的超极端状态,额外再 new Message 就是了。起码这个极低概率下的内存消耗总比无上限的缓存 Message 来得好!

总结

除了 Message,Android 系统当中还有很多类也采用 obtain() 来获取实例,比如 MotionEventKeyEventAccessibilityEvent等。

  • MotionEvent 和 KeyEvent 的 obtain() 的原理和 Message 几乎完全一致,都是使用单链表来维护实例的复用,不同的是 Event 池子的 Size 上限为 10
  • AccessibilityEvent 则稍稍不同,obtain() 是从 SynchronizedPool 里取得实例,其本质上是固定长度的数组,每次从最后一个元素获取和存入实例,感兴趣的朋友可看看源码

obtain 的实现思路非常巧妙,了解它不仅仅是应付别人的提问,更是在日后的编码当中应用上这种实现思路,尤其是可以用在频繁使用某个实例的场景。

再次总结下 obtain 的好处:

  1. 可以从 Pool 里取出缓存好的 Message
  2. 也可以在使用过的时候将旧的 Message 对象缓存起来
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TechMerger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值