我们的消息是如何传送到UI线程的?Android消息机制

Author Avatar
MoonChen 8月 07, 2017

由于Andoird的UI元素采用的单一线程模型,只能在UI线程进行更新,于是我们用来更新UI,常用Handler进行更新,用法如下:

1
2
3
4
5
6
7
8
mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
public boolean handleMessage(Message msg) {
//do ui update
return false;
}
});
Message message = new Message();
mHandler.sendMessage(message);

用法简单明了,接着我们直接来体会一下 消息是如何被传送到主线程的。

0x00 有些什么?

Handler: 我们通过它来发送消息。
Looper: 构造Handler时,需要传入一个 Looper对象。例子中我们传入了主线程的Looper。

MessageQueue:当我们使用 Handler将一条Message sendMessageDelayed 出去,在Handler源码中追踪,调用路径如下:

1
2
3
4
5
6
7
Handler.sendMessage
sendEmptyMessage
sendEmptyMessageDelayed
sendMessageAtTime
enqueueMessage
msg.target = handler;// set target -> Handler
MessageQueue.enqueueMessage(msg, uptimeMillis)

这边出现了我们的新的对象 MessageQueue。很明显的,sendMessage的操作,其实是将Message 放入了MessageQueue中。

我们先来大致梳理一下3者的关系:

img

简言之,我们在子线程,生成了一个持有主线程 Looper的Handler。

  1. 我们通过Handler将Message 传递到了主线程,加到了对应的MessageQueue中。
  2. 主线程的Looper读取Message,并且处理。

其中,有几个关键点:

  1. 如何找到 目标线程?
  2. MessageQueue如何对新增Message进行处理的?
  3. Message如何被消费的?

我们一个一个看。

0x01 Handler如何找到目标线程,并且发送Message?

从上面的sendXXX调用堆栈可以看出,最终调用到MessageQueue.enqueueMessage(msg, uptimeMillis)。那么,Handler中的MessageQueue 是怎么来的呢?

我们可以从构造器中找到答案:

1
2
3
4
5
6
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

来自Looper,看记得那个Looper么?-> Looper.getMainLooper().
我们来看看Looper:

1
2
3
4
5
6
7
8
9
10
11
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
...
sMainLooper = myLooper();
...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

这里出现了一个很重要的对象:sThreadLocal->ThreadLocal\ 我们来看看它。

ThreadLocal 原理

这是一个用来做线程内部存储的类,通过它可以再指定的线程中获取、存储数据,并且只有在该线程里才能获取,其他线程无法获取。而我们的不同线程的Looper 就是放在ThreadLocal中。对外提供的方法很简单:

  1. public T get()
  2. public void set(T value)

我们大致看看实现:

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
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = current.localValues;
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = current.localValues;
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
class Values {
...
void put(ThreadLocal<?> key, Object value) {
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
...
}
}
...
}

主要思路是将数据结构放在了Thread.localValues,用某种算法进行了下标计算,得出 put、get的index。

Ok,那么现在,我们的Looper、以及它的MessageQueue都非常eazy的获取到了。

0x02 MessageQueue如何对新增Message进行处理的?

首先,我们需要了解一下MessageQueue的结构。最重要的一个变量是:Message mMessages;

而在Message也有几个重要的变量:

  1. Message next; 标志下一个Message
  2. Handler target 目标Handler是什么,用于做消息派发(还记得上面代码部分,有设置过么? msg.target)。

从中我们可以看出,MessageQueue是个单链表结构。

此时,我们再回到那个函数:MessageQueue.enqueueMessage(msg, 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
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
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 {
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;
}
...
}
return true;
}

可以很明显看出,这是一个根据 msg.when 为依据,做的链表插入。这样,我们的Message就能按照正确时间的顺序,进行排列。

之外:

MessageQueue还有其他的操作,都是对单链表的操作。如:

  1. private void removeAllFutureMessagesLocked()
  2. void removeMessages(Handler h, Runnable r, Object object)

0x03 Message如何被消费的?

我们有一块很重要的东西,那就是Looper,来看看前面那张图,Looper 会从Message中,不断的读取 Message。是如何做到的呢? 我们试着来看看源码。

现在我们没有了头绪,转过来,我们先来看看主线程是怎么启动的。

主线程的启动过程中对Looper的处理。

我们知道,主线程启动,是通过ActivityThead.main(String[] args) 方法进行启动,我们一起看看:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
..
Looper.prepareMainLooper();
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
...
}

里面有几行对于looper的处理,有2个关键的操作:

  1. Looper.prepareMainLooper
  2. Looper.loop();

Looper.prepareMainLooper

prepareMainLooper会接着调用到prepare(boolean quitAllowed)

prepare的过程比较简单。我们大致看一眼Looper中的部分:

1
2
3
4
5
6
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));
}

还记得 上面的 ThreadLocal吗?prepare的过程,其实就是生成一个Looper,往ThreadLocal 中塞。

Looper.loop();

这个是最核心的部分,我们依然上源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
...
for (;;) {
Message msg = queue.next(); // might block
...
msg.target.dispatchMessage(msg);
...
}
}

第一行获取myLooper()的过程,也就解释了为什么要先做“prepare”操作。

接着,会是一个死循环,核心步骤2步:

  1. 不断的从MessageQueue.next 中读取Message。
  2. 并且将消息进行派发-msg.target.dispatchMessage(msg);

我们接着看这2步:

MessageQueue.next

这个方法会返回一个Message,但是是一个阻塞方法,在适当的时候才会返回,这也就是为什么我们可以使用sendxxxDelay(...)在一段时间后才执行的原因。

我们来看源码:

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
Message next() {
...
for (;;) {
...
synchronized (this) {
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;
}
...
}
...
// 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() 方法,里面也有一个死循环,当message.when到了之后,才会返回。

消息派发:msg.target.dispatchMessage(msg);

这步能够找到正确的target,完全依赖于在 Handler.sendMessage() 做 enqueueMessage 时设置的:msg.target = handler;

所以,能够用正确的Handler进行派发,Handler派发逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

注意哦,这里进行消息处理,是Looper调用过来的,因此是在 目标线程。

0x04 总结

我们再来回忆一下这张图:

img

发送消息流程:

  1. 新建Handler,指定Looper。
  2. 调用sendXXXX方法。
  3. Handler将消息发送到 Looper.MessageQueue中。

注意几个细节:

  1. Looper通过ThreadLocal找到。
  2. 当一个线程需要用Looper时,要先调用Looper.prepare(),目的就是为了在ThreadLocal中创建当前线程的Looper。

消息处理过程:

  1. 每个线程调用Looper.loop(),是个死循环,会不断的从MessageQueue.next读消息进行处理。
  2. MessageQueue.next也是个阻塞函数,会等到有可处理的消息时返回Message对象。
  3. 对Message消息,会调用message.target.dispatchMessage进行消息传递。

注意几个细节:

  1. 主线程已经进行了Looper.prepareMainLooper,子线程也可以用handler进行消息传递,但是得先调用 Looper.prepare() 为当前线程创建Looper。