理解 Handler

android May 20, 2018

一年四度的面试季节,老生常谈的 Handler 源码问题。掌握 Handler 源码,可以说能为面试添上浓墨重彩的一笔。

那么今天这篇文章,就深入 Handler 的各个死角,让你对面试中 Handler 的问题应对自如。

  • 什么是 Handler
  • Handler, Looper, Thread 的关系
  • post 和 sendMessage 的区别
  • 一个 Looper 维护唯一的一个消息队列,一个 Looper 对应多个 Handler,那么 Looper 如何知道使用哪个 Handler 来处理对应消息。
  • Handler 有三种处理消息的方式,那么 Handler 如何选择以哪种方式来处理?
  • HandlerThread 的实现
  • 如何同步等待 message 处理完成

不急着回答上面的问题,先上一张图。

img

这张图很好地说明了 Looper 和 Handler 的关系。简单说明一下:

  • Message 是待处理的消息。
    • sendMessage 传入的参数
    • post 传的 Runnable 也会被自动包装成 Message。
  • MessageQueue 是存储这些消息的单链表
  • Looper 翻译过来是循环者,顾名思义,他就是做一个无限循环,然后在循环中不断地取消息,处理消息。
  • Looper 维护线程唯一的一个 MessageQueue
  • 这套机制的运转流程是:
    • 线程 A 中,Handler 把消息交给 Looper,Looper 把消息放到 MessageQueue 的栈底
    • Looper 所属线程中,Looper 从 MessageQueue 顶部取 Message
    • Looper 把消息交还给 Handler 处理
// Handler.java
// 所有的 sendMessage/post 方法最终都会调用这个方法
// queue 参数即 Looper.mQueue, 也就是上图中的 MessageQueue 消息队列
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

// Looper.java
// 这个方法会一直在 Thread.run() 方法里面循环、循环,从消息队列里取消息、把消息交给 Handler 处理。天地翻覆,始终如一。
// msg.target 即是把它加入队列的 Handler 的引用
public static void loop() {
    // 获取线程唯一的 Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    
	...
        
    for (;;) {
        Message msg = queue.next(); // might block
     	...
        // target 在 Handler.enqueueMessage() 方法中设置
        msg.target.dispatchMessage(msg);
        ...
    }
    ...
}

那么什么是 Handler?

很明了了。上面提到了 Handler 的两个作用,一个是使用 post 或 sendMessage 方法把消息加入到消息队列。另外一个是处理 Looper 交还给 Handler 的消息。

所以我们可以说:

Handler 是消息队列的代理人,负责提供消息给消息队列,并处理消息队列中的消息。

Thread Looper Handler 三者究竟是什么关系呢?

Thread 负责提供 Looper,Looper 提供和维护消息队列,Handler 则面向消息队列,提供消息、处理消息。

一个 Thread 对应至多一个 Looper,而一个 Looper 对应多个 Handler。

post 和 sendMessage 的区别

查看源码,post 其实也是调用了 sendMessage 的相关方法。post 把自己的 Runnable 参数封装成 Message 添加到消息队列。当 Looper 处理这个 Message 时,会交还给 Handler 处理。

// Handler.java
public final boolean post(Runnable r)
{
	return sendMessageDelayed(getPostMessage(r), 0);
}

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

这里你可能会有疑问,同样是使用 sendMessage,难道使用 post 只是方便使用吗?

当然不是。如果你有 Handler 的使用经验,那肯定知道 post 处理消息执行的是它的 Runnable 参数,也就是只是把 Runnable 换了个线程执行。而使用 sendMessage 方法,则需要在 Handler.handleMessage() 中重写对应的处理逻辑。

原因在于 post 在封装 Message 时,为其设置了 callback 成员,那么当 Looper 将消息交还给 Handler 处理时,会优先询问 Message.callback 是不是设置了,如果设置了,就交给你处理。否则我就去问下 Handler.mCallback 或者 Handler.handleMessage() 谁能帮我处理下。

// Handler
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

一个 Looper 维护唯一的一个消息队列,一个 Looper 对应多个 Handler,那么 Looper 如何知道是用哪一个 Handler 来处理对应的消息?

Handler 把消息交给 Looper 之前,会为其设置自身的引用。Looper 循环到这个消息时会使用这个引用处理消息。

详情查看上面第一个代码片段:

// Handler.enqueueMessage() 方法
msg.target = this;

// Looper.loop() 方法
msg.target.dispatchMessage(msg);

Handler 有三种处理消息的方式,那么 Handler 如何选择以哪种方式来处理?

上面已经提到过了,三种方式分别是:

  1. Message 的 callback 成员
  2. Handler 的 mCallback 成员
  3. Handler 的 handleMessage 方法
// Handler.java
// 在 Looper.loop() 中调用
public void dispatchMessage(Message msg) {
    // post 的参数 Runnable
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 只能在 Handler 构造函数中设置
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 需要继承 Handler 并重写这个方法
        handleMessage(msg);
    }
}

HandlerThread 实现

class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        Looper.loop();
    }
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }
}
  • 本质是一个Thread
  • run 中初始化 Looper 并使用 Looper.loop() 阻塞线程接受消息循环
  • getHandler() 返回使用其 Looper 创建的 Handler

如何同步等待消息处理完成

Handler 提供了 runWithScissors 方法可同步等待消息处理完成。使用了内部类 BlockingRunnable

线程同步就是另外的课题了。我会另开一篇文章进行讲述。

结束

Handler 与线程无关,与 Looper 有关。

也就是说你只需指定对应的 Looper,它就能正常工作,在 Looper 所在线程处理消息任务,而无关你在哪个线程调用。这就很容易理解,为什么使用 Handler 更新 UI 是如此的合适。

最后需要注意的一点,因为 Handler 在线程中异步处理消息,那么很可能存在内存泄漏问题。

/* 看板娘 */