Handler 从使用到源码探索的思考

271 阅读56分钟

使用 Handler 这么多年,也看过很多分析文章,跟着前人的思考去剖析源码,当时以为理解很透彻了,再回过头去分析时感觉有些地方又模糊不解了。所以决定冷饭再炒,再来分析一下 Hanlder。

在分析之前,先列一些比较细节的问题可以思考一下,本文会对这些问题详细分析:

  1. 当插入的 msg 的 when 相等时,插入顺序是怎样的?后插入的一定排后面吗?
  2. 在哪些情况进行消息回收?
  3. next() 的休眠阻塞是怎么回事?唤醒是怎么回事?出现什么情况时唤醒?为什么要唤醒?
  4. 消息不一定在指定的 when 时刻被分发处理,有些什么可能的原因?
  5. 可以发送多条同步屏障消息吗?发送同步屏障消息后立马就阻塞普通消息吗?发送同步屏障消息后能一定阻塞普通消息吗?
  6. 发送异步消息需要注意什么?
  7. 调用 Looper#quitSafely() 后,其中剩余的 msg 一定能被分发处理吗?

1. 初期使用

在接触 Android 初期进行业务开发的时候,我们就知道,UI 更新需要在主线程,而一些 IO 操作则必须在子线程处理,那怎么将子线程获取的数据呈现在界面上呢?此时就需要了解多线程通信,其中最常用的就是使用 Handler。

1.1 创建与消息处理

Handler 创建常用的有两种方式,一种是静态内部类,一种是匿名内部类,示例如下:

    // 静态内部类的方式
    private class UpdateHandler : Handler(Looper.getMainLooper()) {

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            // 处理消息
        }
    }

    // 匿名内部类的方式
    private val updateHandler = object : Handler(Looper.getMainLooper()) {

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            // 处理消息
        }
    }

然后可在 handleMessage() 方法中处理接收到的消息。处理消息有三种方式,这是其中一种,另外两种方式在后续分析 Handler 的源码部分会有介绍。

1.2 消息发送

Handler 发送消息有 send 和 post,示例如下:

    val msg = Message.obtain().apply {
        what = 1
        obj = "Hello World"
    }
    handler.sendMessage(msg)
    handler.sendMessageDelayed(msg, 1000)
    handler.sendMessageAtTime(msg, 1000)
    handler.sendMessageAtFrontOfQueue(msg)

    handler.sendEmptyMessage(1)
    handler.sendEmptyMessageDelayed(1, 1000)
    handler.sendEmptyMessageAtTime(1, 1000)

    handler.post {  }
    handler.postDelayed({}, 1000)
    handler.postAtTime({}, 1000)
    handler.postAtFrontOfQueue {  }

1.3 遇到问题

在刚开始接触 Handler 的时候,会遇到一些简单问题。例如:

Q1. 内存泄漏; Q2. 发送循环定时消息会越发越多; Q3. 子线程如何创建 Handler;

针对上述问题,接下来一个一个分析。

A1. 内存泄漏是由于静态内部类或匿名内部类持有外部类引用(这是 JAVA 的特性,内部类持有外部类),一般出现的场景是在 Activity 中创建的 Handler 发送延时消息,在该延时消息被处理之前,关闭了 Activity。此时引用链为:Activity 被 Handler 持有引用,Handler 被赋值给 Message 的 target,Message 被插入到 MessageQueue 中,MessageQueue 在 Looper 中创建而被持有引用,Looper 是存储在 ThreadLocal 中的,该 ThreadLocal 是当前 Thread 独有,而 Thread 与 Activity 是不同的生命周期。当 Activity 的生命周期结束,MessageQueue 中还存在未处理的消息时,上面的引用链即使 GC 时也会一直存在,导致 Activity 的对象不允许被回收,使其界面在销毁时内存迟迟不能释放,这就造成了内存泄漏。规避该问题的使用示例如:

    private class UpdateHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {

        // 静态内部类弱引用持有外部 Activity
        private val outer: WeakReference<MainActivity> = WeakReference(activity)

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            sendDelayed()
        }

    }

    override fun onDestroy() {
        super.onDestroy()

        // 在页面销毁时,移除掉 handler 中的消息,避免内存泄漏
        handler.removeMessages(MSG_NEXT)
        handler.removeCallbacksAndMessages(null)
    }

A2. 循环定时消息是在 handleMessage() 处理消息后再次调用 handler.sendMessageDelayed(),这样达到循环定时的效果。而业务逻辑中可能有好几个地方又调用了 handler.sendMessageDelayed(),没有合理调用导致消息越发越多,规避方式示例如:

    fun sendDelayed() {
        // sendMessageDelayed() 之前判断是否存在该条消息,存在的话就先移除
        if (handler.hasMessages(MSG_NEXT)) {
            handler.removeMessages(MSG_NEXT)
        }

        handler.sendEmptyMessageDelayed(MSG_NEXT, 5000)
    }

A3. 子线程创建 Handler,首先创建 Looper,因为 Handler 中 send/post 消息都是围绕 Looper 中的 MessageQueue 来进行的。而主线程不需要创建是因为在系统中已经提前创建好了,在 ActivityThread#main() 方法中可以看到:

    public static void main(String[] args) {
        ...

        Looper.prepareMainLooper(); // 主线程 Looper 创建

        ...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        ...
        Looper.loop(); // Looper 启动

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

子线程创建可以参考 HandlerThread 的源码。其对 Looper 的创建如下:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare(); // Looper 创建
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop(); // Looper 启动
        mTid = -1;
    }

参考 HandlerThread,自己在子线程创建 Handler:


    private fun startLooperThread() {
        val thread = LooperThread().apply {
            start()
        }
        ...
    }

    class LooperThread : Thread() {

        var handler: Handler? = null

        override fun run() {
            Looper.prepare() // Looper 创建

            Looper.myLooper()?.let { // Handler 创建
                handler = object : Handler(it) {

                    override fun handleMessage(msg: Message) {
                        super.handleMessage(msg)
                        // 处理消息
                    }
                }
            }

            Looper.loop() // Looper 启动
        }
    }

1.4 使用示例

对上面的代码片段,做一个总结示例如下:

class MainActivity : AppCompatActivity() {

    private val handler = UpdateHandler(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        checkData()
        sendDelayed()
    }

    override fun onDestroy() {
        super.onDestroy()

        // 在页面销毁时,移除掉 handler 中的消息,避免内存泄漏
        handler.removeMessages(MSG_NEXT)
        handler.removeMessages(MSG_UPDATE)
        handler.removeCallbacksAndMessages(null)
    }

    private fun checkData() {
        CoroutineScope(Dispatchers.Default).launch {
            try {
                // 此处进行 IO 操作
                ...

                val msg = Message.obtain().apply {
                    what = MSG_UPDATE
                    obj = "Hello World"
                }
                handler.sendMessage(msg)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    fun sendDelayed() {
        updateAnimator()

        if (handler.hasMessages(MSG_NEXT)) {
            handler.removeMessages(MSG_NEXT)
        }

        handler.sendEmptyMessageDelayed(MSG_NEXT, 5000)
    }

    private fun updateAnimator() {
        // 更新动画
    }

    fun updateUI() {
        // 更新 UI
    }

    private class UpdateHandler(activity: MainActivity) : Handler(Looper.getMainLooper()) {

        // 静态内部类弱引用持有外部 Activity
        private val outer: WeakReference<MainActivity> = WeakReference(activity)

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)

            if (msg.what == MSG_NEXT) {
                outer.get()?.sendDelayed()
            }

            if (msg.what == MSG_UPDATE) {
                outer.get()?.updateUI()
            }
        }

    }
}

private const val MSG_NEXT = 1
private const val MSG_UPDATE = 2

2. 源码探索

Handler 机制主要是 Handler、Message、MessageQueue 和 Looper 这几个部分,但是在分析过程中发现 ThreadLocal 也是一个非常重要的概念,它实现了数据隔离,维护线程安全。理解它,有助于我们理解 Looper 的唯一性问题。下面对各个部分的源码进行分析探索。

2.1 Handler

2.1.1 对象创建

创建 Handler 的方法有好几个,其中最重要的两个成员变量就是 mLooper 和 mQueue。mLooper 就是当前线程的 Looper,它是唯一的,至于为什么,会在后面的 Looper 部分进行分析,而 mQueue 是当前 Looper 中的 MessageQueue,它也是唯一的。并且 mLooper 和 mQueue 都必须赋非空值。

在即将废弃 (@Deprecated) 的两个 Handler 构造方法中,给 mLooper 赋值后会判断其是否为空。若为空的话,会直接抛出 RuntimeException:

    // 即将废弃 (@Deprecated) 的两个 Handler 构造方法,最终调用到这个方法
    public Handler(@Nullable Callback callback, boolean async) {
        ...

        mLooper = Looper.myLooper(); // 获取该 handler 所在线程的 looper,赋值给成员变量 mLooper
        if (mLooper == null) { // mLooper 为空,抛出 RuntimeException
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; // 获取 mLooper 的 MessageQueue,赋值给成员变量 mQueue
        mCallback = callback; // 全局的 mCallback 回调
        mAsynchronous = async; // 全局的异步标记
        mIsShared = false;
    }

非废弃的构造方法倒是直接赋值 looper,没有非空判断(但使用了 @NonNull 注解),但是接下来的 mQueue = looper.mQueue; 这句会直接对 looper 抛出 NullPointerException:

    // 非废弃的 Handler 构造方法,最终调用到这个方法
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,
            boolean shared) {
        mLooper = looper; // 将 looper 赋值给成员变量 mLooper
        mQueue = looper.mQueue; // looper 为空的话,会抛出 NullPointerException
        mCallback = callback; // 全局的 mCallback 回调
        mAsynchronous = async; // 全局的异步标记
        mIsShared = shared;
    }

而 mQueue 为空的话,会在发送消息时抛出 RuntimeException。

