面试再也不怕 Handler 了,消息传递机制全解析

2,171 阅读4分钟

一、为什么要使用 Handler

众所周知,Android 不允许在子线程中更新 UI。但是我们在子线程完成耗时的操作之后,需要对界面数据进行更新,又该怎么处理呢?这时候,我们可以使用 Handler 进行 UI 更新。值得注意的是,更新 UI 我们需要把 Message 发送到主线程持有的 MessageQueue ,否则程序依然就会发生奔溃。

另外,除了更新 UI,Handler 是 Android 系统的消息传递机制,它定义了一套处理消息的规则,广播、服务以及线程间的通信都需要靠它来完成。

Handler 相关的还有 LooperMessageQueue,接下来我们就从它的使用开始分析,对这三剑客一网打尽。

二、Handler 发送消息的流程

Handler 发送消息有两种方式,一种是 sendMessage 的方式,一种是 post 的方式,通过对源码的阅读,post 的方式其实是调用到了 sendMessage 的方式。那我们就来看看 sendMessage 的流程吧。通过调用 sendMessage,最终会走到下面方法中:

image.png

这里做的事情很简单,必须满足 MessageQueue 不能为空,否则程序会抛出异常,接下来看 enqueueMessage 的流程:

image.png

在这里完成了两个重要的流程:

  • 为 msg 的 target 赋值,msg.target = this,因此这个 target 就是调用的 sendMessage 的 Handler。(记住这里的重点)
  • 调用了 MessageQueue 的 enqueueMessage 方法。

到目前为止,流程来到了 MessageQueue 中。现在看 MessageQueue 的 enqueueMessage 方法。

三、MessageQueue 的工作流程

由于 enqueueMessage 的方法比较长,我们这里不截图,直接看下面的代码:(省略部分代码)

