[toc]
问题1:Handler机制是怎么实现线程间通信的?
问题2:post(runnable)方法发送的runnable为什么能在主线程执行?
创建
首先,看一下创建Handler的过程:
//Handler.java
final Looper mLooper;
final MessageQueue mQueue;
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper(); //获取当前线程的looper实例
...
mQueue = mLooper.mQueue; //当前线程的消息队列
mCallback = callback; //用于处理消息
mAsynchronous = async;
}
在Handler的构建方法中,主要是字段赋值,通过Looper.myLooper()
获取了Looper实例,接下来就看一下Looper.myLooper()
的源码。
//Looper.java
//ThreadLocal用于线程局部变量,值与调用线程关联
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/**
* 返回与当前线程关联的Looper对象。如果调用线程没有与循环器关联,则返回null。
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
...
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
注:ThreadLocal类型用于线程局部变量,它的值将保存在thread.threadLocals
字段,get/set时通过Thread.currentThread()
方法获取当前线程thread实例来访问。
Looper.myLooper()
返回的looper来自静态字段sThreadLocal
,而sThreadLocal
的值是在Looper.prepare()
方法中创建的looper实例。(Looper.prepare()
并不是在创建handler时调用,而是在之前调用)
- 主线程已在
ActivityThread.main()
方法中调用了Looper.prepareMainLooper()
及Looper.loop()
,无需我们负责; - 在其他线程创建Handler时,必须在
new Handler()
前,调用Looper.prepare()
,否则会引发RuntimeException
。
分析到这里,可以知道handler.mLooper
引用的looper是和线程关联的,是线程局部变量,在Looper中创建的消息队列自然也和线程关联。所以Handler在创建时便和线程绑定了,由handler发送的消息,才能在创建handler的线程处理。
发送消息
handler最常见的用法是sendMessage(msg)
和post(runnable)
,先看handler.sendMessage()
方法的源码:
//Handler.java
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
//SystemClock.uptimeMillis()返回开机后的毫秒数
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//参数uptimeMillis指定处理消息的时间
public boolean sendMessageAtTime(@NonNull 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);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; //消息发送的目标,表示消息将投递到当前handler处理
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); //将消息放入队列
}
经过几层调用,添加了预定处理时间,设置了发送目标后,消息被放入handler.mQueue
,之前已经分析过这个消息队列和创建handler的线程绑定。
另外在handler.enqueueMessage()
中设置了msg.target = this
,这说明发送消息和处理消息一定是同一个handler。
然后看一下queue.enqueueMessage()
方法。
//MessageQueue.java
private long mPtr; // 大概相当于native层的线程id
Message mMessages; //链表:保存等待处理的消息
private boolean mBlocked; //指示消息队列关联的线程是否阻塞
private boolean mQuitting; //消息队列是否已废弃
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(); //回收message对象,保存至Message.sPool
return false;
}
msg.markInUse();
msg.when = when; //消息预设的投递时间
Message p = mMessages;
boolean needWake; //是否需要唤醒线程
//根据when决定消息插入队列的位置
if (p == null || when == 0 || when < p.when) {
// 新的头,如果阻塞,唤醒事件队列。
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 插入到队列中间。通常我们不需要唤醒事件队列,
// 除非队列前面有一个屏障,并且消息是队列中最早的异步消息。
// p.target == null代表一个‘同步屏障’消息,它之后的同步消息暂时不投递
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); //唤醒线程
}
}
return true;
}
MessageQueue.enqueueMessage()
的主要工作是根据参数when将消息插入队列适当位置,判断线程是否阻塞,是否需要唤醒线程。
总结一下:
-
handler.sendMessage(msg)
的过程,就是将msg放入handler.mQueue.mMessags
。 - 消息将被 发送它的handler 处理
下面看一下handler.post()
方法的源码:
//Handler.java
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
很明显,post()方法只是生成一个message对象,然后通过sendMessage...()
方法发送。不同的是这里设置了message.callback
字段,另外使用过handler.post()一定知道,这些消息不会通过handler.handleMessage()
处理,那么post()产生的消息会怎么处理呢?我们在“分发消息”部分分析。
分发消息
消息队列中的消息是在Looper.loop()
方法中分发的,直接看源码。
//Looper.java
/**
* 在此线程中运行消息队列。一定要调用quit()来结束循环。
*/
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); //从消息队列取出消息。(可能阻塞)
//正常时候,队列中没有消息,会阻塞线程,不会返回null
if (msg == null) {
// 没有消息 表示消息队列正在退出。
return;
}
...
// 确保观察者在处理事务时不会改变。
final Observer observer = sObserver;
...
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
...
try {
//将消息投递到handler。(msg.target为发送消息的handler)
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
...
}
...
msg.recycleUnchecked(); //回收msg实例
}
}
主要逻辑很简单,就是从消息队列取出消息,投递给指定的handler,然后回收消息。所以在这里只是把消息分发到了handler,然后再看handler.dispatchMessage(msg)
怎么分发消息:
//Handler.java
@UnsupportedAppUsage
final Callback mCallback; //创建handler时设置,用于处理消息
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
//post(runnable)产生的消息,在此处理
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run(); //message.callback为Runnable
}
/**
* 子类必须实现此方法以接收消息。
*/
public void handleMessage(@NonNull Message msg) {
}
/**
* 创建Handler实例时使用,避免实现Handler子类
*/
public interface Callback {
boolean handleMessage(@NonNull Message msg);
}
handler.dispatchMessage(msg)
的逻辑很简单,按照以下优先级分发消息 msg.callback
> mCallback
> handleMessage()
-
handleCallback()
直接运行message.callback
-
handleMessage()
需要在子类中实现 -
handler.mCallback
只有一个方法,功能与handler.handleMessage()
相同,只是省得写Handler子类。(感觉没什么用)
在Looper.loop()
中调用了MessageQueue.next()
,这里也看一下:
//MessageQueue.java
private long mPtr; // 大概相当于native层的线程id
Message mMessages; //链表:保存等待处理的消息
private boolean mBlocked; //指示消息队列关联的线程是否阻塞
private boolean mQuitting; //消息队列是否已废弃
@UnsupportedAppUsage
Message next() {
...
int nextPollTimeoutMillis = 0; //预设阻塞时长
for (;;) { //注意:没找到可返回的消息时,会循环
...
//使线程阻塞,nextPollTimeoutMillis为预设阻塞最长时间,可以提前唤醒
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 尝试检索下一条消息。如果找到则返回。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//msg.target==null代表一个‘同步屏障’消息,它之后的同步消息暂时不投递
// 队列头是同步屏障,则查找下一个异步消息。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果还没有到消息预设时间,设置阻塞时间,下一次循环时阻塞
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 {
// 没有消息,使线程一直阻塞。enqueueMessage()中有唤醒逻辑
nextPollTimeoutMillis = -1;
}
// 现在处理退出消息,所有挂起的消息都已处理。
if (mQuitting) {
dispose();
return null;
}
...
}
...
}
}
总结
- 在主线程创建handler实例后,可以在不同线程使用这个handler实例发送消息,这些消息都会放到主线程的消息队列中,而主线程在
Looper.loop()
方法中不断地取出消息处理消息。 - 主线程调用
Looper.loop()
后会无限循环,那么Activity生命周期方法、屏幕刷新、点击事件等只能在处理消息时执行。 - 在我们自定义的线程也可以使用Looper,使线程长久运行。
参考
- 源码:andorid-29
- 深入理解MessageQueue