最近在项目中遇到了一个极其诡异的问题。当我们向主线程的的Looper post一个Runnable时,这个Runnable有时会在非主线程被执。
车祸现场
这里先贴出和该bug相关的所有信息。
设备信息
小米 4.4
Crash 堆栈
05-10 17:40:03.400 3432-3446/com.easoll.helloworld E/AndroidRuntime: FATAL EXCEPTION: sub thread
Process: com.easoll.helloworld, PID: 3432
java.lang.Exception: should run in main thread
at com.easoll.helloworld.MainActivity$start$1.run(MainActivity.kt:30)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.os.HandlerThread.run(HandlerThread.java:61)
问题代码
package com.easoll.helloworld
import android.os.*
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val mMainHandler = Handler(Looper.getMainLooper())
private lateinit var mSubHandler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_test.setOnClickListener {
start()
}
val handlerThread = HandlerThread("sub thread")
handlerThread.start()
mSubHandler = Handler(handlerThread.looper)
}
private fun start(){
Evil.doSomething()
mMainHandler.postDelayed({
if(Looper.myLooper() != Looper.getMainLooper()){
throw Exception("should run in main thread")
}
start()
}, 100)
mSubHandler.sendEmptyMessage(1)
}
}
导致问题的关键是Evil.doSomethong。大家可以先思考下Evil.doSomething到底是执行了什么,后面将为大家揭晓答案。
############################################################# ###################### 我是分割线 ############################# #############################################################
问题分析
这里先贴出Handler和Message的相关代码
Handler.java
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Message.java
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
public void recycle() {
clearForRecycle();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
我们在通过sendEmptyMessageDelayed和postDelayed发送消息的时候都会从Message的对象池里面取已有的消息来用。正常来说,这个模式是没有问题的。可是如果我们在Evil.doSomething里面做一些不太好的事情,问题就要出现了。
object Evil{
fun doSomething(){
val message = Message()
message.recycle()
message.recycle()
}
}
正常的消息池应该是一个无循环的链表,可是当我对同一个message多次调用recycle的时候就会导致这个链表形成环。 我们通过一张图来对比下正常情况和异常情况下MainHandler和SubHandler从消息池中取消息的过程。

正常情况 MainHandler 和 SubHandler拿到的应该是不同的Message对象,可是当对象池中出现循环时,就会导致MainHandler和SubHandler拿到的是同一个对象。这就导致了同一个消息被发送到了两个MessageQueue.
由于我们向MainHandler中发送的消息是一个延时消息,所以本应在主线程中执行的Runnable就在sub thread中执行了。