文章参考:https://juejin.cn/post/6940607471153053704

概述

我们回过去看看Handler的构造函数函数。你会发现一个Handler的构造函数。这个构造函数我们一般不常用。而且这个构造函数加上了@UnsupportedAppUsage注解。说明这个构造函数是不支持我们来用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 判断这个Handler发送的消息是不是异步Handler消息
*/
final boolean mAsynchronous;
/**
*
* 默认情况下,Handler处理消息是同步的,除非使用此构造函数来创建异步处理的构造函数。
* 异步消息不受{@link MessageQueueenqueueSyncBarrier(long)}引入的同步障碍的约束
* @param async如果为true,则Handlerh会针对发送给它的每个Message或发布给它的
* Runnable程序调用{@link Message#setAsynchronous(boolean)}
* @hide
*/
@UnsupportedAppUsage
public Handler(boolean async) {
this(null, async);
}

public Handler(@Nullable Callback callback, boolean async) {
// 获取当前线程的looper对象
mLooper = Looper.myLooper();
// looper为空的异异常处理
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// 获取Looper对象里面的创建的MessageQueue
mQueue = mLooper.mQueue;
// 入参传入的callback
mCallback = callback;
mAsynchronous = async;
}

从这个构造函数的注释我们可以看出。默认情况下,Handler处理消息是同步的,除非使用此构造函数来创建异步处理的构造函数。异步消息不受{@link MessageQueueenqueueSyncBarrier(long)}引入的同步障碍的约束。如果构造一个异步处理消息的Handler。则每个发给他的消息。Handler都会调用Message#setAsynchronous(boolean async)来设置这个消息为异步消息。

那这个设置我们怎么进行设置一个消息为异步消息呢??

咱们先来回顾一下Handler处理消息是怎么处理的吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 使用Handler来发送消息,这个方法会将所发送的消息添加到所有待处理消息的消息队列的末尾
* 在当前时间,在所有待处理消息之后,将消息推送到消息队列的末尾。
* 在和当前线程关联的的Handler里面的handleMessage将收到这条消息,
*/
public final boolean sendMessage(@NonNull Message msg) {
// 调用sendMessageDelayed
return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
// 该方法内部就做了两件事
// 1、判断delayMillis是否小于0
// 2、调用了public boolean sendMessageAtTime(Message msg, long uptimeMillis)方法
if (delayMillis < 0) {
delayMillis = 0;
}

// SystemClock.uptimeMillis() 返回系统启动到现在的毫秒数,不包含休眠时间。就是说统计系统启动到现在的非休眠期时间+delayMillis
// 以Android系统的SystemClock.uptimeMillis()为基准,以毫秒为基本单位的绝对时间下进行延迟执行。

// 其实是在未来某个时间点来处理这个消息
// 在所有待处理消息后,将消息放到消息队列中。深度睡眠中的时间将会延迟执行的时间,
// 你将在和当前线程绑定的的Handler中的handleMessage中收到该消息。
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
// 获取Java层的Looper里面消息队列。
// 这个MessageQueue是Handler绑定的Looper里面MessageQueue
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 将当前的消息以及延迟的世间异步加入到消息队列中
// 调用了boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 为当前消息msg添加target。 这个target其实就是当前这个handler对象
// 所以我们想到之前在Looper的对象在loop()方法里面调用message的target来处理当前消息,
// 所以最终这个消息的target就是当前这个Handler对象
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

// 设置这个消息是异步的,这个变量的实例化是在Handler实例化的时候
// 如果Handler的mAsynchronous值为true(默认为false,即不设置),
// 则设置msg的flags值,让是否异步在Handler和Message达成统一。
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 执行queue中的异步消息入队列相关逻辑
return queue.enqueueMessage(msg, uptimeMillis);
}

上面这个Handler处理消息的方法,想必大家都不陌生了。而且代码中中的注释也是十分清晰的。在enqueueMessage方法里面我们会判断当前handler是不是一个异步消息处理Handler.如果是的话,我们就将他所发送的处理消息设置为异步消息。

异步消息 or 同步消息

这个就引出了一个新的概念:异步消息、同步消息。 奇怪,我们Handler本来不就是用来处理异步的嘛!!!其实,所以Handler处理异步消息其实指的是消息处理的时间是异步的。但是消息队列里面消息处理顺序是同步处理的。一旦消息队列里面加入同步屏障,所有的消息就都被阻塞。无法处理。

