毁三观系列之-错乱的Message

535 阅读2分钟

最近在项目中遇到了一个极其诡异的问题。当我们向主线程的的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中执行了。