Android-Message

Android-Message

1. Message的种类

在 Android 中经常使用 Message 来发送一些“消息”,这个“消息”有自己的标识 what,有自己的两个通用参数 arg1arg2,有自己的具体消息内容 obj,似乎消息就是一个通过标识传递数据的功能,但其实里面大有门道。

在使用 Message 时,默认情况下越早发送的消息越早被处理,这是因为默认的消息即为同步消息,而实际上消息有三种:同步消息、异步消息、障栅消息:

  1. 在默认情况下均为 同步消息,同步消息的意义即:除非指定 Message 的执行时间,否咋 Message 会以队列(FIFO,先进先出)的机制顺序处理消息。
  2. 异步消息则不受顺序的限制,即使在同步消息阻塞的情况下,依然可以处理异步消息。
  3. 障栅消息本身并不携带额外的数据,可以看成是一个阻塞器,它用于阻塞同步消息而对异步消息没有影响,可以看成是让异步消息优先执行的一个调节器。

而消息是否同步,并不是由 Message 本身决定的,而是由处理消息的 Handler 决定的。


2. 同步消息

在默认初始化 Handler 时,不论是调用无参构造方法,还是传递 Looper 的构造方法,或者重写回调的构造方法,其实源码里都有这么一个内部调用:

1
2
3
4
5
6
7
8
9
public Handler() {
this(null, false);
}
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@Nullable Callback callback) {
this(callback, false);
}

内部调用了有布尔值的构造方法:

1
2
3
4
5
6
// 省略了其他几个带布尔值的构造方法,详细可查源码
@UnsupportedAppUsage
public Handler(boolean async) {
this(null, async);
}
......

这些带布尔值的内部构造方法,其布尔值含义都是一样的:async,是否异步。可以看到,当我们实例化 Handler 没有显式传入一个 true 作为参数时,默认调用的内部构造方法均使用了 false 作为参数,也即:默认情况下不使用异步


3. 异步消息

如果需要使用异步消息,则需要实例化 Handler 时手动指定 async 参数。从源码可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@hide
@UnsupportedAppUsage
public Handler(boolean async) {
this(null, async);
}

@hide
public Handler(@Nullable Callback callback, boolean async) {
......
}

@hide
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
......
}

由于这 3 个可以指定 async 的构造方法,均添加了 @hide@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
33
34
35
36
37
/**
* Use the {@link Looper} for the current thread with the specified callback interface
* and set whether the handler should be asynchronous.
*
* Handlers are synchronous by default unless this constructor is used to make
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
* with respect to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
*
* @param callback The callback interface in which to handle messages, or null.
* @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
* each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
*
* @hide
*/
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}

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 来实例化 Handler。手动指定该方法的参数 async = true,即可使得该 Handler 所发送的消息均为 异步消息


4. 障栅消息

障栅消息的本质也只是一个 Message,但是其 targetnull(其他消息不能设置为 null,否则会报异常),且 arg1 设置为一个从 0 开始每次自增 1 的 token,用于标识不同的障栅消息。前面已经介绍到,障栅消息的作用是阻塞添加到消息队列的时间比它晚的同步消息,因此障栅消息添加到队列时,会根据添加的时间 when 来插入到对应的消息位置。为了便于理解,这里把 when 比障栅消息小的最后一个消息(即障栅消息前一个)称为 LAST,把这个消息的下一个(即障栅消息后一个)称为 ONEMORE。

(1)在 MessageQueue 中发送障栅消息的源码:

1
2
3
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}

其调用了另一个私有同名方法:

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
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) {
// mNextBarrierToken 从 0 开始
final int token = mNextBarrierToken++;
// 障栅消息
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
prev = p;
// p 指向下一条消息
p = p.next;
}
// 循环完成后,会找出所有 when 比障栅消息更小的 Message
// 且按照原顺序连接在单链表中,这些消息不会阻塞。
// 此时 LAST 即是 prev,而 p 则指向了 ONEMORE。
}
if (prev != null) { // invariant: p == prev.next
// 说明循环进到了内部,也即障栅消息将位于 LAST 和 ONEMORE 的中间
// 则障栅消息的下一条为 ONEMORE,即 p
msg.next = p;
// LAST 的下一条为障栅消息
prev.next = msg;
} else {
// 说明循环没有进入,也即障栅消息将位于消息队列的首位
// 则障栅消息的下一条为原先消息队列的第一条消息
msg.next = p;
// 消息队列的第一条消息变为障栅消息
mMessages = msg;
}
return token;
}
}

并返回了一个 token,当一个障栅消息被加入到 MessageQueue 后,比障栅消息被添加的时间 when 更晚的同步消息将被阻塞,而异步消息不受影响,直到使用和返回值相同的 token 作为参数调用 removeSyncBarrier(int token) 将该障栅消息移除后,同步消息才恢复处理。

从源码中可以得知,这一步其实只是把一个障栅消息插入到 MessageQueue 中,插入的位置是由障栅消息的 when 决定的,而障栅消息的 when 则是在公有无参 postSyncBarrier() 中,传入了一个 SystemClock.uptimeMillis(),因此障栅消息的添加无法自定义插入点,只能在代码中手动在想要阻塞的同步消息的前面执行 postSyncBarrier()

(2)接下来是移除障栅消息 removeSyncBarrier(int token) 的源码:

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
/**
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
// 从消息队列的第一个元素开始
Message p = mMessages;
//遍历消息队列的所有元素
// 只有 p.targe == 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.");
}
// 是否需要唤醒
final boolean needWake;

if (prev != null) {
// 说明目标障栅消息不是第一个消息
// 则将障栅消息的前一条消息的 next 指向障栅消息的下一条
prev.next = p.next;
// 因为障栅消息之前有消息,还没有阻塞,所以不需要唤醒
needWake = false;
} else {
// 如果障栅消息是第一条消息
// 则消息队列的第一条消息直接设置为障栅消息的下一条
mMessages = p.next;
// 如果当前消息(原先障栅消息的下一条)为 null,说明消息队列中没有消息
// 如果当前消息的 target != null,说明
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);
}
}
}

移除障栅消息就比较简单了,就是遍历消息队列找到 target == null 且 token 对应的消息并移除即可,满足某些条件时还需要唤醒 native 层的消息队列。