1 什么是消息机制
一句话概括消息机制:一个线程持续监听并处理其他线程交给该线程的任务。
结合实际开发中的例子来说:所有的UI更新操作都应该是在UI线程(ActivityThread)中完成。
因为安卓的UI操作是线程不安全的。如果并发地对UI进行操作,会产生线程安全问题。即使在并发中加入锁,也会导致某些线程得不到锁而阻塞,降低执行效率;也提高了安卓的学习门槛,对初学者不利。所以,安卓系统将UI操作设计为单线程模式,只有在UI线程才能对UI进行操作。
如果其他线程需要更新UI,则需要转换到UI线程中。怎么转换到UI线程呢?就需要用到消息机制。
其他线程通过所持有的UI线程中的handler引用,发出更新UI请求。MessageQueue使用一个单链表将所有请求保存起来。Looper(UI线程中)不断从MessageQueue中读取请求,将请求分发到目标handler中处理。
在该过程中,handler既是发送请求者也是处理请求者;MessageQueue是存储请求者;Looper不断监听请求,并将请求分发到指定的handler中处理。
首先我们学习一下安卓消息机制中的几个关键组成
2 MessageQueue
消息队列,在内部使用单链表存储着本线程中待执行的所有消息。一个线程只有一个MessageQueue
。MessageQueue提供插入消息(enqueueMessage(Message,long))和读取消息(Message next())的操作。
2.1 Boolean enqueueMessage(Message msg,long when)
boolean enqueueMessage(Message msg, long when) {
// msg 必须有target也就是必须有handler
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//插入消息队列的时候需要做同步,因为会有多个线程同时做往这个队列插入消息
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();
//when 表示这个消息执行的时间,队列是按照消息执行时间排序的
//如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// p==null 表示当前消息队列没有消息
msg.next = p;
mMessages = msg;
//需要唤醒主线程,如果队列没有元素,主线程会堵塞在管道的读端,这时
//候队列突然有消息了,就会往管道写入字符,唤醒主线程
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//将消息放到队列的确切位置,队列是按照msg的when 排序的,链表操作自己看咯
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;
}
// 如果需要唤醒Looper线程,这里调用native的方法实现epoll机制唤醒线程,我们就不在深入探讨了
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
enqueueMessage是一个线程同步的方法,首先判断是否需要唤醒主线程(因为主线程会不断读取消息队列中的消息,如果没有消息,主线程会暂停不占有CPU的时间片),如果现在消息队列中没有消息,并且新插入的消息需要立即执行的时候,需要唤醒主线程。然后,根据when将消息插入到链表中的适合位置。
2.2 Message next()
next方法是一个无限循环的方法,如果链表中存储有消息,返回链表头部的消息(最先插入的消息),如果链表中没有消息,调用Binder.flushPendingCommands()使线程阻塞。
Message next()
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis
//才会返回
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行)
//一种是等到有消息产生就会返回,
//另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回
nativePollOnce(ptr, nextPollTimeoutMillis);
//nativePollOnce 返回之后才能往下执行
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) {
// 循环找到一条不是异步而且msg.target不为空的message
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay,
//计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
//表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取到消息
mBlocked = false;
//链表一些操作,获取msg并且删除该节点
if (prevMsg != null)
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
//返回拿到的消息
return msg;
}
} else {
//没有消息,nextPollTimeoutMillis复位
nextPollTimeoutMillis = -1;
}
.....
.....
}
Looper
Looper在消息机制中负责消息的分发,Looper会不断调用MessageQueue的next()方法获取消息(这时,线程也切换到了looper线程,完成了线程切换)。如果获得消息,根据该消息的目标handler(handler.target),调用Handler.dispatchMessage(msg)方法将消息分发出去。如果没有获得消息,则会一直阻塞(因为MessageQueue.next方法阻塞,导致looper也会阻塞)。
public static void loop() {
final Looper me = myLooper(); //获取TLS存储的Looper对象,获取当前线程的Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //获取Looper对象中的消息队列
....
for (;;) { //主线程开启无限循环模式
Message msg = queue.next(); //获取队列中的下一条消息,可能会线程阻塞
if (msg == null) { //没有消息,则退出循环,退出消息循环,那么你的程序也就可以退出了
return;
}
....
//分发Message,msg.target 是一个Handler对象,哪个Handler把这个Message发到队列里,
//这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写
//的handler的handleMessage方法。
msg.target.dispatchMessage(msg);
....
....
msg.recycleUnchecked(); //将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。
}
}
在Looper的loop方法中,会自动获取该线程的消息队列。
一个线程只有一个Looper,为了实现每个线程都有自己的Looper实例,安卓中使用了ThreadLocal。ThreadLocal可以为每个线程保存线程私有的对象。主线程的Looper是在ActivityThread.main方法中创建的(在安卓Application的启动中我们学习到了一个App的开始是ActivityThread的main方法,在main方法中会创建ActivityThread的实例,创建Looper,Handler,将自身绑定到AMS中)。
Handler
Handler在消息机制中负责消息的发送和消息的处理。分别提供一系列send和post方法来发送消息,dispatchMessage来处理消息。
发送消息
Handler虽然提供了很多发送消息的方法,但最后都会调用MessageQueue的enqueueMessage(msg)方法,而该方法就是将消息插入到消息队列中。
处理消息
在Looper的分析中我们知道,Looper获取到消息msg后,会调用msg.target.dispatchMessage方法来分发消息。msg.target就是获得该msg的目标handler。通过调用handler的dispatchMessage方法,Handler获得消息,并调用handlerMessage方法来处理消息。这样,就完成了消息的传递和处理。
ThreadLocal
经过前面的学习我们知道,一个线程中Looper和MessageQueue只有一个,而同一进程中多个线程是共享内存的,我们如何为每个线程保存一个Looper实例呢?这就用到了ThreadLocal。
ThreadLocal.set(T value)
public void set(T value) {
//获得当前线程
Thread currentThread = Thread.currentThread();
//根据当前线程获得Values 对象
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
//根据ThreadLocal对象和需要存储的value将value保存到适合的位置
values.put(this, value);
}
当我们调用ThreadLocal.set(T value)方法的时候,会首先获得当前线程,根据线程返回一个Values ,然后调用values.put(this, value);将对象保存到values中。继续看values.put(this, value)方法。
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
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;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
put方法中,根据ThreadLocal对象产生一个hash值,然后根据这个hash值将value保存到数组对应的下标中。
到这里我们知道了ThreadLocal的工作原理:每一个线程中,都有一个对应的Values对象,该对象中有一个数组,用于保存该线程的私有对象。当调用ThreadLocal.put方法的时候,首先会获得当前的线程,根据当前的线程来获得本线程的Values对象。然后根据ThreadLocal对象生成一个Hash值,将需要存储的对象保存到Values内部的数组中hash值对应的下标位置处。
T get()
public T get() {
// Optimized for the fast path.
//获取当前线程
Thread currentThread = Thread.currentThread();
//根据线程来获得Values对象
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
//计算hash值
int index = hash & values.mask;
if (this.reference == table[index]) {
//返回对应下标的对象
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
知道了ThreadLocal的set方法,那么get方法就很容易理解了,我就不再多说了。
下面用一张图来总结一下。
image.png
在该图中,我们创建了两个线程线程1和线程2.在主线程中创建了两个handler:Handler1和Handler2。
1.线程1持有Handler1的引用,调用Handler1的sendMessage方法将msg1发送出去。Handler1获取本线程(ActivityThread)的MessageQueue,调用MessageQueue的enqueueMessage将msg1添加到链表中。
2.线程2持有Handler2的引用,调用Handler2的sendMessage方法将msg2发送出去。Handler2获取本线程(ActivityThread)的MessageQueue,调用MessageQueue的enqueueMessage将msg2添加到链表中。
3.主线程中,Looper不断调用MessageQueue的next方法,获取消息对象。
4.Looper获得了msg1对象,msg1的target中存储着目标Handler,Looper调用Handler1.dispatchMessage方法将msg1发送到Handler1中。Handler1调用handlerMessage方法来处理msg1。
5.Looper获得了msg2对象,msg2的target中存储着目标Handler,Looper调用Handler2.dispatchMessage方法将msg2发送到Handler1中。Handler2调用handlerMessage方法来处理msg2。
注意
1.我们在自己创建的线程中创建并使用Looper时需要注意,在不适用Looper的时候需要Looper.quit(立即退出)或者Looper.quitSafely(执行完消息队列中所有的消息后才会退出),否则looper会一直阻塞在这里,导致线程不能停止。因为Looper会不断读取MessageQueue的消息,如果读取不到则会阻塞在这里,如果我们不使用的时候不对其停止,Looper会一直阻塞,造成内存泄漏。
2.ActivityThread的main方法时一个APP的入口。在main方法中,会自动调用Looper.prepareMainLooper()方法来创建主线程的Looper以及MessageQueue。所以在主线程中创建Handler我们不用先创建Looper。但是在其他线程中默认是不创建Looper的,我们需要先创建Looper,然后创建Handler,否则会报错。
3.主线程(ActivityThread)的Looper在ActivityThread.main方法中创建后,还会创建一个Handler来消息队列进行交互,这个Handler就是H。这个H有着很重要的作用,在ActivityThread将自身绑定到AMS中(通过ApplicationThread),AMS会通过ApplicationThread和ActivityThread通信,发出一系列指令(如:启动acitivity,绑定Application等),这些命令最后都会加入ActivityThread的消息队列中,最终由H来处理。
网友评论