概述
相信不管是出入Android,还是已开发多年的老司机们,肯定都对Android的Handler不会陌生,而它就是今天要介绍的Android消息机制中的一部分。
在Android系统中,有两大特色利剑:Binder IPC机制和消息机制。Android也由大量的消息驱动方式来交互,大到四大组件的工作流程,小到异步回调更新UI等等,各处都有消息机制的存在。
角色
在对消息机制进行分析之前,先来看一下消息机制中,都含有哪些角色以及他们各自的作用又是什么:
-
Message
消息本体,一切逻辑都围绕它来展开。
-
MessageQueue
消息队列,管理消息的入队和出队。
-
andler
消息机制的两端,可作为消息产生端,也可作为消息消费端。
-
Looper
消息机制运转的动力,不断的循环执行,取出消息、分发消息。
他们之间的关系,可以通过一个简单图来表示一下:
![](https://img.haomeiwen.com/i23087443/bfd4658a893a8dc3.png)
Handler
在消息机制的四个角色中,我们经常使用和见到的就是Handler了,那就先从Handler看起。
构造
Handler有很多构造方法,但是可用开发中使用的只有如下几个:
Handler()
Handler(@Nullable Callback callback)
Handler(@NonNull Looper looper)
Handler(@NonNull Looper looper, @Nullable Callback callback)
这样来看无非是可设置两个参数:Looper
和Callback
。
如果不指定Looper
,在构造时会通过Looper.myLooper()
获取当前线程的Looper
,如果当前线程没有Looper
那么会抛出异常。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
至于Callback,后面会进行分析。
作为生产者生产Message
Handler的sendEmptyMessage、sendEmptyMessageAtTime、sendEmptyMessageDelayed、sendMessage
和sendMessageDelayed
,最终都会调用sendMessageAtTime
:
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);
}
这其中的uptimeMillis = SystemClock.uptimeMillis() + delayMillis
,之所以采用SystemClock.uptimeMillis()
,是因为它是已开机时间,而如果使用System.currentTimeMillis()
在用户修改手机时间时,该值就会发生变化。
除了上面的sendMessageAtTime
,还有一个特殊的方法sendMessageAtFrontOfQueue
:
public final boolean sendMessageAtFrontOfQueue(@NonNull 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;
}
//固定uptimeMillis为0
return enqueueMessage(queue, msg, 0);
}
可以发现这两类方法最终都会通过调用方法:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//将msg的target属性指向当前Handler
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//转到MessageQueue,消息入队
return queue.enqueueMessage(msg, uptimeMillis);
}
作为消费者消费Message
当Looper
在通过MessageQueue
读取到下一条消息时,就会通过handler的dispatchMessage
分发给目标Handler来消费这条消息:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
//如果配置了Callback,就不再走Handler的handleMessage
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Looper
既然在创建Handler时需要制定或从当前线程获取Looper
,那么接下来就看一下Looper
。
构造
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从构造方法可以看出:
1.私有方法,不允许外部直接通过构造方法创建
2.初始化时会初始化MessageQueue
3.初始化时会记录当前线程
在线程中创建Looper
,可以使用prepare
:
public static void prepare() {
//必须可退出
prepare(true);
}
//quitAllowed是否允许退出Looper
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));
}
Android系统在创建主线程Looper
时,是通过prepareMainLooper
:
public static void prepareMainLooper() {
//不允许退出
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
开启死循环
Looper
既然是通过死循环,为消息机制提供运转动力,那么在创建Looper
之后,就要适时的开启死循环:
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 (;;) {
//从queue中取消息,可能会阻塞当前线程
Message msg = queue.next();
if (msg == null) {
// 取出null,说明消息机制退出,那么跳出循环
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//省略部分代码
//取到了消息,分发消息
msg.target.dispatchMessage(msg);
//省略部分代码
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
//省略部分代码
//回收这条消息
msg.recycleUnchecked();
}
}
MessageQueue
在Looper
初始化时,会初始化MessageQueue
,接下来就看一它有哪些内容。
构造
//quitAllowed是否允许退出
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
不允许退出(quitAllowed传false)
会怎样:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
//省略部分代码
}
消息入队
在前面分析Handler时,最终发送消息都会通过MessageQueue
的enqueueMessage
:
boolean enqueueMessage(Message msg, long when) {
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();
msg.when = when;
//下一个准备要分发的消息
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//没有准备要分发的消息
//或者这条消息是sendMessageAtFrontOfQueue发送的
//或者这条消息要发送的时间比下一条要早
//那么下一条就是你了
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//省略部分代码
}
//省略部分代码
}
return true;
}
消息出队
在Looper
中,会通过死循环的方式调用queue.next()
来获取下一条消息:
Message next() {
//省略部分代码
for (;;) {
//省略部分代码
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//省略部分代码
if (msg != null) {
if (now < msg.when) {
// 省略部分代码
} else {
// 取到了一个消息
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();
//返给looper
return msg;
}
}
//省略部分代码
}
}
}
我是主线程,我要做的事很多,但有优先级机制
我们知道,在Android中,主线程不只是要完成开发者写代码逻辑需求,还要完成系统对它的指示,比如刷新页面。
对于同一个Looper
来说,是可以同时存在多个Handler,可以同时向Looper
中发送消息,这其中既有Android系统中定义的各种Handler,又有开发者编写的Handler,那么如何才能让MessageQueue
首先将系统发布的msg
分发出来,能够被率先执行呢?
MessageQueue.postSyncBarrier(long when)
往消息队列头部,放入一个系统的“告示”,告知MessageQueue
接下来我会发送一些优先级高的指令,务必先执行我接下来的优先指令。
//类似于enqueueMessage,根据when合理的插入这个“告示”
//此方法被hide标记
//添加成功后会返回一个唯一的token,标识该屏障
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
//未给该msg设置target,是“告示”的身份特征
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) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
再来MessageQueue
是怎么识别“告示”的:
Message next() {
//省略部分代码
for (;;) {
//省略部分代码
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;
//当找到第一个msg.isAsynchronous() = true的消息时,就会跳出循环,首先分发这个消息
} while (msg != null && !msg.isAsynchronous());
}
//省略部分代码
}
//省略部分代码
}
}
Message.setAsynchronous(true)
通过postSyncBarrier
,系统告知MessageQueue
接下来先执行的事,那么哪些才是要先执行的事呢?就是通过msg.setAsynchronous(true)
方法,标记为true的事。
MessageQueue.removeSyncBarrier(int token)
系统除了可以在特定的场合(如刷新屏幕)添加同步屏障,告知MessageQueue
先执行特定的优先级消息之外,还可以取消同步屏障,让MessageQueue
回复正常排队执行。比如本来需要刷新下一帧,但是页面在下一帧刷新时间前被关闭了,那么就移除之前的“告知”。
一些思考
没有这个“告知”,始终在MessageQueue.next
中判断msg.isAsynchronous
为true,那么就优先分发它行不行?
如果没有这个添加“告知”和移除“告知”的存在,那么有些消息,包括系统发出的普通消息(msg.isAsynchronous = false)
,就可能永远不会被执行了(开发者把所有消息都进行msg.setAsynchronous(true)
)。必须要在合适的时机,让queue按照时间顺序,依次执行消息的分发,而不是始终将isAsynchronous
标志放在第一位。
这里放出一个问题,可以思考一下:如果将Message的setAsynchronous
进行hide处理,在MessageQueue.next
中始终判断msg.isAsynchronous
,优先分发msg.isAsynchronous == true
的消息,又是否可行呢?
MessageQueue.IdleHandler
再来回看MessageQueue.next
方法:
Message next() {
//省略部分代码
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (msg != null) {
if (now < msg.when) {
//省略部分代码
}else{
//省略部分代码
//如果找到了要分发的msg,并且到了分发时间,那么就返回给looper
return msg;
}
}
//省略部分代码
//以下代码执行条件,是未找到下一条msg或下一条msg还未到分发时间
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
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 {
//执行idler.queueIdle,保存返回结果
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
//如果不保留当前idler,那么移除
mIdleHandlers.remove(idler);
}
}
}
}
}
再来看一下接口IdleHandler
:
public static interface IdleHandler {
boolean queueIdle();
}
使用场景:
-
在Activity绘制完成后,做一些事情
-
结合HandlerThread, 用于单线程消息通知器
关于使用场景,更详细的内容,之后再详解。
死循环为什么不会阻塞App
阻塞App时,往往是不能在继续处理后续的逻辑,但是Android的消息机制,虽然是死循环,但是依然在有条不紊的接收和处理任务。这跟业务代码中,错误书写的一个局部小的死循环不同,正是由于存在这个死循环的存在,主线程才能一直轮询处理新的任务,保持应用的生机。
在消息机制中,取消息时,如果没有可分发消息或下一条要分发的消息还未到分发时间,就会进行适当的阻塞;在有消息传入时,会根据目标线程的阻塞状态,决定是否进行唤醒,已使其能够顺利的处理接下来的消息分发。
网友评论