Handler 的 Message 实例怎么获得?为什么不是直接 new?
Posted TechMerger
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler 的 Message 实例怎么获得?为什么不是直接 new?相关的知识,希望对你有一定的参考价值。
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 对象缓存起来
以上是关于Handler 的 Message 实例怎么获得?为什么不是直接 new?的主要内容,如果未能解决你的问题,请参考以下文章