学习总结 -- Android 消息机制各个击破

提到消息机制大家都不陌生,因为在日常开发中少不了和它打交道,郭婶也有过相关的篇章来讲述:Android异步消息处理机制完全解析,带你从源码的角度彻底理解,看完不禁感叹写得很棒很适用。说实话,郭婶这篇文章看了不下 3 遍,甚至还打印出来认真仔细研究,每次都会随笔记点东西,但都是零零散散的,这次结合《Android 开发艺术探索》来记录自己的学习过程。

Android 规定访问 UI 只能在主线程中进行,如果在子线程中访问 UI 就会出现异常,这是为什么呢?

Android 系统为什么不允许子线程中访问 UI 呢?这是因为 Android 的 UI 控件是线程不安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态。

Android 消息机制的解析通过上面郭婶的文章就差不多了,但是这次我再来各个击破。

Handler 工作原理

Handler 提供了一系列的 send() 和 post() 方法,post() 系列的方法最终都会调用 send() 系列的方法来发送消息。在一系列的 send() 方法中除了 sendMessageAtFrontOfQueue() 方法之外,其它的发送消息方法都会辗转调用到 sendMessageAtTime() 方法,但是最终都会调用 Handler 本身的 enqueueMessage() 方法。下面看看这几个方法:

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

// Handler.sendMessageAtFrontOfQueue()
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, 0);
}

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

在 enqueueMessage() 方法中首先把当前的 handler 对象存储起来:msg.target = this,然后再调用 MessageQueue 的 enqueueMessage() 方法将消息入队列,这就是通过 Handler 发送消息。

当调用 Looper.loop() 时就将消息出队列,最终消息由 Looper 交给 Handler 处理,即调用 Handler 的 dispatchMessage() 方法。dispatchMessage() 方法的实现如下:

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

首先检查 Message 的 callback 即 msg.callback 是否为 null,不为 null 就通过 handleCallback() 方法来处理消息。msg.callback 是一个 Runnable 对象,实际上就是通过 Handler 的 post 方法所传递的 Runnable 参数。handleCallback() 方法的逻辑也很简单:

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

所以通过 post() 方法来发送消息就可以写成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
Handler handler = new Handler();
new Thread(new Runnable() {
@override
public void run() {
// 这里进行耗时操作
handler.post(new Runnable() {
@override
public void run() {
// 这里进行 UI 操作
}
});
};
}).start();

这样一来对 handleCallback() 方法的理解就更深刻了。回到 dispatchMessage() 方法继续判断 mCallback 是否为 null,如果不为 null 就调用 mCallback.handleMessage() 来处理消息,Callback 的定义如下:

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

Callback 是个接口,它的意义是可以用来创建一个 Handler 实例而不需要派生 Handler 的子类。来个例子最直观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class callback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
// 3.进行 UI 操作
return false;
}
}
Handler handler = new Handler(new callback());
new Thread(new Runnable() {
@Override
public void run() {
// 1.进行耗时操作
// 2.通过 handler 的一系列方法发送消息
}
}).start();

到这 mCallback.handleMessage() 也搞清楚了。这样 dispatchMessage() 到最后就调用 Handler 的 handlerMessage() 方法来处理消息。

至此通过 Handler 发送消息和处理消息算是过了一遍了。刚刚有说到 Handler 的 enqueueMessage() 方法最终是调用 MessageQueue 的 enqueueMessage() 方法来入队列的,接下来就看看消息是怎么入队列的。

MessageQueue 工作原理

MessageQueue 被称为消息队列,它主要包含两个操作:插入( enqueueMessage )和删除( next ),但是实际上 MessageQueue 内部是通过一个单链表的数据结构来维护消息的,因为单链表在插入和删除上比较有优势。

enqueueMessage 的作用是往消息队列中插入一条消息;而 next 的作用是从消息队列中取出一条消息并将其从消息队列中移除,因为 MessageQueue 的读取操作本身会伴随着删除操作。下面来看看 MessageQueue 的 enqueueMessage() 方法:

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
boolean enqueueMessage(Message msg, long when) {
...
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) {
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

enqueueMessage() 方法的主要操作就是单链表的插入操作,它并没有把所有消息都存储起来,而是用 mMessage 来表示当前待处理的消息,然后根据时间顺序调用 msg.next 来指定下一条消息。

在 Handler 部分有说到当调用 Looper.loop() 时会将消息出队列,实际上 loop() 方法里面是调用了 MessageQueue 的 next() 方法来将消息出队列,看看 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
 Message next() {
...
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;
}
...
}
...
}
}

next() 方法里面有一个无限循环的方法,如果 MessageQueue 中没有消息,那么 next() 方法就会一直阻塞在这,直到有新的消息到来;如果 MessageQueue 中存在 mMessage (待处理消息),就将其出队列并将其从单链表中移除,然后把下一条消息赋值给 mMessage。

所以 MessageQueue 才是最终将消息入队列和出队列的,那就再来看看 Looper.loop() 的实现。

Looper 工作原理

Looper 在 Android 消息机制中扮演着消息循环的角色,具体来说就是会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就立刻处理,否则就会一直阻塞在那里。下面是 loop() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

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

首先判断 myLooper() 方法的返回值,如果返回值为 null 就抛出异常,意味着该线程还没有 Looper,否则就往下执行。