    // send/post 消息,会调用到这两个 send 方法
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) { // queue 为空的话,抛出 RuntimeException
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis); // 最后调用该私有方法将消息插入 MessageQueue
    }

    // send/post 消息,会调用到这两个 send 方法
    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        MessageQueue queue = mQueue;
        if (queue == null) { // queue 为空的话,抛出 RuntimeException
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, 0); // 最后调用该私有方法将消息插入 MessageQueue
    }
  • Tip1. 判断当前线程是不是主线程,可以通过下面的方式:
    Looper.myLooper() == Looper.getMainLooper()
    Looper.getMainLooper().getThread() == Thread.currentThread()
    Looper.getMainLooper().isCurrentThread()

2.1.2 消息发送

最终所有的 send/post 到最后都会调用到下面这个 enqueueMessage() 私有方法。在这个方法中,会将发送消息的 handler 赋值给该条 msg 的 target 成员变量,最后调用 queue.enqueueMessage(); 插入 MessageQueue 队列。

    // 将消息插入到 MessageQueue 中,是 Handler 中真正发送消息的核心方法
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this; // 把调用该方法的目标 handler 赋值给该 msg 的 target 成员变量
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) { // 若全局异步标记为 true,则将该 msg 设置为异步消息
            msg.setAsynchronous(true); // 标记为异步消息
        }
        return queue.enqueueMessage(msg, uptimeMillis); // 将 msg 按 uptimeMillis 时间插入 MessageQueue
    }
  • Tip2. 在线程中创建多个 Handler,每个 Handler 在 send/post 消息后,是怎么能够收到对应消息的呢?这是因为 Message 中有个成员变量 target,表示发送该条消息对应的 Handler,而 Looper 取出的消息就是通过这个 target 分发的。上层代码在包装 Message 的时候,不用对 target 赋值,Handler 内部在插入 MessageQueue 之前会统一赋值。

注意该方法的形参 uptimeMillis,这个参数的值最后会赋给 Message 的 when,决定了在 MessageQueue 的插入位置,按升序插入,值越小,插入位置越靠前,就越先被 Looper 取出。若这个值相等怎么插入呢,在后面 MessageQueue 部分会有分析。

uptimeMillis 的设置,在 sendMessageDelayed() 方法里有所体现,就是 SystemClock.uptimeMillis() + delayMillis。其中 SystemClock.uptimeMillis() 是指开机到现在的时长(毫秒级),是一个相对时间;delayMillis 就是延时多久被目标 Handler 处理,两个数值的和赋值给 Message 的 when。

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  • Tip3. 当调用 postAtTime() 或者 sendMessageAtTime() 时,形参 uptimeMillis 不增加 SystemClock.uptimeMillis() 时长而直接赋值一个 delayMillis 可不可以呢,答案是可以。甚至可以设为0,也就是 sendMessageAtFrontOfQueue()/postAtFrontOfQueue() 方法的实现了,这会将 Message 插入到 MessageQueue 的最前面,从而会被 Looper 最先取出执行。需要注意的是,uptimeMillis 不以系统开机时长作为参考值的话,不建议发送定时或者循环消息,因为 MessageQueue#next() 取消息是以系统开机时长作为参考值来取的,没有开机时长作为参考,达不到定时或者循环效果。

使用 post 发送消息时不用包装 Message,因为 post 方法中会从消息缓存池中取出一条消息进行包装,将 runnable 赋值给该消息的 callback 成员变量,然后调用 send 去发送。

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

注意 Handler 中有个 mAsynchronous 异步标记,用来发送异步消息,关于异步消息在后面介绍 MessageQueue 的同步屏障机制时进行分析。需要理解的是,当我们通过静态方法 createAsync() 创建 Handler 时,会将异步标记 mAsynchronous 设为 true,也就是说该 Handler 所发送的所有消息都将会是异步消息。

    // 创建 Handler 对象,用于发送异步消息
    @NonNull
    public static Handler createAsync(@NonNull Looper looper) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        return new Handler(looper, null, true);
    }

    @NonNull
    public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        if (callback == null) throw new NullPointerException("callback must not be null");
        return new Handler(looper, callback, true);
    }

2.1.3 消息处理

消息处理的核心方法就是 dispatchMessage(),该方法会在 Looper#loopOnce() 中调用:

    public static void loop() {
        ...

        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next();
        if (msg == null) {
            return false;
        }

        ...

        try {
            msg.target.dispatchMessage(msg); // 这里调用
            ...
        } catch (Exception exception) {
            ...
        } finally {
            ...
        }

        ...

        return true;
    }
  • Tip4. 这里需要注意,上面 loopOnce() 中的代码是顺序执行的,每 next() 遍历一条消息,然后 dispatchMessage() 一条消息,而 loop() 又死循环调用 loopOnce(),意思就是每条消息需要被处理完了之后,才会遍历下一条消息。所以在处理消息时不要执行耗时较长的任务,这会阻塞后面消息的分发,阻塞的后果例如有:阻塞 view 绘制中的异步消息,严重时会掉帧,造成界面卡顿;若 MessageQueue 本身存在大量消息,或者又被插入大量消息,这会导致存/取消息时的遍历速度下降,影响性能;耗时任务可能导致 ANR。

dispatchMessage() 方法的源码如下:

    // 消息的分发处理,主要是在 Looper 中取出消息后,调用该方法
    // 该方法有三种处理消息的方式,根据创建 Handler 方式不同而进行不同处理
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg); // 方式一
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) { // 方式二
                    return;
                }
            }
            handleMessage(msg); // 方式三
        }
    }

现在结合示例来分析 dispatchMessage() 中的三种处理方式:

    val handler = (object : Handler(Looper.getMainLooper(), object : Handler.Callback {

        override fun handleMessage(msg: Message): Boolean {
            // 2. 这是方式二,若方式一没执行,则会执行此方式。判断 handler 的全局 mCallback 不为空,就会执行到此处
            return false // 当返回为 true 时,则不执行方式三
        }

    }) {

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            // 3. 这是方式三,最后才执行此方式
        }

    }).apply {

        sendEmptyMessage(1)

        post(object : Runnable {

            override fun run() {
                // 1. 这是方式一,会最先执行此方式。判断 msg 的成员变量 callback 不为空,则直接执行到此处,后续方式不执行
            }

        })
    }
  • Tip5. 在 A 线程中创建的 Handler,在其他线程通过该 Handler 发送消息,最终的消息处理都是在 A 线程中。这是因为该 Handler 中的成员变量 mLooper 和 mQueue 都是属于 A 线程的,其他线程发送的消息最终插入到这个 Handler 的 mQueue,而 mLooper 通过执行 Looper#loopOnce() 遍历这个 mQueue,最终调用 msg.target.dispatchMessage(msg); 将消息分发给该 Handler 处理。

2.2 Message

2.2.1 参数详解

Message 源码的参数在此做了详细解释:

    public int what; // 用于区分这个消息到底是属于谁来处理的
    public int arg1;
    public int arg2; // arg1 和 arg2 相对于 data 方法来讲,如果只是想携带 int 类型的数据时,使用成本更加低廉
    public Object obj; // 待传递任意类型的数据,但在进程间传递序列化的数据类时必须保证非空。建议传递其他数据时还是以 Bundle 类型的 data 为主
    // 注意:设计 obj 的主要目的是传递引用数据类型,而 Bundle 类型的 data 是可以一次性传递多种基本数据类型的载体

    /*package*/ static final int FLAG_IN_USE = 1 << 0; // 该消息为正在使用中的标记
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; // 该消息是同步屏障的标记
    @UnsupportedAppUsage
    /*package*/ int flags; // 标记位

    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when; // 什么时间去分发消息给 handler 处理

    /*package*/ Bundle data; // 待传递的 data 数据

    @UnsupportedAppUsage
    /*package*/ Handler target; // 目标 handler,也就是哪个 handler 发送的消息,就只能该 handler 处理这条消息

    @UnsupportedAppUsage
    /*package*/ Runnable callback; // 使用 post 方式发送消息时携带这个参数,当分发到该条消息,handler 通过这个 callback 处理消息

    @UnsupportedAppUsage
    /*package*/ Message next; // 消息的缓存池策略是链表结构,next 是指向下一条数据的指针

    private static Message sPool; // 当前消息对象,也是链表表头
    private static int sPoolSize = 0; // 当前缓存池数据量大小

    private static final int MAX_POOL_SIZE = 50; // 默认缓存池最大数量为50

    // 在回收消息时,若消息是在使用中状态,根据该值来确定是否抛出 IllegalStateException 异常;
    // 当 API 小于21时,该标记为 false
    private static boolean gCheckRecycle = true; // 缓存池检查标记

2.2.2 复用机制

