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() 来获取实例,比如 MotionEvent、KeyEvent、AccessibilityEvent等。
MotionEvent 和 KeyEvent 的 obtain() 的原理和 Message 几乎完全一致,都是使用单链表来维护实例的复用,不同的是 Event 池子的 Size 上限为 10
AccessibilityEvent 则稍稍不同,obtain() 是从 SynchronizedPool 里取得实例,其本质上是固定长度的数组,每次从最后一个元素获取和存入实例,感兴趣的朋友可看看源码
obtain 的实现思路非常巧妙,了解它不仅仅是应付别人的提问,更是在日后的编码当中应用上这种实现思路,尤其是可以用在频繁使用某个实例的场景。
再次总结下 obtain 的好处:
可以从 Pool 里取出缓存好的 Message
也可以在使用过的时候将旧的 Message 对象缓存起来