Handler 消息回收与协程异步执行的时序陷阱
众所周知当你调用 Message.obtain() 时,Message 会优先检查缓存池中有没有实例,如果有就会直接拿出现有的 Message 对象,如果缓存池中没有才会去 new Message():
java
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
正常情况下,Handler 在调用 handleMessage 方法之后便会立即清除 msg 中的数据并将它丢入缓存池。具体回收流程查看源码如下:
java
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride = getThresholdOverride();
me.mSlowDeliveryDetected = false;
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
loop() 会不断调用 loopOnce(),以下为了方便定位核心逻辑我删除了很多代码并保持了剩余代码的原结构:
java
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
// 一系列代码(性能监控,观察者模式回调...)
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
// 关键代码在此处
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
PerfettoTrace.end(PerfettoTrace.MQ_CATEGORY).emit();
msg.recycleUnchecked();
return true;
}
可以看到 msg.target.dispatchMessage(msg); 这个分发事件的核心入口,无论成功或抛异常,最终都会执行到 msg.recycleUnchecked()。
我们接着在 Message 中找到 target,可以发现它是一个 Handler 类型的对象:
java
@UnsupportedAppUsage
/*package*/ Handler target;
再从 Handler 找到 dispatchMessage() 方法:
java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在这里我们终于看到了 handleMessage(msg) 也就是我们重写的那个方法。
我们接着查看 recycleUnchecked():
java
@UnsupportedAppUsage
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
其实包括 msg.recycle() 最终也是调用的 recycleUnchecked():
java
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
可以清楚地看到调用 recycleUnchecked() 的一系列操作。至此整个链路全部完毕:
- 取出消息 :
Message msg = me.mQueue.next(); - 开始处理 :
msg.target.dispatchMessage(msg); - 进入你的
handleMessage(msg)或Runnable.run()。 - 你的代码执行完毕 ,
handleMessage返回。 dispatchMessage方法执行完毕并返回。- 处理后工作: 记录耗时、打印日志、恢复线程状态等。
- 回收消息 :
msg.recycleUnchecked(); - 宣告完成 :
return true;// 告诉 Looper "本次任务完成"
总结就是一旦 handleMessage 执行完毕,msg 便会被清洗回收加入缓存池中。
kotlin
class HandlerTest() {
var handlerA: Handler? = null
var handlerB: Handler? = null
fun start() {
Thread {
Looper.prepare()
handlerA = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
val count = msg.data.getInt("count")
println("HandlerTest A:$count")
CoroutineScope(Dispatchers.IO).launch {
println("HandlerTest A Coroutine: ${msg.data.getInt("count")}")
delay(1000)
handlerB?.sendMessage(
Message.obtain().apply {
data = Bundle().apply {
putInt("count", count + 1)
}
}
)
}
}
}
Looper.loop()
}.start()
Thread {
Looper.prepare()
handlerB = object : Handler(Looper.myLooper()!!) {
override fun handleMessage(msg: Message) {
val count = msg.data.getInt("count")
println("HandlerTest B:$count")
CoroutineScope(Dispatchers.IO).launch {
delay(1000)
handlerA?.sendMessage(
Message.obtain().apply {
data = Bundle().apply {
putInt("count", count + 1)
}
}
)
}
}
}
Looper.loop()
}.start()
Thread.sleep(500)
handlerA?.sendMessage(
Message.obtain().apply {
data = Bundle().apply {
putInt("count", 0)
}
}
)
}
}
执行代码可以看到以下日志:
text
I HandlerTest B:1
I HandlerTest A:2
I HandlerTest A Coroutine: 0
I HandlerTest B:3
I HandlerTest A:4
I HandlerTest A Coroutine: 0
I HandlerTest B:5
I HandlerTest A:6
I HandlerTest A Coroutine: 0
可以看到在协程内调用 msg.data.getInt() 返回的是 0。这是因为 launch 将任务提交给协程调度器后立即返回,handleMessage 随即执行完毕。loopOnce() 紧接着调用 recycleUnchecked() 将 msg 的所有字段清空。等到协程真正被调度到后台线程开始执行时,msg 已经被回收。
后续在协程内访问 msg.data,实际上调用的是 msg.getData(),这是 Kotlin 的语法糖。实际执行以下代码:
java
public Bundle getData() {
if (data == null) {
data = new Bundle();
}
return data;
}
所以实际访问的是一个新的空 Bundle,接着调用 getInt() 返回默认值 0:
java
public int getInt(String key) {
unparcel();
return getInt(key, 0);
}
最后,如果在该 Message 被回收后、协程执行前,它恰好被另一次 Message.obtain() 复用并写入了新数据,协程将读到完全不属于当前业务的脏数据。希望以上内容对大家理解 Handler 消息机制有所帮助。