由于 Handler 极为常用,为了节省开销,Android 给 Message 设计了复用机制,在 Message 内部维护一个消息缓存池,用于消息对象的复用,所以在使用过程中尽量复用 Message,减少内存消耗。可以使用这两种方式获取,避免每次都创建对象重新分配内存:

    Message.obtain()
    handler.obtainMessage()
  • Tip6. Message 复用池默认最大数量为 50,当复用池没有 Message 时,会新创建一个 Message 对象;假设复用池有 50 条 Message,短时间内被同时取用(isInUse() = true),此时复用池没有 Message 了,那就只能再去创建一个 Message 对象。

Message 的无参 obtain() 方法就是从消息池中取一个 Message 对象出来,其他的带参 obtain() 都会先调用该无参方法获取到对象后,再根据所带形参去修改成员变量。

    // 若缓存池中存在复用消息,则取出;否则创建一条消息
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) { // 缓存池不为空时,取出链表中的第一条数据
                Message m = sPool; // 取出当前链表中头指针的消息
                sPool = m.next; // 缓存池的头指针指向下一条数据
                m.next = null; // 断开取出的头指针的引用链
                m.flags = 0; // 清除标记位
                sPoolSize--; // 当前缓存池数据减1
                return m;
            }
        }
        return new Message(); // 否则创建一条消息
    }

源码中有个 next 对象,这就可以看出消息池是一个链表结构,为了便于理解,这里画了一个在复用池取消息的链表走向图:

2.jpeg

2.2.3 消息回收

将一个 Message 对象回收到消息池中,其核心方法就是 recycleUnchecked()。

    // 回收消息到缓存池,需要进行消息的使用状态检查
    public void recycle() {
        if (isInUse()) { // 消息回收时,判断消息若是使用中的状态
            if (gCheckRecycle) { // 若该标记为 true 时,则抛出 IllegalStateException
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return; // 使用中的消息不进行回收
        }
        recycleUnchecked();
    }

    // 直接回收消息到缓存池
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // 消息回收待下一次使用,所以标记该消息为使用状态,并且清除消息中的其他所有信息
        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; // 将该条回收消息的 next 指向缓存池链表的头指针
                sPool = this; // 将缓存池链表的头指针指向该条回收的消息
                sPoolSize++; // 当前缓存池数据加1
            }
        }
    }

同样的,消息回收也画了一个链表走向图:

3.jpeg

  • Tip7. 怎么去回收 Message 然后存到复用池的呢?其实 Looper 中每 loopOnce() 一次后都会在该方法结尾调用 recycleUnchecked(),将该 Message 内数据置空放到复用池;还有就是 MessageQueue 中消息入队时判断 Looper 是退出状态且消息标记为未使用时会回收消息,以及消息移除和同步屏障移除也会在方法结尾进行消息回收。

源码中消息的回收主要集中在 Looper 和 MessageQueue 中:

  1. 在 Looper#loopOnce() 中回收 Message 到复用池的调用如下,调用的是 recycleUnchecked() 方法:
    private static boolean loopOnce(...) {
        ...

        msg.recycleUnchecked(); // 这个方法的末尾调用

        return true;
    }
  1. MessageQueue#enqueueMessage() 方法中回收 Message,调用的是 recycle() 方法,若 Looper 被退出了,并且该消息是未被标记为使用中的状态,最终会被回收:
    boolean enqueueMessage(...) {
        ...

        synchronized (this) {
            ...

            if (mQuitting) { // Looper 中调用 quit() 方法后会将 MessageQueue 中的这个 mQuitting 标记设置为 true
                ...
                msg.recycle(); // 这里调用
                return false;
            }

        ...
        return true;
    }
  1. MessageQueue 移除消息的相关方法中回收 Message 调用的是 recycleUnchecked() 方法:
    void removeMessages(...) {
        ...

        synchronized (this) {
            Message p = mMessages;

            while (...) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked(); // 这里调用
                p = n;
            }

            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (...) {
                        Message nn = n.next;
                        n.recycleUnchecked(); // 这里调用
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

    // 下面的方法都有回收 Message
    void removeEqualMessages(Handler h, int what, Object object) { }

    void removeMessages(Handler h, Runnable r, Object object) { }

    void removeEqualMessages(Handler h, Runnable r, Object object) { }

    void removeCallbacksAndMessages(Handler h, Object object) { }

    void removeCallbacksAndEqualMessages(Handler h, Object object) { }

    private void removeAllMessagesLocked() { }

    private void removeAllFutureMessagesLocked() { }
  1. MessageQueue#removeSyncBarrier() 移除同步屏障的方法中回收 Message 调用的是 recycleUnchecked() 方法:
    @UnsupportedAppUsage
    @TestApi
    public void removeSyncBarrier(int token) {

        synchronized (this) {
            ...
            p.recycleUnchecked(); // 这里调用
            ...
        }
    }
  • Tip8. 不一定对每条 Message 的成员变量 target 都必须赋值。发送普通 Message,其 target 必须赋值,否则抛出异常。这是因为普通 Message 是通过 MessageQueue#enqueueMessage() 方法插入的,在该方法中首先就会去检查 target, 若其为空,就抛出 IllegalArgumentException 异常。但是发送同步屏障消息时,target 就没有设置,此时插入 Message 调用的是 postSyncBarrier() 方法,不会对其 target 进行非空检查。

MessageQueue#enqueueMessage() 方法将普通消息插入链表,若检查到 target 为空,抛出异常:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) { // target 为空的话,抛出 IllegalArgumentException
            throw new IllegalArgumentException("Message must have a target.");
        }

        ...
        return true;
    }

发送同步屏障消息,对 Message 标记为使用中状态,并只赋值了 when 和 arg1,但是没给 target 赋值:

    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain(); // 缓存池获取 msg,但是没对 msg.target 进行赋值
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            ...
            return token;
        }
    }

2.3 MessageQueue

MessageQueue 本质是一个链表类型的数据结构,用来存和取 Message。

2.3.1 消息存储

我们先看看存储消息的 enqueueMessage() 方法。

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) { // 发送普通 msg 时,目标 handler 为空,抛出异常
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) { // msg 被标记为使用中的状态时,抛出异常
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            if (mQuitting) { // Looper 调用 quit() 退出死循环后,此时再插入消息,会执行此处
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle(); // 将 msg 回收到缓存池
                return false; // 返回 false 表示插入消息失败
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages; // mMessages 是一个链表的表头,p 指向表头
            boolean needWake; // 是否需要唤醒的标记,native 层的唤醒机制
            if ( // 这个条件分支只要满足下面三个任一条件,就表示 msg 要插入到 MessageQueue 最前面
              p == null || // MessageQueue 为空
              when == 0 || // 待插入 msg 的 when 为0(当插入多条 when 为0的 msg,后插入的 msg 排在 MessageQueue 最前面)
              when < p.when // 待插入 msg 的 when 比头节点的 when 要小(两个 when 相等时,走后面的条件分支)
            ) {
                msg.next = p;
                mMessages = msg;
                // mBlocked 表示是否处于阻塞状态,阻塞的是 next() 方法
                needWake = mBlocked; // 需要唤醒的条件是:msg 插入表头时,此时 next() 正处于阻塞状态
            } else { // 这个条件分支表示 msg 插入到 MessageQueue 中间某个位置
                // 如果同时满足下面三个条件,将唤醒标记设为 true
                needWake = mBlocked && // next() 正处于阻塞状态
                           p.target == null && // MessageQueue 的表头是个同步屏障消息
                           msg.isAsynchronous(); // 待插入的 msg 是个异步消息
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    // 若代码执行到这里,上面设置的唤醒标志有可能被更改
                    if (needWake && // 上面的唤醒标志为 true
                        p.isAsynchronous() // 遍历到的 p 是个异步消息
                    ) {
                        needWake = false; // 将唤醒标志设为 false
                    }
                }
                msg.next = p;
                prev.next = msg;
            }

            if (needWake) {
                nativeWake(mPtr); // 调用 native 层的唤醒机制
            }
        }
        return true; // 返回 true 表示插入消息成功
    }
  • Tip9. 当待插入的消息的 when 与 MessageQueue 中某条消息的 when 相等时,其插入顺序是:当这个相等的 when 值为0的时候,后插入的会排在链表最前面;当该值不为0时,按插入的先后顺序,先插入的排前面,后插入的排后面。

对这个方法的分析可知,Message 插入分两种,一种是插入到 MessageQueue 最前面,一种是插入到中间某个位置。

  1. 当 MessageQueue 为空,或者插入的 msg 的 when = 0,或者 msg 的 when 小于表头的 when,就插入到最前面。此时如果 next() 正处于休眠阻塞状态,needWake 为 true,需要唤醒。此时可以思考一下,为什么要在这个时刻唤醒?取普通消息部分会有分析。

  2. 否则,遍历 MessageQueue,当 msg 的 when 小于遍历到的 p 的 when,将 msg 插入到这个 p 的前面。若这两个 when 相等的话,会继续往后遍历。或者遍历下来,没找到合适的位置,就插入到 MessageQueue 的尾部。将此种插入方式画图显示如下:

4.jpeg

上面的第一种情况,表示 MessageQueue 当前只有一条表头消息,待插入的 msg 插入到这条表头消息后面;

上面的第二种情况,表示 MessageQueue 当前有两条消息,待插入的 msg 插入到这两条消息中间;

