深入理解 Handler 消息机制

上一篇 - 消息机制 Handler 使用 文章讲了 Handler 的一个概述和基本的使用方法,这里还有一点需要强调一下:对于初学者一定要将你创建的子线程区分开,Handler 是你在子线程执行完,准备进行线程切换执行其他操作时才开始使用 Handler

本篇主要对 Handler 的工作原理进行分析,即 Handler、Looper、MessageQueue 三者是如何工作的,从源码层面来分析下,本篇的主要内容如下:

handler_detail

Looper

首先来说 Looper,实际上 Handler 最开始是和 Looper 关联起来的,上一篇中对 Handler 的构造函数进行了介绍,无论有参构造还是无参构造,其实都是需要有 Looper的。


public Handler(Looper looper) {
this(looper, null, false);
}

public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}

这两个好理解,构造中直接需要有 Looper,可以使主线程的 Looper,也可以是你自己创建的线程所属的 Looper。

主线程 Looper 获取方法:

public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}

如果是你自己创建的子线程,那么如果创建子线程的 Looper 呢?

// 在当前线程中创建 Looper,但是只能创建一次,重复创建(即重复调用这个方法,或报错)
public static void prepare() {
prepare(true);
}

// 创建之后,还需要将 Looper运转起来,才能执行消息队列中的消息
public static void loop() {
...
}

就像下面这个小例子,就是在子线程中使用 Handler,其他线程就可以使用这个 Handler,发送消息后,消息会在这个 new Thread 线程中执行。


private Handler handler;

new Thread()
{
public void run()
{

Looper.prepare();

handler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
Log.e("TAG",Thread.currentThread().getName());
};
};
Looper.loop();

}

}

好,回到 Looper 分析中来,那么无参数构造的 Handler 呢?最终是调用这个构造方法。

public Handler(Callback callback, boolean async) {

...

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

这个方法中有一个 Looper.myLooper() 方法,通过这个方法就是获取到了 Looper,具体是怎么获取?分下这个方法。


public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

看到是通过 ThreadLocal 来获取的,ThreadLocal 是线程内部维护数据的一个变量,能够存储当前线程的一些数据,只有在当前线程才能够获取当前线程中的数据, Looper 属于线程的数据,所以可以通过 ThreadLocal 可以获取,主要的过程就是通过 ThreadLocal 的 put 和 get 方法,以 key-value 的形式来完成的,key 就是当前线程。那么什么时候将线程的 Looper 存储到 ThreadLocal 中的呢?就是创建 Looper 的时候。


public static void prepare() {
prepare(true);
}

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));
}

看到这段代码是不是就理解了。

好,接着往下走,获取到 Looper 的目的是什么呢?mQueue = mLooper.mQueue, 答案是为了获取到 MessageQueue。


权限是包内权限可以知道 Handler  Looper 类处于同一个包下 .../android/os 
final MessageQueue mQueue;

获取当前线程的 MessageQueue,有这几种方法,都是通过 Looper 来获取的


// (1)同一个包内,直接使用
mQueue = mLooper.mQueue;

// (2)已经获取了 Looper
public @NonNull MessageQueue getQueue() {
return mQueue;
}

// (3)没有获取looper ,不在同一个保内,当时在当前线程
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}

Looper 持有 MessageQueue,MessageQueue 是在什么时候创建的呢?看下 Looper 的构造函数

public static void prepare() {
prepare(true);
}

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));
}

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

// MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

代码比较清晰,就是在创建 Looper 时创建 MessageQueue,既然 一个线程的 Looper 只能有一个,同理,MessageQueue 也是只能有一个。本文最开始的的问题,如何切换到主线程?,Looper 和 MessageQueue 是在主线程,那么通过 Handler 发送的消息自然就在主线程中了。

对于 Looper 来讲,剩下就是一个最重要的方法,loop()


/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
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;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
...

boolean slowDeliveryDetected = false;

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...

try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}

...

msg.recycleUnchecked();
}
}

首先是获取 MessageQueue,然后 Looper 中通过一个无限循环,不断从消息队列中取出消息,然后通过调用 msg.target.dispatchMessage(msg); 来执行消息,虽然我们可能不知道 msg 中的 target 是啥?但是可以猜想一下,是不是应该是 Handler 呢?因为我们通过 Handler 来执行我们在主线程(准确说是 Handler 线程,这里只是以主线程为例)想干事的事情,即 handleMessage 或者是 Runnable 的 run 方法,下面再去验证想法。