异步消息不受MessageQueue#enqueueSyncBarrier(long)引入的同步障碍的约束。

如果构造一个异步处理消息的Handler。则每个发给他的消息。Handler都会调用Message#setAsynchronous(boolean async)来设置这个消息为异步消息。即使加入了消息屏障。有些消息依然可以不被阻塞继续执行,这些消息就是上面Haddler标记的异步消息

同步屏障

什么是同步屏障机制

同步屏障机制是一套为了让某些特殊的消息得以更快被执行的机制。

我们知道每一个Message在被插入到MessageQueue中的时候,会强制其target属性不能为null。

1
2
3
4
5
6
7
boolean enqueueMessage(Message msg, long when) {
// msg的target的Hanlder不允许为空
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
...
}

但是,很奇怪的是。Android提供了另外一个方法来插入一个特殊的消息,强行让target==null:

同步屏障消息的添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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++;
// 初始化同步屏障消息。我们可以看到这个消息没有设置target
// 这个在Handler里面可是不被允许的!!!!
final Message msg = Message.obtain();
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) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

这个方法就是postSyncBarrier。 主要方法就是用来向消息队列里面插入同步屏障消息。

重点在于:没有给Message赋值target属性,且插入到Message队列头部。这个target==null的特殊Message就是同步屏障。

既然我们插入了一个target为null的同步屏障消息。我们是怎么把同步屏障消息后面的消息给阻塞住的??

又是怎么让异步消息不受同步屏障消息的约束,可以绕过约束来执行的呢??

这个我们就要回过头去看Handler去取消息队列中的消息的机制。

异步消息的处理

之前的文章我们学习过,Handler是通过Looper对象中loop方法来不断循环的从消息队列里面取得消息进行梳理的,那么下面我们就来看一下loop()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
*
* 这个方法主要是进行当前线程里面的message queue队列来遍历消息,进行消息的分发
* 这个我们自己在线程创建的时候自己类执行以下loop()方法
*/
public static void loop() {
// 这个方法就是从TreadLocal里面取得当前的Looper对象。
// 也就是我们每个线程实例化的ThreadLocal对象里面取得保存的Looper对象
final Looper me = myLooper();
// 毫无疑问,me为null 表示这个线程里面没有进行Looper对象的Looper.prepare()初始化。
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 这个queue就是Looper的构造函数中实例化的mQueue
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();

// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);

boolean slowDeliveryDetected = false;
// Looper的loop方法执行死循环。
// 我们可以看到这个地方,是执行了一个死循环,在这个死循环中依次遍历queue里面的所有消息
for (; ; ) {
// 从Looper的queue中依次取出消息实体
// 拿到下一个Message.如果没有消息这个地方就会阻塞掉
Message msg = queue.next(); // might block

// 如果为null,表明这个额消息队列正在停止,这个时候就跳出循环。
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// Looper里面的由一个logging对象,在消息开始处理的时候,会打印一个Dispatching to
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;

final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;

if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}

final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
// 我们从这个地方调用 msg.target的dispatchMessage()方法
// 那么 msg.target对象是什么?
// 其实就是Handler对象
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
// 消息处理完成完成之后,会打印这个Finished to的日志
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();
}
}