上面的第三种情况,表示 MessageQueue 当前有多条消息,待插入的 msg 插入到 prev 和 p 这两条消息中间(p 有可能为 null,也就是 msg 插入到末尾);

分析一下唤醒标记的设置:

  1. 判断当 mBlocked && p.target == null && msg.isAsynchronous(); 这个条件不满足时,needWake 为 false;
  2. 当这个条件满足时,表示当前正在阻塞状态,并且表头是同步屏障消息,并且待插入的 msg 是异步消息,对第一种情况和第二种情况,执行后会 break,不会执行到 needWake && p.isAsynchronous() 这个条件分支,needWake 必定为 true;
  3. 对第三种情况,会对 needWake && p.isAsynchronous() 这个条件进行判断,只要是处于 msg 前面的所有消息中有异步消息,needWake 就为 false,若都没有异步消息时,needWake 还是为 true。

从这里可以总结一下插入消息后需要唤醒的条件:当 next() 处于阻塞状态,并且 MessageQueue 的表头是同步屏障消息,并且插入后的 msg 的前面要么是表头,要么前面所有的 Message 都不是异步消息,而 msg 本身是异步消息,也就是在同步屏障消息后插入这条 msg 的 when 是所有异步消息中最小的,就需要唤醒 next() 方法。此时可以思考一下,为什么要在这种情况唤醒?同步屏障机制中会有分析。

2.3.2 消息取出

2.3.2.1 普通消息的处理

接下来看看取出消息的 next() 方法。这里区分取普通消息,处理 IdleHandler,处理同步屏障机制,我们先分析取普通消息。

    Message next() {
        ...
        int pendingIdleHandlerCount = -1; // IdleHandler 的数量
        int nextPollTimeoutMillis = 0; // 休眠阻塞时长,也就是经过这个时长后会唤醒
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // 这个是 native 层的阻塞方法,会休眠 nextPollTimeoutMillis 这个时长后唤醒
            // 若 nextPollTimeoutMillis == 0,休眠 0ms,也就是不休眠,继续执行后面的代码
            // 若 nextPollTimeoutMillis > 0,有限期休眠,休眠这么久后自动被 native 层唤醒,继续执行后面的代码
            // 若 nextPollTimeoutMillis == -1,无限期休眠,不会自动唤醒,需要调用 nativeWake() 方法来唤醒
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis(); // 以开机时长作为参考值取消息
                Message prevMsg = null;
                Message msg = mMessages; // 取出头节点消息
                ...
                if (msg != null) {
                    if (now < msg.when) {
                        // 当前时刻小于 msg 设置的 when,将休眠时长设置为它俩的时间差
                        // 这次遍历完成后,下一次遍历会调用到 nativePollOnce(),休眠 nextPollTimeoutMillis 这个时长后自动唤醒
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else { // 这个条件分支表示:now >= msg.when,也就是说在 now 时刻取 msg 有可能会超过这个 msg 的 when
                        mBlocked = false; // 阻塞状态设为 false。消息都取出来了,没必要阻塞
                        if (prevMsg != null) { // prevMsg 与同步屏障机制相关,后面介绍
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next; // 将表头指向 msg 的下一条数据
                        }
                        msg.next = null; // 切断 msg 的链表指向
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg; // 返回 msg,此次 next() 就调用完毕
                    }
                } else {
                    // MessageQueue 中无消息可取出,将休眠时长设置为-1
                    // 这次遍历完成后,下一次遍历会调用到 nativePollOnce() 进入无限期休眠
                    nextPollTimeoutMillis = -1;
                }

                ...

                // 当代码能执行到此处,说明 MessageQueue 中,在 now 时刻没有消息可取出
                // 也就是执行的 msg == null 或者 msg != null && now < msg.when 的条件分支
                if (pendingIdleHandlerCount <= 0) { // 此时又没有 IdleHandler
                    mBlocked = true; // 将阻塞标志设为 true
                    continue; // 继续下一次遍历,就会执行 nativePollOnce() 进入有限期/无限期休眠
                }
                ...
            }

            ...
        }
    }
  • Tip10. 当我们 send/post 消息时设置的 when,消息不一定在指定的 when 时刻被处理,有可能存在延后。可能的原因:

  1. 如果处理前一条消息耗时较长,完成之后已经超过了 when;
  2. 或者在即将到 when 时刻之前,又被插入了一条需要耗时处理的消息;
  3. 或者同步屏障机制优先处理大量的即时异步消息;
  4. 或者同步屏障机制优先处理延时异步消息;
  5. 又或者线程失去了 cpu 时间片,当再次分配到时间片时已经超过了 when。

现在总结一下取普通消息的过程:

(1)nextPollTimeoutMillis 初始设为0,第一次遍历时,nativePollOnce() 不会进入休眠状态,执行后面的代码。

(2)若 msg != null 这个条件满足表示 MessageQueue 中有 Message,取出表头消息,若 now < msg.when 这个条件不满足表示这条消息的 when 比当前 now 时刻要小,那就更新阻塞标记 mBlocked 为 false,然后返回此条消息;

(3)若 now < msg.when 这个条件满足,那就更新一下休眠时长,并将阻塞标记 mBlocked 设为 true,继续处于阻塞状态;下一次遍历时,nativePollOnce() 休眠 nextPollTimeoutMillis 这个时长后再自动唤醒,此时一定能取出消息,执行(2);

(4)若 msg != null 这个条件不满足表示 MessageQueue 中没有 Message,将 nextPollTimeoutMillis 设为-1,并将阻塞标记 mBlocked 设为 true,下一次遍历时,nativePollOnce() 进入无限期休眠,阻塞在此处,不继续往下执行;

这里解释一下上面的 next() 阻塞时在 MessageQueue 表头前插入一条 Meaasge 时为什么要唤醒的问题:

  1. 上面处于阻塞状态时,若 MessageQueue 中没有 Message,此时插入一条消息进来,那么这就是第一条消息,就需要唤醒继续执行接下来的代码;
  2. 面处于阻塞状态时,若插入一条 when 为0的消息,这也是第一条消息,就需要唤醒上面的阻塞,及时取出这条消息返回给调用者;
  3. 上面处于阻塞状态时,若插入一条消息的 when 的时刻比表头消息的 when 时刻要早,这也是第一条消息,也需要唤醒上面的阻塞,要么更新休眠时长,要么返回消息。
2.3.2.2 IdleHandler 的处理

IdlerHandler 用来在消息分发的空闲时间处理一些任务。IdleHandler 的使用示例:

    private var idleHandler: MessageQueue.IdleHandler? = null
    private var isInited = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        idleHandler = object : MessageQueue.IdleHandler {

            override fun queueIdle(): Boolean {
                if (isInited) {
                    return true
                }
                // ...
                // isInited = true
                return false
            }
        }
    }

    override fun onResume() {
        super.onResume()
        idleHandler?.let {
            Looper.myQueue().addIdleHandler(it) 
        }
    }

    override fun onPause() {
        super.onPause()
        // isInited = false
        idleHandler?.let {
            Looper.myQueue().removeIdleHandler(it)
        }
    }

分析一下 next() 中的 IdleHandler 的处理:

    Message next() {
        ...

        int pendingIdleHandlerCount = -1; // 初始 IdleHandler 的数量设为-1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ...

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                ...

                if (pendingIdleHandlerCount < 0 // 初始值为-1,后面该值都会大于等于0,所以这个条件分支只会执行一次
                        && (mMessages == null || now < mMessages.when)) { // MessageQueue 为空,或者还没到表头消息的执行时间
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) { // 若这个条件满足,就不再执行后面 IdleHandler 的代码
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 这段代码在上面的死循环中只执行一次,因为接下来会将 pendingIdleHandlerCount 设为0
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null;

                boolean keep = false;
                try {
                    keep = idler.queueIdle(); // IdleHandler 执行 queueIdle() 后的返回值
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) { // 若 queueIdle() 的返回值为 false
                    synchronized (this) {
                        mIdleHandlers.remove(idler); // 删除列表中的这个 IdleHandler
                    }
                }
            }

            pendingIdleHandlerCount = 0; // pendingIdleHandlerCount 设为0

            // 注意这个值,不管前面设置了其大于0或者-1,执行到这里后会将其设为0,
            // 意味着下次遍历执行 nativePollOnce() 不会进入休眠状态
            // 这也很好理解,因为执行 IdleHandler 也会花费一定时间,所以需要再次更新一下休眠时长
            nextPollTimeoutMillis = 0;
        }
    }

现在总结一下 IdleHandler 的处理过程:

(1)能执行到 IdleHandler 的相关逻辑,就表明此次死循环遍历没取出消息,意味着设置了一个有限期/无限期休眠时长;

(2)若 pendingIdleHandlerCount < 0 这个条件满足时,再判断 mMessages == null || now < mMessages.when 这个条件也满足时,也就是 MessageQueue 为空,或者还没到表头消息的执行时间,就查询一下有没有 IdleHandler,并将 IdleHandler 的数量赋值给 pendingIdleHandlerCount;

(3)若 pendingIdleHandlerCount <= 0 这个条件满足,表示没有 IdleHandler,将阻塞标记 mBlocked 设为 true,就不再执行后面 IdleHandler 相关的逻辑了,继续下一次的遍历,下一次遍历就会进入休眠状态;