到这里会有一些疑问?

  • 为什么是无限循环,这样的话,CPU 满载,手机岂不是一直满负荷在跑,变得烫手。。。
  • 什么时候 msg 为 null,这时候 return 之后会怎么样?
  • Message msg = queue.next(); // might block 可能会阻塞,含义是什么?

这几个问题中,到这里暂时只能解释第二个,如果 msg 为空,返回之后 loop() 方法结束。这里以 应用程序的主线程为例,在主线程,主要是更新 UI,也就是在 Looper 中取出来的消息是用来更新 UI 的操作,既然都不能更新 UI,那么也只有应用程序死掉了,主线程退出的情况。

对于其他的子线程,也是消息队列中没有消息了,消息队列退出,当前子线程也就结束了。

现在我们知道 Handler 获取了 Loopper,Looper 中有消息队列,也能够获取到,即 Handler 持有 Looper 和 MessageQueue,对于 Handler 来说,有了这些,能够做些什么,下面就来看看 Handler。

Handler

Handler 的主要原理,可以看一下下图:

Handler

实际上,Handler 做的主要就是两件事,发送消息,然后等到消息处理时进行执行。先来看发送消息,在上一篇文章中提到,发消息有两种方式,send 和 post 方式,最终都是调用 sendMessageAtTime 方法。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);

}

sendMessageAtTime 方法中 uptimeMillis 参数指定消息执行的绝对时间,接着将消息加入到消息队列中,MessageQueue 就是上面提到的,通过 Looper 获取的消息队列。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

(1)msg.target = this 这一句验证了我们上面的猜想,target 就是 Handler 本身,在 Looper 中 loop() 方法中执行的 msg.target.dispatchMessage(msg),就是回调 Handler 的 dispatchMessage () 方法。

(2)中间部分代码,如果设定的是异步执行,需要将消息打上异步的标记。默认情况下,消息队列中都是同步执行的,但是会根据设定的时间来区分消息执行的先后顺序。

将消息加入到消息队列中的具体操作在下文介绍 MessageQueue 时再详细分析。接下来看一下,消息的执行,即 dispatchMessage。

/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
// 通过 post 方法发送的消息
if (msg.callback != null) {
handleCallback(msg);
} else {
// 通过 send 方法发送的消息
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
} 
// handler 的默认方法,空方法
handleMessage(msg);
}
}

这个方法就比较简单,分为 3 种情况:

(1) 第 1 种,通过 post 方法发送的消息,post(Runnable r),是一个 Runnable 对象,然后赋值到 Message 的 callback 变量,执行时也就是调用 callback 的 run 方法,注意这里虽然是 Runnable 对象,但是跟 Thread 中的 Runnable 是不一样的,这里仅仅是为了执行 run 方法而已。

private static void handleCallback(Message message) {
message.callback.run();
}

(2) 第 2 种, 通过 send 方法发送的消息,这个就是我们构造 public Handler(Callback callback) 这样一个 Handler,执行时就会通过 Callback 对象来执行消息。Callback 是一个接口,方法就是我们熟悉的 handleMessage 方法,所以 mCallback.handleMessage(msg) 就是调用这个接口的方法了。

public interface Callback {
public boolean handleMessage(Message msg);
}

(3) 第 3 种,在 Handler 中 handleMessage 是一个空方法

public void handleMessage(Message msg) {
}

也可以进行重写,但是一般情况下,我们使用前两种方式较多。

MessageQueue

Message

介绍消息队列之前,先介绍下 Message。主要看下 Message 中的参数


public int what;

public int arg1;

public int arg2;

public Object obj;

/*package*/ long when;

/*package*/ Bundle data;

/*package*/ Handler target;

/*package*/ Runnable callback;

// sometimes we store linked lists of these things
/*package*/ Message next;

以上是 Message 中能够携带的参数:

  • 一般我们使用 what 来作为标识,表明是哪种消息
  • arg1 和 arg2 是 int 类型参数
  • obj 是对象类型参数,
  • 当然也可以使用 Bundle
  • when 是指定的消息执行的时刻
  • target 就是我们上面猜测得 Handler 对象
  • callback 使用 post 方法发送消息的 Runnable 对象
  • next 指向下一个消息,可以得知,消息队列中是单链表形式的

