前言
Handler消息机制是Android中提供的一种线程间通信机制。说到线程间通信,懂Java的我们都知道可以通过synchronized、(wait/notify)实现线程间通信,但是这种机制会产生锁的竞争、线程的阻塞。为保持用户界面流畅UI线程不能被阻塞,耗时的任务又不能在UI线程操作,所以需要单独开一个工作线程,但是UI线程是非线程安全的,所以除UI线程外其他线程又不可执行UI操作,最后还是要回到UI线程更新UI,这就需要多线程之间的通信。可Java中线程间通信又都是阻塞式方法,所以传统的Java多线程通信方式在Android中并不适用。
为此Google开发人员就不得不设计一套UI线程与Worker线程通信的方法。既能实现多线程之间的通信,又能完美解决UI线程不能被阻塞的问题。Handler机制应用而生。
Handler消息机制符合生产者消费者模型,有以下几个角色组成。Looper、MessageQueue、Message、Handler,下面分别来介绍。
Handler整体工作流程
简要概述下Handler消息机制的整体运作流程,然后分别拆解每个流程的一些核心思想
Handler通过sendMessage方法将消息发送到MessageQueue中,Looper不断的循环从MessageQueue获取消息,然后拿到消息之后交给Handler处理。工作流程图如下:
分析Looper的关键源码
Looper是线程隔离的。每个线程拥有独立的Looper对象,线程隔离的实现方式是通过ThreadLocal,接下来我们来看下Looper类的关键代码
Looper类暴露了一个静态方法prepare用来进行Looper对象的创建
public static void prepare() {
prepare(true);
}
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));
}
可以看到,Looper对象是存储在ThreadLocal(线程私有变量)中的。第一次从ThreadLocal中获取,如果不存在,则创建Looper对象,然后存入到ThreadLocal中。注意对主线程的Looper对象,Looper类单独做了处理,暴露了两个静态方法用于创建主线程的Looper对象和获取主线程的Looper对象
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
然后在看下myLoop方法,该方法用来获取当前线程的Looper对象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
然后继续分析loop方法
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
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
final Printer logging = me.mLogging;
//记录消息分发前,利用这个可以统计消息处理的时间,检测卡顿测原因,BlockCanary就是实现自己的logging,来分析日志数据。
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// 将消息分发给Handler处理 msg.target就是该消息携带的Handler
msg.target.dispatchMessage(msg);
//记录消息分发后
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// 消息回收
msg.recycleUnchecked();
}
}
可以看出来loop是一个死循环,不断的从MessageQueue中读取消息。然后调用msg.target.dispatchMessage(msg);分发给Handler处理。消息处理完毕之后进行回收。当然还有一些额外获取消息队列、线程等方法,就不再一一介绍,看源码即可。
分析MessageQueue
MessageQueue 可以理解为一个消息队列,但是实际并不是采用队列这种数据结构,而且一个单链表
MessageQueue 主要用来存储消息和获取消息,是一个消息容器。接下来我们看下MessageQueue的关键代码。
Message next() {
// Looper循环如果退出了,这里返回null,消息循环退出
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // idleHandler数量
int nextPollTimeoutMillis = 0;// 睡眠时间
// 死循环
for (;;) {
// 这里调用了C++层,实现阻塞,CPU休眠,nextPollTimeoutMillis为休眠时间
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 系统开机到现在的运行时间,相对时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// msg.target == null 只有一种可能,该消息是屏障消息
if (msg != null && msg.target == null) {
// 这里遇到屏障之后,会跳过所有的同步消息,直接获取异步消息,相对于提高了异步消息的一个优先级。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// when代表消息的执行时刻
if (now < msg.when) {
// 还没到消息的执行时间,继续睡眠(msg.when - now)的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 到达消息的执行时间,取出消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
// 标记消息在使用
msg.markInUse();
// 返回消息
return msg;
}
} else {
// 消息队列没有消息,nextPollTimeoutMillis = -1 一直睡眠下去
nextPollTimeoutMillis = -1;
}
// 退出了
if (mQuitting) {
dispose();
return null;
}
// 这里是处理IdleHandler,条件为,消息队列没有消息或者延迟消息还没到时间
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 执行IdeHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// keep = false 执行一次就会移除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置pendingIdleHandlerCount = 0,所以IdleHandler只会执行一次
pendingIdleHandlerCount = 0;
// 执行到IdleHandler之后,这会可能有消息到来了,这里nextPollTimeoutMillis=0,直接开始运行,不会睡眠了
nextPollTimeoutMillis = 0;
}
}
可以看出来next方法是一个死循环,不断地从MessageQueue中取出消息。从代码注释看出有几个关键词: 同步屏障、异步消息、IdleHandler、阻塞睡眠,我们稍后解释
继续分析下enqueueMessage方法
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) {
// 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;
}
// 唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
分析Handler
先看下Handler的构造函数
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(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;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
构造函数重载的比较多,稍微看下,这里主要分析下dispatchMessage方法,上一节说到
Looper循环loop方法取到消息之后会调用msg.target.dispatchMessage方法,这个target就是消息携带的Handler对象,这里分析下Handler的dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
分析下方法回调的优先级,第一优先级,如果消息设置callback,则执行handleCallback
private static void handleCallback(Message message) {
message.callback.run();
}
比如我们平时handler.post(Runnable runnable) ,这时候内部创建了一个Message,并且message.callback=runnable;所以消息处理的时候会执行run方法。第二优先级,如果构建Handler的时候传入了Callback,则会执行这个callback的handleMessage方法
public interface Callback {
public boolean handleMessage(Message msg);
}
此方法带有返回值,如果返回了false,则证明此消息没有被处理掉,那么继续回调handleMessage方法。
public void handleMessage(Message msg) {
}
如果返回true,则已经处理完毕,return结束。这样一次完整的消息分发就结束了!
分析Message
public final class Message implements Parcelable {
public int what;// 消息标识
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
static final int FLAG_ASYNCHRONOUS = 1 << 1;// 标记是否是异步消息
int flags;
long when;// 消息执行时间
Bundle data;
Handler target; // 携带的handler对象
Runnable callback;// 回调runnable
Message next;// 链接下一条消息
private static final Object sPoolSync = new Object();
private static Message sPool;// 消息池
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;// 消息池默认大小50
}
Message就是一个数据讯息载体,这里Message的设计采用了享元设计模式,减少了对象的频繁创建,实现了对象复用,消息池默认大小50
关键词解耦
- 同步消息屏障
同步屏障可以通过MessageQueue#postSyncBarrier函数来设置,代码如下
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
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;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到,就是将消息队列插入了一条消息,根据消息的when时间进行排序放到对应的位置,特殊之处在于该条消息的target == null。所以这一步就对接上我们分析MessageQueue#next方法中的注释了。所以,从代码层面上来讲,同步屏障就是一个Message,一个target字段为空的Message。
- 异步消息
通常我们使用Handler发消息时,这些消息都是同步消息,如果我们想发送异步消息,那么在创建Handler时使用以下构造函数中的其中一种(async传true)
public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);
然后通过该Handler发送的所有消息都会变成异步消息,也可通过Message#setAsynchronous()设置为异步消息。
异步消息是与同步屏障一起连用的。分析MessageQueue#next()取消息时,如果检查到同步屏障,那么会跳过屏障之后的同步消息,直接获取异步消息,所以同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。
- 同步屏障的使用场景
Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步障碍,确保mTraversalRunnable优先被执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//内部通过Handler发送了一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalRunnable调用了performTraversals执行measure、layout、draw
为了让mTraversalRunnable尽快被执行,在发消息之前调用MessageQueue.postSyncBarrier设置了同步屏障
问题思考?
- Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
通过分析MessageQueue#next得知,消息队列在没有消息时,或者延时消息没到时间,会调用nativePollOnce()进行睡眠,这是native层的方法,通过native层实现的cpu的休眠,所以并不是无限循环的执行,一直占用着资源。有了睡眠自然有唤醒操作,在分析MessageQueue#enqueueMessage方法,我们得知,在有同步消息到来,或者新消息执行时间小于队列头部的消息执行时间,这里会调用nativeWake执行唤醒操作。 - Handler中延迟消息的实现?
通过上面的分析,其实延时消息的实现我们已经知道了,首先延时消息插入消息队列,会按照when时间进行排序,放到指定的位置,如果时间没到,那么会调用nativePollOnce()进行阻塞休眠,时间到了被执行。当然在休眠期间会有唤醒操作,MessageQueue#enqueueMessage,每次唤醒都会比对下时间,时间没到,还是会继续睡眠 - IdeHandler怎么理解
通过以上的分析,其实IdleHandler是一种闲时任务处理机制,在MessageQueue#next方法中得知,在消息队列没有消息时,或者延时消息还没到时间,就会检查是否存在IdleHandler,如果有则会遍历调用所有的IdleHandler调用其queueIdle方法进行任务执行。