(4)若 pendingIdleHandlerCount <= 0 这个条件不满足,表示有 IdleHandler,那就遍历一遍 IdleHandler 列表,执行 queueIdle() 方法。若该方法的返回值为 false,就从列表中删除该 IdleHandler;若返回 true,该 IdleHandler 不会删除,意味着下次调用 next() 能再次执行 IdleHandler 相关逻辑,还会处理该 IdleHandler。

(5)将 pendingIdleHandlerCount 设为0,在下一次死循环遍历时不会再查询 IdleHandler 的数量,会执行(3)。也就是说调用 next() 一次,IdleHandler 列表也只会遍历一次,因为 IdleHandler 的执行也会耗费一定的时间,不能因为执行 IdleHandler 而影响到正常消息的分发处理,这也就是为什么在取不出消息时,也即在空闲时间去执行 IdleHandler 了。

(6)将 nextPollTimeoutMillis 设为0,因为执行 IdleHandler 会耗费一定时间,若该值不设为0,下一次死循环遍历时直接进入休眠状态,导致休眠时长不准确,所以该值设为0后,不会休眠阻塞,而是继续执行代码,要么取出消息,要么更新休眠时长。

  • Tip11. 当在界面绘制完后需要做一些少量非耗时任务时,如获取控件宽高、跑马灯等动画效果,可以使用 IdleHandler,但需要注意的是,在任务做完或者界面不可见时,及时将回调方法返回 false,或者调用 removeIdleHandler() 方法移除,否则可能会引起类似 "input dispatching timed out" 的 ANR 问题。
2.3.2.3 同步屏障机制

同步屏障机制就是阻塞同步屏障消息后面的普通消息的分发,只处理其后面的异步消息。当遍历到 MessageQueue 的表头是同步屏障消息时,不管其他的普通消息有多么靠前,都会优先取出 MessageQueue 中的异步消息,即使是延时的异步消息,甚至没有异步消息,宁愿有限期/无限期地休眠等待,都不会取出普通消息。

这样的机制会阻塞普通消息的处理,那它有什么用处呢?假设一个场景:MessageQueue 中存在大量比 now 时刻要小或者等于的 Message,而现在又需要向 MessageQueue 中发送一条 UI 绘制操作的 Message,若这条 Message 按照普通消息流程发送的话,它可能会被延迟很长一段时间才会被分发处理,这会导致绘制不及时造成界面卡顿。也许有人会说,将这条绘制消息的 when 设为0不就插入在最前面了吗?这里就涉及到手机屏幕的刷新频率(如 60Hz),也就是每隔一段时间(非常短,如 16.6ms)刷新一次屏幕,系统底层每经过这个刷新间隔就发一个 VSYNC 信号,收到此信号后才会发送绘制消息进行绘制,所以绘制消息会有所延迟。即使屏幕刷新间隔接近0,但是这样发送后,又立马发送了大量的 when 为0的消息呢?那这条绘制消息同样会被延后处理。而同步屏障机制可以让这条绘制消息被优先分发。

前面提到同步屏障机制会阻塞普通消息,所以正确的使用分三步:一是发送同步屏障消息;二是发送异步消息(但是如果异步消息比同步屏障消息优先执行,那这个异步消息与普通消息无异);三是等异步消息被取出来处理的时候优先移除同步屏障消息,异步消息处理完后就可以取普通消息去分发处理了。

前面有提到过,Message 的 target 为 null 的消息被称为同步屏障消息。插入同步屏障消息的源码如下:

    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain(); //这条 msg 没有被赋值 target
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) {
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token; // 这个值是用来移除同步屏障消息的索引
        }
    }

插入同步屏障消息到 MessageQueue 与插入普通消息没什么太大的区别,只不过没有调用 nativeWake() 方法,因为同步屏障消息单独使用没什么用处,反而会阻塞普通消息的处理。简要总结下插入流程:

(1)当插入的同步屏障 msg 的 when 为0时,此时 prev 为 null,将该 msg 插入到 MessageQueue 的最前面,同时将表头指针 mMessages 指向 msg;

(2)否则遍历 MessageQueue,找到 when >= prev.when 并且 when < p.when 的位置,将 msg 插入这两者之间。

  • Tip12. 由于同步屏障消息不一定会插入到表头,所以需要理解发送同步屏障消息后不表示立马就阻塞了普通消息,在这条同步屏障消息的 when 之前的所有消息是会被优先处理的。

接下来分析移除同步屏障消息:

    public void removeSyncBarrier(int token) {// 这个 token 就是插入同步屏障消息后返回的索引
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) { // 没找到同步屏障消息,抛异常
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked(); // 消息回收

            if (needWake && !mQuitting) {
                nativeWake(mPtr); // 唤醒
            }
        }
    }

总结下移除流程:

(1)遍历 MessageQueue,找到一条 target 为 null,同时 arg1 为给定的 token 的 p,这个条件 (p.target != null || p.arg1 != token) 其实等价于 !(p.target == null && p.arg1 == token)。从这里能看出,可以向 MessageQueue 中发送多条同步屏障消息;

(2)若 p == null 这个条件满足,抛出 IllegalStateException 异常。这个 null 表示没找到同步屏障消息,甚至有可能是整个 MessageQueue 为空;

(3)若 p == null 这个条件不满足,则判断 prev != null 这个条件是否满足,若满足,表示是在 MessageQueue 中间某个位置找到需要移除的同步屏障消息。它的前一节点指向 prev,此时设置唤醒标记 needWake 为 false,分析一下为什么没必要唤醒:

  1. 当处于消息分发状态,本身就是“醒着”状态了,那就没必要唤醒;
  2. 当处于休眠状态,这个休眠状态一定是在 prev 前面的某一个时间段,否则 prev 都被取出分发了,怎么还会有 prev 节点出来,也就是说休眠完后有可能取的是 prev 前面的节点,有可能取 prev 节点,移除该消息不会影响到休眠时长,所以没必要唤醒。

(4)若 prev != null 这个条件不满足,意味着前面的 while 循环没走进去,意味着 p 节点是头节点。分析一下是否需要唤醒:

  1. mMessages == null 条件满足,表示 p 节点的下一节点为空(也就是说整个 MessageQueue 只有这个头节点),needWake 设为 true,因为 MessageQueue 中只有这一个同步屏障消息时,就是根据的这条 msg 进入的无限期休眠,移除这条 msg 后唤醒一下,遍历发现 MessageQueue 为空了,会再次进入无限期休眠;
  2. mMessages.target != null 条件满足(这里隐含满足这个条件 mMessages != null),表示 p 节点的下一节点为普通/异步消息,needWake 设为 true,因为同步屏障消息的原因,休眠时长有可能是后面异步消息的 when,也有可能是无限期的,所以需要重新唤醒更新一下休眠时长,或者直接分发消息;
  3. mMessages.target != null 条件不满足(这里隐含满足这个条件 mMessages != null),表示 p 节点的下一节点为同步屏障消息,needWake 就设为 false,不需要唤醒,因为同步屏障机制中只有异步消息会影响休眠时长。从这里也能看出,可以向 MessageQueue 中发送多条同步屏障消息。

再来分析一下 next() 中同步屏障消息/异步消息的处理:

    Message next() {
        ...
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ...

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                ...
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) { // 找到一条异步消息,那这个 prevMsg 必定不为 null
                            prevMsg.next = msg.next;
                        } else {
                            ...
                        }
                        msg.next = null;
                        ...
                        return msg;
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
                ...
            }

            ...
        }
    }

普通消息的取出流程已分析过,这里只分析头节点不为空并且是个同步屏障消息的流程,总结下该流程:

(1)若 msg.target == null 条件满足,表示头节点是个同步屏障消息,就循环遍历 MessageQueue 找出第一条异步消息;

(2)若 msg != null 条件满足,表示找到异步消息,再判断 now < msg.when 条件是否满足,若不满足,阻塞标记 mBlocked 设为 false,更改 MessageQueue 的引用链,并断开这条异步消息的引用链后返回给调用者;

(3)若 now < msg.when 条件满足,则在下一次死循环遍历时进入有限期休眠,休眠完后唤醒执行(2);

(4)若 msg != null 条件不满足,表示没有找到异步消息,则设置 nextPollTimeoutMillis 为-1,在下一次死循环遍历时进入无限期休眠。

  • Tip13. 遍历到表头为同步屏障消息后,就一定能阻塞普通消息了吗?不一定,例如执行同步屏障消息后,还没取到异步消息,会进入休眠等待,但此时再发送一条 when 为0的消息,该消息会插入到表头,替换同步屏障消息的位置,同时它会唤醒执行,此时会遍历取出该消息进行分发。

前面 enqueueMessage() 有提到过,阻塞时,在同步屏障消息(同时也是表头)后面插入的异步消息 msg 的 when 是所有异步消息中最小的时候,就需要唤醒 next() 方法,现在来分析一下为什么:

  1. 假设当前没有异步消息,next() 因为同步屏障消息而进入无限期休眠状态,此时插入了一条异步消息 msg,那就需要唤醒更新休眠时长,要么立即取出该 msg,要么有限期休眠;
  2. 假设当前有异步消息,必定是处于有限期休眠状态,此时插入的一条异步消息 msg 的 when 比当前 MessageQueue 中第一条异步消息的 when 还要小,那肯定也需要唤醒更新阻塞时长了;
  3. 假设当前有异步消息,此时插入的一条异步消息 msg 的 when 比当前某些异步消息的 when 还要大,这不会影响前面异步消息的休眠时长,就没必要唤醒了。