MessageQueue

先来看一下消息队列的构造,还记得上面在创建 Looper 时就是创建 MessageQueue 的时刻。

MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

mQuitAllowed 为 true 时表明消息队列可以退出,意味着为 false 是不能退出,具体这个有什么影响,这里不去探究了。

mPtr = nativeInit() 是一个 native 方法,下面一段话是引自老罗Android应用程序消息处理机制(Looper、Handler)分析这篇文章

(1)在 Java 层,创建了一个 Looper 对象,这个 Looper 对象是用来进入消息循环的,它的内部有一个消息队列 MessageQueue 对象 mQueue;

(2)在 JNI 层,创建了一个 NativeMessageQueue 对象,这个 NativeMessageQueue 对象保存在 Java 层的消息队列对象 mQueue 的成员变量 mPtr 中;

(3)在 C++ 层,创建了一个 Looper 对象,保存在 JNI 层的 NativeMessageQueue 对象的成员变量 mLooper 中,这个对象的作用是,当 Java 层的消息队列中没有消息时,就使 Android 应用程序主线程进入等待状态,而当 Java 层的消息队列中来了新的消息后,就唤醒 Android 应用程序的主线程来处理这个消息。

实际上,java 层 Looper 在底层都能对应到底层,因为是涉及到线程操作,包括等待、唤醒,这些操作需要在底层来完成。

添加消息

消息队列的构造分析之后,接下来看一下如何把消息加入到消息队列当中的。

boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

synchronized (this) {
if (mQuitting) {
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;
}

msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 情况一:直接将消息加入到消息队列头部(单链表头部)
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 {
// Inserted within the middle of the queue.  Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.

// 情况二:根据时间点比较将消息插入到链表中
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 通过底层操作来唤醒
nativeWake(mPtr);
}
}
return true;
}

分析:将消息加入到消息队列中,实际上就是操作单链表的过程,分为两种情况:

(1)情况一:直接将消息加入到消息队列头部(单链表头部)这种情况的条件是下列条件中的一个:

  • p == null 消息队列为空
  • when == 0 当前需要加入的消息的时间点为0,意味着需要立即执行的消息,所以需要加入到队列头部
  • when < p.when 当前需要加入的消息的时间点比消息队列头部的消息的时间小早,也就是说需要先执行的消息,但不一定立即执行

(2) 情况二:根据时间点比较将消息插入到链表中

典型的单链表插入操作,首先找到根据消息的时间点来找到插入的位置,然后将消息插入到链表中。

取出消息

取出消息是通过 next() 取出消息的,该方法在 Looper 的 loop() 方法中被调用,用来获取需要执行的消息

Message next() {

...

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

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {

// 线程等待状态
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// Try to retrieve the next message.  Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier.  Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready.  Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}


}

... 

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

将代码简化了一下,for 循环中主要执行取出消息的操作,首先是找到一个消息,这个消息是最先执行的。找到之后,先看这个消息的时间点,如果到了执行的时间就返回消息,并从链表中删除,返回给 Looper;如果时间还没到就等待,nativePollOnce(ptr, nextPollTimeoutMillis);

什么情况下线程会进入等待状态?两种情况,一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。消息队列中的消息是按时间先后来排序的。

下面回答下上面的未给出答案的两个问题:

(1)为什么是无限循环,手机不烫手?

(2)Message msg = queue.next(); // might block 可能会阻塞,含义是什么?

线程的等待唤醒底层采用的是管道机制,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。

所以阻塞时,线程处于等待状态,这时候 Linux 系统中的 epoll 机制,并不会占用很高的 CPU 使用率,因此即使是无限循环,也不会导致手机发烫啦…

总结

Handler 中持有 Looper,并通过 Looer 获取 MessageQueue,然后发送消息就是将消息加入到 MessageQueue 中,并且在 Message 中将 Handler 本身携带过去,在 Looper 的 loop 循环中 通过 MessageQueue 的 next 方法 得到消息后,在 Message 中取出 target,即 Handler,然后回调 dispatchMessage,执行我们自定义的方法。

本篇对 Handler 的分析就到这里,如果文中有错误的地方,希望在评论区给出指正!另外如果对 Handler 的底层想进行研究的话,建议看看老罗的文章。

参考

《安卓开发艺术探索》

Android应用程序消息处理机制(Looper、Handler)分析

Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