boolean enqueueMessage(Message msg, long when) {
    // 1、target 不能为空,否则直接抛出异常
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 2、加锁,不能有多个 Handler 同时发送消息
    synchronized (this) {
        msg.when = when;
        Message p = mMessages; // 出队列的 msg 的下一个要出队列的 msg
        boolean needWake;
        // 3、下面这三种情况直接插在 head 节点上,(1)这个队列是一个空队列,
        // (2)这个 msg 需要立即处理,(3)是它需要处理的时间比即将出队列的节
        // 点的处理时间还要小
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            
            // 4、如果之前第三点的条件不满足,就会从 head 节点开始遍历,
            // 插入到一个合适的时间,或者链表的尾部,这个 for 循环做的其实就是
            // 链表节点的插入
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
        // 5、是否需要进行唤醒,在 queue.next() 方法中如果没有获取到 msg就会休眠
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

解释其实已经在上面的代码里了,下面来做一个简单归纳:

  • MessageQueue 其本质上一个单向链表,入队列这个操作进行了加锁的处理,不能多个 msg 同时入队列。
  • 在插入队列的时候,会根据当前队列是否为空,或者处理消息的时间选择合适的插入位置。
  • 最后判断是否需要进行 wake up

到目前为止,我们看了 Handler 的发送消息的流程,以及消息是如何插入链表的,那么消息是如何处理的呢?我们知道,只有调用了 Looper 的 loop() 方法之后,才能处理消息,那接下来看 Looper 的 loop() 方法。

四、Looper 的工作流程

Looper 的 loop() 方法也是相当长,接下来看代码:(省略部分代码)

public static void loop() {
    // 1、获取 Looper 对象,定进行判空处理
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    // 2、获取了 MessageQueue 对象
    final MessageQueue queue = me.mQueue;
    for (;;) {
        //  3、调用 MessageQueue 的 next(),返回值是 msg
        Message msg = queue.next(); // might block
        if (msg == null) {
            return;
        }        
        ....
        try {
            // 4、之前说过,在 SendMessage 的时候设置了 msg 的target,这个 target 就是调用 sendMessage 的 Handler
            msg.target.dispatchMessage(msg);
        } catch (Exception exception) {
        } finally {
        }

        msg.recycleUnchecked();
    }
}

代码本身很长,但是其实做的事情也不多,现在简单归纳一下:

  • 在调用 Looper.loop() 之前,必须先调用 Looper.prepare(),如果没有 Looper 对象的话程序会直接抛异常。
  • 通过调用 MessageQueue 的 next 方法不断的从队列里取消息出来。
  • 最后把 msg 交给 Handler 的 dispatchMessage() 进行处理。

通过源码我们可以发现调用 queue.next() 时可能发生阻塞,那这个方法又做了什么?还有,为什么要先调用 Looper.prepare(),这个方法又做了什么处理?先来看比较简单的吧:

image.png

这个 Looper.prepare() 其实是创建了一个 Looper 对象,并且通过 ThreadLocal 实现每个线程有且仅有一个这样的 Looper 对象。为什么要创建 Looper 呢?没有就不行吗?我们来看 Handler 的构造函数:

image.png

可以看到,如果 Looper 为空的话,程序直接抛异常。这个 myLooper() 是用来获取当前线程的 Looper 对象:

image.png

从时序上说,我们调用 Looper.prepare() 的时机必须在 new Handler() 之前。那么,我们主线程使用 Handler 的时候,并没有调用 Looper.prepare() 这个方法,这又是怎么回事呢?

原来,在 ActivityThread 的 main() 方法中已经为我们进行了处理:

image.png

这个 prepareMainLooper() 在内部调用了 Looper.prepare() 。到目前为止,我们解决了 Looper 的相关问题,说明了必须存在 Looper 的原因。现在还有一个问题没有解决,queue.next() 方法做了什么事情?它为什么发生阻塞呢?

接下来看 MessageQueue 的 next() 方法:(已省略部分代码)

Message next() {

    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 1、这是一个 native 方法,如果messageQueue 没有可以处理的消息就会休眠
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // 2、同步屏障,寻找队列中的下一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 3、下一个出队列的这个 msg 还没有到时间,并计算需要阻塞的时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 4、得到一个能够处理的msg,并返回这个 msg
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            ...
        }
    }
}

重要的点其实已经在上面说了,下面总结一下:

  • 获取 msg 的这个过程有可能会发生阻塞,具体调用到的是 native 的 nativePollOnce 方法
  • 获取消息的时候,有一个同步屏障,也就是对 msg 对应的 target(Handler) 为空的消息进行了过滤。
  • 如果能获取到一个 msg ,那么就返回这个 msg。

四、再看 Handler

先来梳理一下我们现在明白了什么:

  • 在创建 Handler 的时候,必须先创建 Looper 对象,之后还需要调用 Looper.loop() 方法才能让 Handler 开始工作。
  • 通过 Handler sendMessage 发送消息,其实是调用了 queue.enqueueMessage,这个 Queue 其实是一个单向链表,在调用这个方法的时候,会根据当前队列的转态以及 when 把这个 msg 插入到合适的位置。
  • queue.next() 可能会发生休眠,原因是拿到不到合适的 msg,在 queue.enqueueMessgae 的时候会判断是否需要唤醒。

之前我们说过,这个 msg 其实是交给了 Handler 的 dispatchMessage 去处理,下面来看一下 Handler 是怎么处理的:

image.png

  • msg.callback 是我们通过 post 方法传递进来的一个 Runnable 对象,如果我们没有使用 post 的话,就不会走到 handleCallback(msg) 中。
  • mCallback 是一个 CallBack 对象,如果我们在创建 Handler 的时候没有传这个参数,那么 mCallback 也是为null 的。
  • 最后才会走到 handleMessage(msg) 中。

另外,由于水平有限,有错误的地方在所难免,未免误导他人,欢迎大佬指正!码字不易,感谢大家的点赞关注!🙏