现在简单介绍一下同步屏障机制的应用。Android 中主要应用在 view 的绘制上,其同步屏障机制的调用链如下,可以发现发送同步屏障消息,等待 VSYNC 信号到来后,发送异步消息,然后通过 Choreographer 回调执行 TraversalRunnable 的 doTraversal() 方法,该方法中会先移除同步屏障消息:

// viewRootImpl
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 发送同步屏障消息
            mChoreographer.postCallback( // 调用 postCallback 注册监听 VSYNC 信号
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }

    // 上面的 mTraversalRunnable
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal(); // VSYNC 信号到来后,会回调到这里
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // 移除同步屏障消息

            ...
            performTraversals(); // 执行绘制流程
            ...
        }
    }


// Choreographer
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }

    public void postCallbackDelayed(...) {
        ...
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

    private void postCallbackDelayedInternal(...) {
        ...
        synchronized (mLock) {
            ...
            // 添加 scheduleTraversals() 中的 mTraversalRunnable 到 mCallbackQueues
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now); // 立即执行
            } else { // 发送异步消息延迟执行,最终调用 scheduleFrameLocked()
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true); // 标记为异步消息
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                ...
                if (isRunningOnLooperThreadLocked()) { // 是当前线程
                    scheduleVsyncLocked(); // 申请 VSYNC 信号
                } else { // 不是当前线程,发送异步消息,最终调用 scheduleVsyncLocked()
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true); // 标记为异步消息
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                ...
            }
        }
    }

    private void scheduleVsyncLocked() {
        try {
            ...
            mDisplayEventReceiver.scheduleVsync(); // natvie 层申请 VSYNC 信号
        } finally {
            ...
        }
    }

    // 上面的 mDisplayEventReceiver
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        ...

        // 这里接收 VSYNC 信号
        @Override
        public void onVsync(...) {
            try {
                ...
                // 收到 VSYNC 信号后发送异步消息
                Message msg = Message.obtain(mHandler, this); // this 是 msg 的 callback(Runnable),执行下面的 run()
                msg.setAsynchronous(true); // 标记为异步消息
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            } finally {
                ...
            }
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData); // onVsync() 中发送的异步消息执行这里
        }
    }

    void doFrame(...) {
        ...
        try {
            ...
            // 执行 viewRootImpl 中 postCallback() 的回调
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameIntervalNanos);
            ...
        } finally {
            ...
        }
        ...
    }

    void doCallbacks(int callbackType, long frameIntervalNanos) {
        CallbackRecord callbacks;
        ...
        synchronized (mLock) {
            ...
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS); // 取出 mCallbackQueues
            if (callbacks == null) {
                return;
            }
            ...
        }
        try {
            ...
            for (CallbackRecord c = callbacks; c != null; c = c.next) { // 遍历 mCallbackQueues
                ...
                c.run(mFrameData); // 遍历到 viewRootImpl 中 postCallback() 的 mTraversalRunnable 就去执行
            }
        } finally {
            ...
        }
    }

限于篇幅,这里不再详细分析。感兴趣的可以继续去扒一扒 View 绘制相关源码,可以参考一下 这篇文章

需要注意的是,上层 App 不允许发送同步屏障消息(除非反射),只能发送异步消息。现在介绍一下发送异步消息的两种方式:

        @RequiresApi(Build.VERSION_CODES.P)
        Handler.createAsync(Looper.getMainLooper()) // 这个方法需要28及以上才能调用,以下的可以使用反射

        val msg = Message.obtain().apply {
            what = MSG_UPDATE
            obj = "Hello World"
            isAsynchronous = true // 将 msg 的异步标记设为 true
        }

什么场景发送异步消息呢?发送同步屏障消息后,若系统绘制任务的异步消息还没有及时发送出来,此时普通消息就会被屏蔽阻塞,而我们又有某些任务需要尽快处理,这种场景可以考虑发送异步消息。

  • Tip14. 发送异步消息需要注意:

  1. 如果发送的异步消息比系统发送的同步屏障消息要早,这条消息与普通消息无异;
  2. 如果发送的异步消息比 UI 绘制消息要晚,这条消息与普通消息无异,因为 UI 绘制之前会先移除同步屏障消息;
  3. 异步消息与普通消息一样,不要耗时处理任务,或者不要同时发送大量的异步消息,因为这会阻塞正常的异步 UI 绘制消息的处理,严重时掉帧;
  4. 耗时或者大量的异步消息也会影响到阻塞的普通消息,如果 MessageQueue 中阻塞了太多的普通消息,当同步屏障消息移除后,太多的消息导致从 MessageQueue 存/取消息时的遍历速度下降,影响消息的执行效率。
2.3.2.4 退出机制

退出逻辑的代码如下:

    void quit(boolean safe) {
        if (!mQuitAllowed) { // 主线程在 ActivityThread 中创建 Looper 时,将该值设为 false,不允许 App 调用退出
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true; // 退出标志设为 true,将不再向 MessageQueue 插入 Message。enqueueMessage() 有分析

            if (safe) { // 安全退出
                removeAllFutureMessagesLocked();
            } else { // 非安全退出
                removeAllMessagesLocked();
            }

            nativeWake(mPtr); // 不管有没有休眠,唤醒一次
        }
    }

    // 直接清空 MessageQueue 里的所有 Message,并将 Message 回收到缓存池
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

    // 调用 quit() 后,删除此刻之后的所有 Message
    private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else { // MessageQueue 中表头消息的 when 小于或者等于此刻 now 时
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) { // 遍历到尾节点的 next 为 null 了,后面没有可再清空回收的节点,后续代码不用再执行
                        return;
                    }
                    if (n.when > now) { // 遍历出 MessageQueue 中节点的 when 大于此刻 now 的 n 节点
                        break;
                    }
                    p = n;
                }
                // 下面的步骤是将 n 节点的上一节点 next 置空,断开引用链,然后删除 n 节点及后面的所有节点
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

removeAllMessagesLocked() 就是一把将 MessageQueue 中的所有 Message 全部清空回收,不再通过 Looper 分发了。

removeAllFutureMessagesLocked() 就是比 now 这个时刻点要小或者等于的 Message 不清空回收(前面有分析过,msg 的分发不一定在 when 时刻,有可能晚,所以存在小于 now 时刻的 msg),清空回收 now 之后的。

调用 quit() 的最后,不管有没有休眠阻塞,都会调用 nativeWake() 唤醒 next(),将 removeAllFutureMessagesLocked() 后剩下的 Message 取出来(这些剩余的 Message 一定是比 next() 中的 now 要小或者相等的)分发掉。

  • Tip15. 调用 quit(true) 方法,MessageQueue 中剩余的 Message 一定能分发完成吗?不一定,因为如果同步屏障消息没有正确移除,而又没有异步消息,或者异步消息的 when > now,这些剩余的 Message 就没机会执行了。见下面的代码分析,因为同步屏障的逻辑,next() 最终返回 null,Looper 中判断 next() 为 null 会退出循环,不再遍历 MessageQueue。
    // 调用 quit(true) 的执行逻辑
    Message next() {
        ...
        for (;;) {
            ...

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                ...
                if (msg != null && msg.target == null) {
                    ...
                    // 如果存在同步屏障消息,存在两种情况:
                    // 1. 没有异步消息,此时取出的 msg 为 null;
                    // 2. 或者取出了异步消息
                }
                if (msg != null) { // MessageQueue 中还能取出消息
                    if (now < msg.when) {
                        ...
                        // 如果存在同步屏障消息,而又取出了异步消息,且它的 when > now,执行这里后,代码继续往下执行
                    } else {
                        ...
                        // 如果没有同步屏障,代码能执行到这里,那 msg.when <= now 一定成立,return msg,消息还能继续分发处理
                        return msg;
                    }
                } else {
                    ...
                    // 存在同步屏障消息,没有取出异步消息,执行这里后,代码继续往下执行
                }

                // 如果上面没有 return msg 的话,执行到这里,死循环的最后一次遍历
                if (mQuitting) {
                    dispose();
                    return null;
                }

                ...
            }

            ...
        }
    }

2.4 Looper

2.4.1 对象创建

Looper 只有一个私有构造方法,外部对其初始化方式不是 new 一个 Looper 对象,而是通过静态方法 prepare() 去实现,并且利用 ThreadLocal 对象帮我们处理好 Looper 与 Thread 的关联。至于怎么让 Looper 与 Thread 关联上的,后续 ThreadLocal 部分会进行分析。

提供给 App 的只有一个无参的 prepare() 方法,一般是在子线程中初始化 Looper 时调用。prepare() 只能调用一次,因为该方法中会对存储在 ThreadLocal 中的 Looper 对象进行非空校验,若多次调用,会检查到成员变量 sThreadLocal 非空,此时就抛出 RuntimeException 异常。这就体现了 Looper 在 Thread 中的唯一性。当第一次调用 prepare() 时,创建一个 Looper 对象并存储在 sThreadLocal 中,而 Looper 的构造方法中会创建一个 MessageQueue,由于当前 Thread 只能有一个 Looper,那 MessageQueue 在 Thread 中也具有唯一性。

    // 提供给 App 使用的方法只有这个
    public static void prepare() {
        prepare(true);
    }

    // quitAllowed 参数表示是否允许 Looper 退出循环
    private static void prepare(boolean quitAllowed) {
        // 每个 Thread 中只有唯一一个 Looper,从这里就可以体现
        if (sThreadLocal.get() != null) { // 判断 sThreadLocal 存储的 Looper 非空,抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    // Looper 的私有构造方法
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed); // 由于 Looper 在 Thread 中的唯一性,MessageQueue 在 Looper 中也是唯一的
        mThread = Thread.currentThread(); // 获取当前 Looper 所在线程
    }