上面的方法,如果我们学习过Handler的源码。基本都是熟悉了。最主要的其实是下面的next()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
@UnsupportedAppUsage
Message next() {
// 如果消息循环已经退出了。则直接在这里return。因为调用disposed()方法后mPtr=0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 记录空闲时处理的IdlerHandler的数量
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// native层用到的变量 ,如果消息尚未到达处理时间,则表示为距离该消息处理事件的总时长,
// 表明Native Looper只需要block到消息需要处理的时间就行了。 所以nextPollTimeoutMillis>0表示还有消息待处理
int nextPollTimeoutMillis = 0;
for (; ; ) {
if (nextPollTimeoutMillis != 0) {
// 刷新下Binder命令,一般在阻塞前调用
Binder.flushPendingCommands();
}
// 调用native层进行消息标示,nextPollTimeoutMillis 为0立即返回,为-1则阻塞等待。
// 就是这行代码进行消息阻塞的。这当代码会调用Native层的Looper的方法
nativePollOnce(ptr, nextPollTimeoutMillis);
// 加上同步锁
synchronized (this) {
// 获取开机到现在的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// 获取MessageQueue的链表表头的第一个元素
Message msg = mMessages;
// 如果msg不为空并且target为空,说明是一个同步屏障消息
// 关于同步屏障的问题,我们可以参考#ViewRootImpl#scheduleTraversals的方法
// 如果是则执行循环,拦截所有同步消息,直到取到第一个异步消息为止
// 进入do while循环,遍历链表,直到找到异步消息msg.isAsynchronous()才跳出循环交给Handler去处理这个异步消息。
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// 如果能进入这个if,则表面MessageQueue的第一个元素就是障栅(barrier)
do {
prevMsg = msg;
msg = msg.next;
//如果msg==null或者msg是异步消息则退出循环,msg==null则意味着已经循环结束
} while (msg != null && !msg.isAsynchronous());
}

// 判断是否有可执行的Message
if (msg != null) {
// 判断该Mesage是否到了被执行的时间。
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
// 当Message还没有到被执行时间的时候,记录下一次要执行的Message的时间点
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
// Message的被执行时间已到
// 从队列中取出该Message,并重新构建原来队列的链接
// 刺客说明说有消息,所以不能阻塞
mBlocked = false;
// 如果还有上一个元素
if (prevMsg != null) {
//上一个元素的next(越过自己)直接指向下一个元素
prevMsg.next = msg.next;
} else {
//如果没有上一个元素,则说明是消息队列中的头元素,直接让第二个元素变成头元素
mMessages = msg.next;
}
// 因为要取出msg,所以msg的next不能指向链表的任何元素,所以next要置为null
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
// 标记该Message为正处于使用状态,然后返回Message
msg.markInUse();
return msg;
}
} else {
// No more messages.
// 没有任何可执行的Message,重置时间
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
// 关闭消息队列,返回null,通知Looper停止循环
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

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

// ...........

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

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

Next方法中,我们在遍历消息队列的时候。如果msg不为空并且target为空,说明是一个同步屏障消息。如果是则执行循环,拦截所有同步消息,直到取到第一个异步消息为止。进入do while循环,遍历链表,直到找到异步消息msg.isAsynchronous()才跳出循环交给Handler去处理这个异步消息。

如果所有的消息,都遍历完,没有找到异步消息的话,我们就会nextPollTimeoutMillis设置为-1 及永久阻塞住next的方法。知道我们有新的消息插入的时候,我们调用native层的方法nativeWake(mPtr)进行唤醒。然后才会继续执行。

关于这个逻辑我们在其他的文章有具体分析。这里就不再分析了。

同步屏障消息的移除

同步屏障不会自动移除,使用完成之后需要手动进行移除,不然会造成屏障后面的同步消息无法被处理。

移除的方法就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 移除同步屏障消息
* @param 传入我们执行 {@link #postSyncBarrier(long)} 添加同步屏障消息时候返回的令牌Token
* @hide
*/
@TestApi
public void removeSyncBarrier(int token) {
// 从队列中删除同步屏障令牌做标记的同步屏障消息。
// 如果队列不再因障碍而停滞,也就是说移除同步屏障消息,之后我们则将其唤醒。
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 遍历消息队列,找到p.target = null 并且 p.arg1 = token的消息。也就是我们添加进去的同步屏障消息
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.");
}
// 移除同步屏障消息,并判断是否需要唤醒消息队列的next遍历
final boolean needWake;

if (prev != null) {
// 让prev.next 等于同步屏障消息后面的那个消息
prev.next = p.next;
// 如果同步屏障消息之前有消息,其实不需要唤醒,很好理解,因为这些消息本来就会正常被执行
// 不会被同步屏障消息长时间阻塞
needWake = false;
} else {
// 如果prev为null 则说明同步屏障消息之前没有消息了。
// 则mMessages == null || mMessages.target != null需要执行唤醒。
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();

// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}

同步屏障的使用

我们看到,同步屏障的添加和移除都是不对外提供的方法。那就说明这些方法都是系统自己来使用的方法。

那么系统什么时候会添加同步屏障