解析Handler消息机制(Android12)
一 概述
在我们之前介绍四大组件的过程中,经常会看到两类代码。一个就是 mService(AMS) 和 mThread(ApplicationThread) 的调用,它们分别代表了应用进程通过 mService 与 AMS 的通信,和 AMS 通过 mThread 向应用进程的通信。
然而,在这个通信过程中,我们还会经常看到另外一类代码,它们就是 scheduleTransaction 之类。它们是在经过了进程间的通信之后,从进程间通信的 binder 线程,切换到应用的 UI 线程的线程间的通信方式。
所以,说到底,Handler 其实就是 Android 用来在线程间通信的一种方式。今天,我们就来说一说,应用的线程间通信的方式,即 Handler 机制。
二 线程间通信
2.1 线程与同步
既然我们说 Handler 是 Android 用来进行线程间通信的一种方式,那么我们就先来说说 Android 是如何使用 Handler 进行线程间通信的。
首先,在我们的主线程中,有一个 Looper,这个 Looper 会不断的从一个 MessageQueue 中取出 Message,这些 Message 就表示对应的回调。然后在其他的线程,可以通过 Handler 发送消息,这样主线程中的 Looper 就会取到对应的 Message,然后执行回调。
那么,大家想过这样一个问题没有,Android 的 Handler 通信,是如何保证不同线程间的同步的?
首先,我们可以查看主线程代表的类 ActivityThread。
[frameworks/base/core/java/android/app/ActivityThread.java]
@UnsupportedAppUsage
final Looper mLooper = Looper.myLooper();
@UnsupportedAppUsage
final H mH = new H();
大家有没有想过这样一个问题?为什么这里的 mLooper 和 mH 都是 final 的。
其实这是 Java 并发编程中的知识,对于 Java 的多线程同步,一般有两种方式,其一就是使用 synchronized 或者锁,另一种则是使用不变性。最为代表的就是 final。这样就能保证不论是哪个线程调用到了 ActivityThread,它们拿到的 mLooper 和 mH 都是相同的。
2.2 Handler 的线程间同步
然后我们再简单看一下 Handler 中发送消息的逻辑。源码相关的解析我们放后面,我们直接看最终的调用函数 sendMessageAtTime。
[frameworks/base/core/java/android/os/Handler.java]
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在 sendMessageAtTime 中,将 Handler 的成员变量 mQueue 取出,然后调用了它的 enqueueMessage 函数。前面我们说了,在 ActivityThread 中,通过定义 mH 为 final 保证了不同线程拿到的 Handler 都是相同的。
2.3 MessageQueue 的线程间同步
但是,在这里就算是同一个 Handler,依旧不能保证它的成员变量都是一样的,Android 又是如何保证不同线程的 Handler,它的 MessageQueue 也是同一个呢?
相比熟悉 JUC 的小伙伴已经猜到了,这里依旧使用了不变性,final 关键字。
[frameworks/base/core/java/android/os/Handler.java]
@UnsupportedAppUsage
final Looper mLooper;
final MessageQueue mQueue;
@UnsupportedAppUsage
final Callback mCallback;
final boolean mAsynchronous;
Looper 和 MessageQueue 都是 final,这样又保证了 Handler 中,不同线程拿到的 MessageQueue 的唯一性,又保证了多线程间的同步。
好了,最后一个问题,MessageQueue 又是怎么保证线程间的同步的呢?我们之前已经说了,MessageQueue 中有一个消息队列,Looper 会不断的轮询这个消息队列,然后取出对应的 Message 执行回调。
这里我们不卖关子,直接看代码。
[frameworks/base/core/java/android/os/MessageQueue.java]
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
...
}
}
在 MessageQueue 的 enqueueMessage 中,除了一开始对 Message 的 target 判断,后面直接使用了 synchronized 关键字,这样就又保证了不同线程间,消息队列的线程间同步。
到此,我们就基本知道了,Handler,Looper 还有 MessageQueue 是如何保证线程间同步的。
2.4 Handler 架构
接下来,我们简单介绍一个 Handler 架构,其实从之前的介绍,我们已经介绍的差不多了。
Handler 中有 Looper 有 MessageQueue,并且还都是 final 标识的。 Looper 中有 MessageQueue,用来轮询。它们的 MessageQueue 都是同一个。
接下来,我们从源码的角度,来具体分析 Handler 机制。
三 Handler
3.1 Handler 的初始化
在之前我们已经了解到了 Android 进程的启动流程,并且在进程的启动过程中,我们看到了 Handler 的初始化。
[frameworks/base/core/java/android/app/ActivityThread.java]
public static void main(String[] args) {
Looper.prepareMainLooper();
// 创建 ActivityThread
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
// 调用 ActivityThread 的 getHandler
//
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
// ActivityThread 的成员变量
// H 是 Handler 的子类
final H mH = new H();
public Handler getHandler() {
return mH;
}
在进程的启动过程中,会创建一个 Handler 和一个 Looper,并对其进行初始化。
public Handler() {
this(null, false);
}
[frameworks/base/core/java/android/os/Handler.java]
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
...
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
3.1 Looper
[frameworks/base/core/java/android/os/Looper.java]
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
3.3 prepareMainLooper
在使用 Looper 之前,ActivityThread 中会调用 prepareMainLooper,主要目的就是为当前的线程准备一个 Looper,并保存到当前线程的 ThreadLocal 中。
public static void prepareMainLooper() {
// prepareMainLooper 传入参数是 false
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
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));
}
prepareMainLooper 其实就是向当前线程对应的 ThreadLocal<Looper>中保存一个 Looper。
3.4 Looper 的构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在 Looper 的构造函数中,创建了一个 MessageQueue 的成员变量 mQueue。然后就是开始轮询 loop -> loopOnce。构造函数传入的参数 quitAllowed 表示这个 Looper 是否可以退出,一般主线程创建的 Looper 是不能退出的,所以传入的参数是 false。
3.5 loop
public static void loop() {
// 拿到当前线程(main线程)的 Looper
final Looper me = myLooper();
// 如果 me 为空就抛出异常,
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
me.mInLoop = true;
// 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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
me.mSlowDeliveryDetected = false;
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
3.6 loopOnce
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
// 从消息队列中取出下一条 Message,阻塞函数
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
// 打印开始消息分发
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what);
}
// 消息分发的观察者,这个是高版本加入的
final Observer observer = sObserver;
...
Object token = null;
// 回调观察者,开始消息分发
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
// 分发消息
msg.target.dispatchMessage(msg);
// 回调观察者,结束消息分发
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
...
// 打印消息,消息分发结束
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
msg.recycleUnchecked();
return true;
}
loopOnce 其实就是从消息队列中取出一条 Message,然后进行分发,而取出消息的逻辑在 MessageQueue 中,并且 next 也是一个阻塞函数。
3.7 dispatchMessage
取出消息之后则是通过 dispatchMessage 进行分发。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
接下来我们看看具体消息是如何取出的。
四 MessageQueue
4.1 next
[frameworks/base/core/java/android/os/MessageQueue.java]
@UnsupportedAppUsage
Message next() {
// 如果 MessageQueue 已经退出的话,mPtr 就会被设置为 0
// 这样就直接返回
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 每次调用 next,都会将 Idle Handlers 的数量先设置为-1
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 调用 nativePollOnce,阻塞等待
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 如果 msg 不为空,并且没有 target
do {
// 如果 msg 是异步消息,就会一直向后查找
// 也就是说,如果存在异步消息,最先处理异步消息
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.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
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;
}
// 如果 pendingIdleHandlerCount 小于 0,即第一次运行
// 并且 mMessages 为空或者当前时间小于消息处理的时间,就获取 Idle Handlers 的数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有 Idle Handlers 就跳出此次循环
mBlocked = true;
continue;
}
// 如果有 Idle Handlers,就创建一个 Idle Handlers 的数组
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 循环处理 Idle Handlers
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
// 处理 Idle Handlers,并且返回 keep
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 如果 keep 为 false,就移除掉这个 Idle Handler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置 Idle Handlers 的数量
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
在 MessageQueue 的 next 中,有着如下的处理逻辑
- 首先调用 nativePollOnce 进入阻塞
- 拿到 Message 列表的第一个消息
- 如果队头的消息 target 为空,就查找消息列表中的异步消息
- 如果队头的消息 target 不为空,就判断最前面的消息的处理时间是否已经到了,如果到了,就直接处理,如果没有到,就进入休眠,并休眠对应的时间
- 如果消息队列处理完毕了,就看是否存在 Idle Handler,如果存在,就处理,如果不存在,就退出循环,等待下一次 next。
在 MessageQueue 的消息队列轮询中,我们看到了系统首先会处理异步消息(如果存在的话),然后再处理普通消息,最后处理 Idle 消息。
到这里,关于 Handler 消息机制 Java 层的原理我们就已经介绍完了。接下来我们再来看看刚才的三种消息,是如何发出的。
- 异步消息
- 普通消息
- Idle 消息
五 普通 Handler 消息的发送
5.1 sendMessage
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
5.2 sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
5.3 sendMessageAtTime
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
// 拿到成员变量 mQueue
MessageQueue queue = mQueue;
if (queue == null) {
...
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
5.4 enqueueMessage
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
5.5 MessageQueue.enqueueMessage
[frameworks/base/core/java/android/os/MessageQueue.java]
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
...
// 首先会进行三个异常判断,然后将消息的处理时间保存到 when 中
msg.markInUse();
msg.when = when;
// 这是一个链表
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 插入队头
msg.next = p;
mMessages = msg;
// mBlocked 只有在之前队列处理完所有当前时间需要处理的消息之后,才会为 true
needWake = mBlocked;
} else {
// 插入队伍中间
// 只有mBlocked为true,并且还是异步消息,才需要唤醒
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;
}
// 只有在需要唤醒消息轮询的时候,才会调用唤醒函数
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
在 Android 中,所有发送普通消息的逻辑,最后都会走到 enqueueMessage 这个函数,而这个函数中,首先就会判断 target 是否为空,然后再进行消息插入。
这里我们还需要注意插入消息还有一个是否唤醒的判断,需要唤醒的条件只有一种,那就是当前的消息队列,已经处理完了当前时间需要处理的消息。
什么是当前时间需要处理的消息?假如我们当前的时间戳为 100,而消息队列中,需要处理的消息都有对应的时间戳,其中时间戳为 100 的我们已经处理完了,消息头需要处理的消息对应时间戳为 200。这种情况下,消息轮询会休眠一段时间,避免消耗 CPU。所以这时如果我们向消息队列添加了消息,就可能需要唤醒。
具体是否唤醒,会做这样两个判断
- 消息是否插入的是消息头。(如果是消息头,那么就是最早需要处理的消息,则唤醒一次看看,如果到了时间就处理,如果没有到时间就计算一个休眠时间,接着休眠)
- 如果不是消息头,那么就看是否是异步消息,如果是异步消息才需要唤醒。
接下来我们再来说说这其中所谓的异步消息是怎么发出来的。首先,正常情况下,我们是发送不出 target 为空的消息的,那么,target 为空的消息是怎么发送出来的呢?
我们直接看 Android 的刷新逻辑,在 ViewRootImpl 中,有着这样一段代码
六 ViewRootImpl
6.1 scheduleTraversals
[frameworks/base/core/java/android/view/ViewRootImpl.java]
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 首先是向消息队列中,插入一个消息屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 然后发送一条异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
6.2 postSyncBarrier
[frameworks/base/core/java/android/os/MessageQueue.java]
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg 1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
postSyncBarrier 其实就是发送消息屏障的意思,在 postSyncBarrier 函数上,有一个 UnsupportedAppUsage 的注解,也就是说发生消息屏障正常情况下是不会提供给应用端使用的。
而消息屏障其实就是一条没有 target 的消息,其实这也很好理解,消息屏障会暂停普通消息的处理,转而处理异步消息,这样势必会导致普通消息的处理延迟。在我们之前解析四大组件的时候,介绍过,在四大组件的启动过程中,会用到 Handler 机制,并且还有延迟消息来计算 ANR,而不合理的异步消息,势必会导致 ANR 的发生,所以 Android 不让应用端发生异步消息也是很好理解的。
6.3 postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
// 发送一条异步消息,setAsynchronous 为 true
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg 1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
最后,我们可以看到,异步消息其实就是 setAsynchronous 为 true 的消息,和其他的消息也没有什么不同。
转载自:https://juejin.cn/post/7219869424551362616