2.4.1 Looper 启动

前面有介绍 Looper 在子线程中的使用示例,需要创建并启动 Looper,而我们在主线程中不需要这样操作,这是因为源码 ActivityThread#main() 方法中已经帮我们处理好了,ActivityThread 中是通过调用 prepareMainLooper() 方法。如果在主线程非要再次创建 Looper 会有两层拦截抛出异常,一个是 prepare() 中抛出 RuntimeException,一个是 prepareMainLooper() 中抛出 IllegalStateException。

    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

Looper 的初始化除了上面准备一个实例对象,还需要调用 loop() 去启动 Looper 的循环。loop() 时判断 looper 对象为 null 的话,抛出 RuntimeException,如果非空,死循环调用 loopOnce() 方法,若 loopOnce() 为 false,则退出死循环。

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) { // loop() 执行前必须先 prepare(),否则在此处会抛出异常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        ...

        for (;;) { // 死循环不停从 MessageQueue 中取消息
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

从上一章节 MessageQueue 分析中可以知道无消息/未取出合适的消息时,next() 可能会休眠阻塞,等待合适的时机被唤醒,所以,除非 Looper 退出,否则会一直进行死循环。每取出一条消息就执行 Handler#dispatchMessage() 方法将消息分发给目标 Handler,然后通过该 Handler 去处理消息。

    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // 可能阻塞在此处
        if (msg == null) { // 无消息时,意味着 MessageQueue 退出,Looper 也随之退出
            return false;
        }

        // 开始分发消息的日志。这个日志可以辅助我们分析 UI 的频繁绘制
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what);
        }
        ...
        
        try {
            msg.target.dispatchMessage(msg);// 分发 msg 给对应的 target(Handler) 去处理消息
            ...
        } catch (Exception exception) {
            ...
        } finally {
            ...
        }
        ...

        // 结束分发消息的日志。这个日志可以辅助我们分析 UI 的频繁绘制
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        ...

        msg.recycleUnchecked(); // 回收 msg 进缓存池

        return true;
    }

2.4.1 Looper 退出

允许 App 在子线程中退出 Looper 循环,其退出逻辑是:

(1)调用 Looper#quit()/quitSafely() 方法:

    // 直接退出 Looper,MessageQueue 中对其 quit() 方法有分析
    public void quit() {
        mQueue.quit(false);
    }

    // 安全退出 Looper,MessageQueue 中对其 quit() 方法有分析
    public void quitSafely() {
        mQueue.quit(true);
    }

(2)调用 MessageQueue#quit(),将 MessageQueue 的成员变量 mQuitting 置于 true:

    void quit(boolean safe) {
        ...

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            ...
        }
    }

需要注意此时再 send/post 消息会返回 false,并且不会将 Message 插入到 MessageQueue。

    boolean enqueueMessage(Message msg, long when) {
        ...

        synchronized (this) {
            ...

            if (mQuitting) { // mQuitting 为 true 时,msg 不会再插入 MessageQueue
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle(); // 回收
                return false; // 返回 false,表示消息插入失败
            }

            ...
        }
        return true;
    }

(3)MessageQueue#next() 会返回 null:

    Message next() {
        ...
        for (;;) {
            ...

            synchronized (this) {
                ...
                
                if (mQuitting) {
                    dispose();
                    return null;
                }

                ...
            }

            ...
        }
    }

(4)Looper#loopOnce() 判断 MessageQueue#next() 为 null 时,返回 false:

    private static boolean loopOnce(...) {
        Message msg = me.mQueue.next(); // 遍历 MessageQueue 获取 msg
        if (msg == null) {
            return false; // msg 为 null,返回 false
        }
        ...

        return true;
    }

(5)Looper#loop() 判断 Looper#loopOnce() 为 false 时退出循环:

    public static void loop() {
        ...

        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }
  • Tip16. Looper 中的死循环可能会导致线程无法运行完毕,但是子线程在任务执行完后希望能销毁。所以子线程中创建 Looper,在任务结束后,应及时调用 quit()/quitSafely() 方法结束消息的分发,退出死循环。

主线程是希望 Looper 的循环一直存在不退出的,主线程创建 Looper 最终调用 prepare(false); 方法,会给 MessageQueue 的成员变量 mQuitAllowed 赋值为 false,当在 App 的主线程中强制调用 quit()/quitSafely() 方法时,则会在 MessageQueue#quit() 抛出一个 IllegalStateException 异常。

    void quit(boolean safe) {
        if (!mQuitAllowed) { // 主线程创建 Looper 后,给 MessageQueue 的 mQuitAllowed 赋值为 false
            throw new IllegalStateException("Main thread not allowed to quit."); // 主线程调用 quit() 抛出异常
        }

        ...
    }

需要强调的是,主线程 Looper 的退出是在 ActivityThread 的内部类 H 中会有调用 Looper#quit() 方法。这里的调用意味着 App 需要退出了。

class H extends Handler {
    public static final int EXIT_APPLICATION        = 111;
    ...
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            ...
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit(); // 这里调用退出主线程的 Looper
                break;
            ...
        }
    }
  • Tip17. 当出现用户感知的 ANR 问题时,一个可能的原因就是界面不可见时还在频繁的进行 UI 绘制。这里提供一个思路,可以使用下面这个方法解决部分问题: Looper.getMainLooper().setMessageLogging(LogPrinter(Log.DEBUG, "TEST_SIVAN"))

在控制台过滤出 "TEST_SIVAN" 的日志,当某个界面不可见时,若还带有大量 Choreographer 的日志打印出,则页面有可能存在频繁绘制。然后在代码中查看是否有设置 Animation/Animator 为无限循环的动画,追踪这些动画的源码就会发现其最终调用到 ViewRootImpl#scheduleTraversals() 方法,在上一章节有了解到,这里设置了同步屏障,然后发送异步消息,其目的就是为了让 UI 消息第一时间能够得到处理,提高用户体验。如果大量的 UI 绘制消息在页面不可见时还在频繁发送,这会导致出现用户感知的 ANR 问题。通过 Looper#setMessageLogging() 可以辅助分析此类问题。

19:08:29.052  6906-6906  TEST_SIVAN  com.sivan.test  D  >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {c6520b1} android.view.Choreographer$FrameDisplayEventReceiver@eb0996: 0
19:08:29.059  6906-6906  TEST_SIVAN  com.sivan.test  D  <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {c6520b1} android.view.Choreographer$FrameDisplayEventReceiver@eb0996
19:08:29.068  6906-6906  TEST_SIVAN  com.sivan.test  D  >>>>> Dispatching to Handler (android.view.Choreographer$FrameHandler) {c6520b1} android.view.Choreographer$FrameDisplayEventReceiver@eb0996: 0
19:08:29.076  6906-6906  TEST_SIVAN  com.sivan.test  D  <<<<< Finished to Handler (android.view.Choreographer$FrameHandler) {c6520b1} android.view.Choreographer$FrameDisplayEventReceiver@eb0996

2.5 ThreadLocal

前面有提到 Looper 与 Thread 绑定,该绑定是通过 ThreadLocal 实现的,接下来分析一下这种绑定关系。其实 ThreadLocal 本身不存储值,而是通过 ThreadLocalMap 来存储,就是类似 HashMap 的数据结构。ThreadLocal 作为 ThreadLocalMap 中 (key, value) 的 key 值,而真正的存储对象是 value 值。ThreadLocal 具体的源码分析,限于篇幅,这里不展开了,可以看看 这篇文章,本文只简单介绍,重点是分析一下 Handler 机制中 Looper 与 Thread 是怎么绑定的。

ThreadLocal 为每个线程分配独立的空间存储资源副本,这是以空间换时间,从根本上避免发生资源冲突,实现线程间的数据隔离,是一种无锁的线程安全方式。我们可以理解为同一个变量在其他线程中它的值不管怎么发生变化,在本线程中它的值不会跟着改变,举例来说:

class ThreadLocalTest {

    // java 中建议使用 static 修饰,kotlin 中使用伴生对象 companion object
    companion object {
        private val threadLocal = object : ThreadLocal<Int>() {
            override fun initialValue(): Int {
                return 0
            }
        }
    }

    // 建议使用 static 修饰,参考 Looper 中 sThreadLocal 的写法
//    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
//        protected Integer initialValue(){
//            return 0;
//        }
//    };

    public fun testValue() {
        for (i in 0 .. 5) {
            Thread {
                val num = threadLocal.get() ?: 0
                threadLocal.set(num + 2)
                Log.d("ThreadLocalTest", "current is $i, and the num is ${threadLocal.get()}")
            }.start()
        }

        // 这里延迟 300ms 是让前面 Threads 先运行完
        Handler(Looper.getMainLooper()).postDelayed({
            Log.d("ThreadLocalTest", "in main thread, the num is ${threadLocal.get()}")
        }, 300)
    }

    // 子线程中使用完后,建议调用此方法,避免内存泄漏
    public fun clear() {
        threadLocal.remove()
    }
}

