这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
熟悉又陌生的Handler-2
接上文:熟悉又陌生的Handler-1
enqueueMessage消息如何进入队列
Handler发送一个消息,最终就是把消息放入消息队列中:
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;
}
// 标记当前Message正在被使用。
msg.markInUse();
// when就是前面传入的希望消息被处理的时间,一样打包给message对象
msg.when = when;
Message p = mMessages;
boolean needWake;
// caseA:队列里边无消息了,或者消息预期被处理的时间是最早的,则当前插入的消息是队列头
// Handler的sendMessageAtFrontOfQueue这个API就是通过when=0,
// 来达到往消息队列头部插入消息的功能
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 {
// caseB: 队列里边还有消息
// 这个时候是否需要唤醒队列的条件:
// 队列处于挂起态 && 消息头是一个同步屏障消息(target为null)&& 新入队的消息是异步消息
// 这个时候因为异步消息可以通过同步屏障,如果队列挂起了
// 就需要唤醒队列保证异步消息能被取出
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 遍历整个消息队列
for (;;) {
prev = p;
p = p.next;
// 到了消息队列尾,或者新入队列的Message预期被处理的时间早于正在遍历的消息
// 那么就找到了对应的位置
if (p == null || when < p.when) {
break;
}
// 如果遍历的过程中发现新消息前面已经有了异步消息,那么不需要唤醒
// 因为此时消息队列已经处于运行状态
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 队列插入
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 如果需要唤醒,调用nativeWake唤醒消息队列 ,实际实现后文再做分析
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
复制代码
SyncBarrier同步屏障
上面的分析过程中,接触到了同步屏障这个概念,所谓的同步屏障,就是一种特殊的消息,当消息队列遇到同步屏障的时候,会优先取异步消息处理,屏蔽掉同步消息。
在ViewRootImpl.java中,有这样一行调用:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 往MainLooper中发送一个同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
复制代码
postSyncBarrier实现如下:
private int postSyncBarrier(long when) {
synchronized (this) {
// token表示同步屏障的标识
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;
}
}
复制代码
有发送同步屏障的API,那么也就会有移除同步屏障的API(当然同步屏障系列API不允许被App调用),removeSyncBarrier:
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 根据token,找到待移除的同步屏障
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
// 没找到(没添加对应的同步屏障或者已经被移除)直接抛异常
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
// needWake为false
final boolean needWake;
// 如果同步屏障是在队列中间,移除同步屏障。此时队列已经在循环了
// 不需要执行唤醒操作
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
// 如果同步屏障是在队列头部,将队列头指向同步屏障的下一条message
mMessages = p.next;
// 移除完队列头的同步屏障后
// 如果消息队列空了,或者下一个消息不是同步屏障,唤醒消息循环
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// 如果需要唤醒消息循环,那么调用nativeWake 唤醒
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
复制代码
next消息如何从队列中取出
分析完了在消息是如何进入队列的,我们再来看下,队列中的消息是如何被取出来的,上文在Looper.loop的分析中,我们知道了在loop方法中,通过调用next取出一条消息:
Message next() {
// mPtr=0表示消息队列已经退出了,队列中自然也就没有消息需要被处理了
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 标识有多少IdleHandler需要处理
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 下一个消息来到之前,需要等待的时长
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 挂起操作,底层实现,后文再做分析
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 前面说过,对于target==null,对应的message是同步屏障
// 当遇到同步屏障的时候
if (msg != null && msg.target == null) {
// 取出第一条异步消息赋值给msg字段
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 {
// 到这里就表示:取出了一条可被处理的消息
// 标记队列的挂起状态为false
mBlocked = false;
// 消息取出了,更新队列头
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
// 掐掉取出消息的next字段
msg.next = null;
// 标记这个消息正在被使用然后返回这个Message对象给loop去分发
msg.markInUse();
return msg;
}
} else {
// 如果队列中没有消息了,那么就可能需要一直等待
nextPollTimeoutMillis = -1;
}
// 如果队列正在推出,那么返回null
if (mQuitting) {
dispose();
return null;
}
// 接下来进入idlehandler时间,当消息队列处于空闲状态的时候
// 会消费这些idlehandler。
// pendingIdleHandlerCount初始值为-1
// 第二个条件表示没有消息或者消息队列处于空闲状态
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// 那么从ArrayList<IdleHandler>中知道有多少idleHandler需要处理
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 如果连idleHandler也没有,那么队列真的可以休息挂起了
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// 初始化一下mPendingIdleHandlers这个字段,size最大为4
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 开始处理idleHandlers
for (int i = 0; i < pendingIdleHandlerCount; i++) {
// 一个一个取出idleHandlers
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
// 执行idleHandler的任务,并根据queueIdle的返回值
// 决定这个idleHandler的任务执行完了之后是不是要
// 从消息队列的idleHandles中移除
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 如果需要移除,就remove
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置idleHandler个数为0,以保证不会再次重复运行
pendingIdleHandlerCount = 0;
// 当调用处理了idleHandler时,新的Message可能已经被发送进来了
// 或者其它消息的待处理时间到了,这个时候消息队列便不需要继续挂起
nextPollTimeoutMillis = 0;
}
}
复制代码
IdleHandler队列空闲时的Hook
上面提到了IdleHandler,我们可以通过这个API,实现当消息队列空闲的时候,做一些事情,使用也很简单,IdleHandler本身就是一个接口:
private MessageQueue.IdleHandler myIdleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
doSomething();
return false;
}
};
Looper.myQueue().addIdleHandler(myIdleHandler)
复制代码
需要注意的是,idleHandler是在Looper空闲的时候才会执行,执行时机是不可控的,使用的时候你应该明确知道你需要这个场景,举个例子比如一些必须的初始化任务,就不适合用IdleHandler来做,因为初始化任务都应该有一个明确的执行时机,使用IdleHandler去实现,如果MessageQueue中一直有消息要处理,那么没办法保证在对应的时机到了,对应的任务被执行。
可以使用IdleHandler的场景举例如下:
- Activity启动优化:onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间。
- 以往当我们希望在View绘制完了之后做一些操作的时候,我们可能是通过post系列API实现,实际上也可以通过IdleHandler实现。
小结:
至此,Java层的消息循环机制,大体流程已经结束了,其中涉及到了几个native调用,我们后文再做分析,先用流程图的方式总结一下整个消息机制在Java层是如何运转的。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END