接着会无限循环的调用 queue.next(),所以 MessageQueue 的 next() 方法就是在这被调用的。每当有一个消息出队列后就将其传递到 msg.target 的 dispatchMessage() 方法中, msg.target 已经说过了,它其实就是 Handler,这样通过 Handler 发送的消息又回到了它自己的 dispatchMessage() 方法中。

唯一跳出死循环的时机是 MessageQueue 的 next() 方法返回 null。其实 Looper 提供了 quit() 和 quitSafely() 两个方法来退出一个 Looper,这两个方法的区别是:quit() 方法会直接退出 Looper;而 quitSafely() 方法只是设定一个退出标记,当消息队列中已有的消息处理完后才安全退出。

当 Looper 的 quit() 方法或 quitSafely() 方法被调用时,这两个方法内部就会去调用 MessageQueue 的 quit() 方法来通知消息队列退出,此时 MessageQueue 的 next() 方法就会返回 null。因此建议在不需要 Looper 的时候通过 quit() 或 quitSafely() 方法终止 Looper。

刚刚说到 myLooper() 方法,现在看看 myLooper() 方法:

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

sThreadLocal.get() 是个什么?请继续往下看:

ThreadLocal 工作原理

用这么一句话来描述 ThreadLocal 再适合不过:ThreadLocal 是一个线程内部的数据存储类,同一个 ThreadLocal 对象在不同线程中拥有不同的值。

其实大家都知道,在子线程中创建 Handler 时必须先执行 Looper.prepare(),因为在 Handler 的无参构造方法中会通过 (Looper)sThreadLocal.get() 检查是否存在 looper,如果不存在就会抛异常。调用 prepare() 方法正是因为该方法内部会通过 sThreadLocal.set(new Looper())给当前线程设置 looper。

先来个小例子见识下 ThreadLocal 的厉害之处:

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
public class ThreadLocalActivity extends AppCompatActivity {

private static final String TAG = "ljuns";
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_local);

// 主线程
mThreadLocal.set(true);
Log.d(TAG, "main : " + mThreadLocal.get());

// 子线程 1
new Thread(new Runnable() {
@Override
public void run() {
mThreadLocal.set(false);
Log.d(TAG, "thread 1 : " + mThreadLocal.get());
}
}).start();

// 子线程 2
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "thread 2 : " + mThreadLocal.get());
}
}).start();
}

很简单的小例子,先创建出一个 ThreadLocal 对象:mThreadLocal,然后分别在三个线程中对它进行操作,按照刚刚那句话,打印的结果应该是:主线程的 mThreadLocal 是 true,子线程 1 的 mThreadLocal 是 false,子线程 2 由于没有赋值所以应该是 null 。实际的结果怎样呢?来看看:

结果当然在意料之中,因为在此之前已经先试验过了,这就是 ThreadLocal 的奇妙之处。知其然知其所以然,接下来看看它的内部实现,首先看看 set() 方法:

1
2
3
4
5
6
7
8
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

这个方法很简洁,首先获取当前线程,然后通过 values() 方法获取当前线程的 ThreadLocal 数据。为什么这么说呢?先来看看 values() 方法:

1
2
3
Values values(Thread current) {
return current.localValues;
}

localValues 又是什么呢?其实它是 Thread 类内部的一个成员,localValues 内部有一个数组:private Object[] table,这个 table 数组才是真正存储数据的结构,ThreadLocal 的值就存在这个 table 数组中。所以实际上通过 values() 获取到的是 localValues 对象,而 localValues 里面的 table 数组才真正拥有 ThreadLocal 的数据。

回到 set() 方法中,如果拿到的 values 的值为 null,那就对其进行初始化,初始化后再通过 put() 方法进行存储。继续看 put() 方法:

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
void put(ThreadLocal<?> key, Object value) {
cleanUp();
int firstTombstone = -1;

for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];

if (k == key.reference) {
table[index + 1] = value;
return;
}

if (k == null) {
if (firstTombstone == -1) {
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}

if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}

这里只关注 put() 方法的存储规则,可以发现 ThreadLocal 的值存在 table 数组中 reference 的下一个位置。比如 reference 在 table 数组中的索引为 index,那么 ThreadLocal 的值就存在 table 数组中索引为 index + 1 的位置。

看完 set() 方法后再来看看 get() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
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);
}

get() 方法同样先是获取当前 localValues 对象,如果该对象不为 null,那就取出它的 table 数组并且找到 reference 在 table 数组中的位置,然后把 table 数组中 reference下一个位置的数据返回,因为该数据就是当前线程中 ThreadLocal 的值;如果 localValues 对象为 null 则返回初始值,默认情况下为 null。

ThreadLocal 的 set() 和 get() 方法操作的对象都是当前线程的 table 数组,因此也印证了刚刚对 ThreadLocal 的描述。理解完 ThreadLocal,相信对上面 Looper 工作原理的理解又会再次加深。

到这里才几乎算是把 Android 的消息机制走了一遍,最后贴上时序图一张来理解这个过程(刚开始学习使用时序图):

通过这个时序图可以很清晰的知道 一个线程对应一个 Looper 对象,一个 Looper 对象对应一个 MessageQueue 对象。为什么使用异步消息处理的方式就可以对 UI 进行操作了呢?这是因为 Handler总是依附于创建时所在的线程。所以在子线程中进行耗时操作后通过 Handler 把消息发送出去,最后消息又到了主线程中 Handler 的 handlerMessage() 方法进行 UI 操作。

坚持原创技术分享,您的支持将鼓励我继续创作!