打印结果如下:

11:06:02.983  5002-5287  ThreadLocalTest  com.sivan.test  D  current is 1, and the num is 2
11:06:02.984  5002-5291  ThreadLocalTest  com.sivan.test  D  current is 5, and the num is 2
11:06:02.984  5002-5286  ThreadLocalTest  com.sivan.test  D  current is 0, and the num is 2
11:06:02.984  5002-5289  ThreadLocalTest  com.sivan.test  D  current is 3, and the num is 2
11:06:02.984  5002-5288  ThreadLocalTest  com.sivan.test  D  current is 2, and the num is 2
11:06:02.985  5002-5290  ThreadLocalTest  com.sivan.test  D  current is 4, and the num is 2
11:06:03.282  5002-5002  ThreadLocalTest  com.sivan.test  D  in main thread, the num is 0

在查看源码的时候发现 threadLocalHashCode 这个索引的实现很有意思。这里稍微分析一下,它在 ThreadLocalMap 中作为 private Entry[] table; 这个数组下标的参考值。通过 nextHashCode() 静态方法获取原子整型 nextHashCode 的值返回给这个索引,同时 nextHashCode 本身也会自增,增量是散列值 HASH_INCREMENT。可以看到 nextHashCode 是 static 修饰的,所有类实例都会共享这个静态变量,举例来说就是创建多个 ThreadLocal<?>() 对象,它们的 threadLocalHashCode 值都不一样,是 HASH_INCREMENT 的倍数。

    // 常量字段,用于 ThreadLocalMap 的索引,每个 ThreadLocal 对象创建之初会被赋值
    private final int threadLocalHashCode = nextHashCode();
    // 静态字段,为类所有,所有类实例共享这个静态变量,初始值为0
    // 也就是不管创建多少个 ThreadLocal 对象,该变量只创建一次
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    // 散列值,对2的幂次方的哈希表来说,该值分布最佳,散列性最好
    private static final int HASH_INCREMENT = 0x61c88647;
    // 静态方法, 给常量字段 threadLocalHashCode 赋值; 同时根据上面的散列值更新 nextHashCode 的值
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

其实 threadLocalHashCode 就类似于 Object#hashCode(),为什么没有重写它呢?这是因为重写会对所有需要使用该方法的地方都生效,而该值用在其他地方的话散列性不一定很好。这个值是专门针对 ThreadLocalMap 设计的,ThreadLocalMap 的数组容量(初始容量也好,扩容也好)是2的整数幂,threadLocalHashCode 对 ThreadLocalMap 来说散列性最好。举例来说假设这样写:

public class ThreadLocal<T> {

    ...

    // ThreadLocal 源码中未重写 hashCode(),这里只是个示例
    // 重写的话会对所有使用它的地方都生效
    @Override
    public int hashCode() {
        return threadLocalHashCode; // 该值用在其他地方的话,散列性不一定表现好
    }
}

先回顾一下 Looper 中与 ThreadLocal 相关的内容,主要是调用 set/get 方法。

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

介绍下 ThreadLocal 的 get 方法:

  1. 查找当前 Thread 中的成员变量 threadLocals,若存在,根据当前 threadLocalHashCode 查找目标 Entry(key, value),若查找到就返回 value 值;
  2. 若 threadLocals 不存在,或者没有查找到 value 值,就调用初始化方法 setInitialValue();
  3. 再次查找当前 Thread 中的成员变量 threadLocals,若存在,就将 ThreadLocal 对象本身及其初始化值 (initialValue()) 以 Entry(key, value) 的形式保存到 threadLocals 中,并返回这个初始化值;
  4. 若不存在这个 threadLocals,就先初始化这个 threadLocals,然后将 ThreadLocal 对象本身及其初始化值 (initialValue()) 以 Entry(key, value) 的形式保存到 threadLocals 中,并返回这个初始化值。
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 获取当前线程中的 ThreadLocalMap
        if (map != null) { // 若当前线程的该 map 不为空,继续下面判断
            // 根据 ThreadLocal 对象本身的 threadLocalHashCode 作为下标参考值,
            // 查找 map 中数组元素为 (key, value) 的 Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) { // 若这个 Entry 存在的话,返回其 value 值
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 若当前线程中 ThreadLocalMap 为空,或者没查到 Entry,返回 initialValue() 方法的初始值
        return setInitialValue();
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; // 获取给定线程中数据结构为 ThreadLocalMap 的成员变量 threadLocals
    }

    private T setInitialValue() {
        T value = initialValue(); // ThreadLocal 中可被重写的方法,用于初始化 (key, value) 的 value 值,默认为 null
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value); // 若 map 非空的话,将 ThreadLocal 对象本身和其初始值作为 (key, value) 存储在 map 中
        } else {
            createMap(t, value); // 若 map 为空的话,创建 ThreadLocalMap 对象赋值给当前线程的 threadLocals 成员变量
        }
        ...
        return value; // 返回 initialValue() 方法的值
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue); // 初始化创建当前线程的 threadLocals
    }

接着看看 ThreadLocal 的 set 方法,可以看到与 setInitialValue() 很相似,只不过该方法是存储初始化值 (initialValue()),而 set 方法是存储目标值:

  1. 首先获取当前 Thread 中的成员变量 threadLocals,若存在,就直接将 ThreadLocal 对象本身及目标 value 值以 Entry(key, value) 的形式保存到 threadLocals 中;
  2. 若不存在,就创建一个 ThreadLocalMap 对象对其初始化,然后将 ThreadLocal 对象本身及目标 value 值以 Entry(key, value) 的形式保存到 threadLocals 中。
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 获取当前线程中的 ThreadLocalMap
        if (map != null) {
            map.set(this, value); // 若 map 非空,将 ThreadLocal 对象本身和其初始值作为 (key, value) 存储在 map 中
        } else {
            createMap(t, value); // 若 map 为空,创建 ThreadLocalMap 对象赋值给当前线程的 threadLocals 成员变量
        }
    }

Thread 中有一个成员变量 threadLocals,这是线程隔离的关键。它是一个 ThreadLocalMap 类型的数据结构,其初始化赋值方式就是通过 ThreadLocal 中 set() 或者 setInitialValue() 方法。Looper 初始化时将 Looper 对象作为 value 值,通过调用 ThreadLocal 的 set/get 方法,包装成 ThreadLocalMap 的 Entry 结构,将其存储在 Thread 的成员变量 threadLocals 中。通过这种方式将 Looper 与 Thread 进行了绑定。

public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null; // 成员变量 threadLocals
    ...
}

ThreadLocalMap 本质是一个数组,存储 (key, value) 的 Entry 对象,其中 key 是 ThreadLocal 对象本身,value 就是我们设置的值,key 在 ThreadLocalMap 中是个弱引用,GC 后 key 不存在了,但是 value 还存在,成为了无效对象在占据内存,这就会造成内存泄漏,所以在使用时需要注意。

在 Android 中 ThreadLocal 的应用很广泛,除了 Looper,很多地方如系统源码:ActivityThread、ContentProvider、Choreographer、ViewRootImpl、ViewCompat、CoordinatorLayout...,优秀的开源库如:Gson、okhttp、lottie、EventBus...都有通过该类实现部分数据在线程间的隔离。使用时提供几个建议:建议使用static final修饰变量,可以保证所有类实例共享同一个静态变量(参考 Looper 中的写法);子线程使用完后尽量调用 remove() 方法清理局部数据存储,避免内存泄漏。

public final class Looper {
    ...

    // Looper 中用 static 修饰的 sThreadLocal 变量
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    ...
}

3. 总结

将 Handler、Message、MessageQueue、Looper、ThreadLocal 每个部分单独分析完后,有了局部的理解,再将这种局部理解系统地串联起来:

  1. 在当前 Thread 中初始化 Looper#prepare();
  2. 在 Looper 中对当前 Thread 的 ThreadLocal.ThreadLocalMap(threadLocals) 赋值 (key, value) = (ThreadLocal< Looper >, Looper),对 Thread 和 Looper 进行绑定;
  3. 在一个 Thread 中 Looper 只能初始化一次,否则抛出异常,因此 Looper 在 Thread 中具有唯一性,初始化 Looper 时会初始化一个 MessageQueue,因此 MessageQueue 也是唯一的;
  4. 在当前 Thread 中创建 Handler 并重写 handleMessage() 方法;
  5. 在当前 Thread 中开启 Looper#loop(),死循环遍历 MessageQueue#next(),无 Message 时阻塞;
  6. 在其他线程中调用当前 Thread 中创建的 Handler 对象来发送 Message,根据设置的延迟时间调用 MessageQueue#enqueueMessage() 插入到队列的合适位置;
  7. 在合适时机 MessageQueue 取出消息(或者被唤醒取消息),Looper 取出 Message 调用 Handler#dispatchMessage() 分发给对应的 Handler 处理;
  8. 当前 Thread 的 Handler 调用 Handler#handleMessage() 去处理消息;

这样就实现了不同线程之间的通信和消息传递。

限于篇幅,Handler 机制就分析到这里。其实 Handler 机制中还有很多逻辑细节可以再去深入思考。读书百遍,其意自见,每次阅读 Handler 源码,都会有新的理解。这里抛砖引玉,有了这样的分析辅助理解后,可以去探索更多的细节,甚至更深层次地去学习底层逻辑知识。