Android开发Handler是如何确保UI刷新优先执行的源码解读
- 问题分析
- Handler、Looper、MessageQueue、Message四个之间的关联关系
问题1:很久前被问到,requestLayout会立刻触发绘制界面吗?答案是不会立即的,需要等待下一个VSync信号到来(VSync信号即硬件垂直同步信号,比如1秒60帧即1000ms/60约为16.7ms一次)。
当然VSync信号到来也不一定就会触发绘制呀,起码dirty了也就是有变化了才绘制吧,那就bool值标记一下不就好了。。。比如ViewRootImpl里的mTraversalScheduled变量,Choreographer里的mFrameScheduled变量都是标记作用。。。
其实嘛本质是requestLayout方法(调用checkThread()检查是否为主线程)会调用scheduleTraversals()方法,发送一条message消息(特殊的message消息即屏障消息)给handler,接着使用mChoreographer.postCallback()发送一条异步消息给handler,具体实现在Choreographer.postCallbackDelayedInternal()方法里,怎么好像就一条屏障消息和一条异步消息就搞定了?具体下面会分析。
问题2:那我们给handler发送很多消息的话,不是会堵塞UI刷新消息的执行,然后导致UI不能及时刷新吗?答案是不会的,原因就是刷新UI的时候给handler发送的是一条屏障消息和一条异步消息,具体原因下面分析。
//1.Looper的prepare静态方法里实例化了一个Looper对象,并将其放入sThreadLocal实现线程私有
//并且做了判断,也就是说每个线程最多可以有一个Looper,每个线程的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));
}
//1.Looper通过构造函数持有MessageQueue对象,而MessageQueue持有消息循环队列头指针Message mMessages
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
//2.Hanndler通过构造函数持有Looper以及Looper的MessageQueue,也说明Looper持有MessageQueue
public Handler( Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
handler.sendMessage(msg);msg.target = this;//除了屏障消息之外,同步消息和异步消息,有target并且target是Handler
//target的作用(msg的target持有的是Handler对象)
public void dispatchMessage( Message msg) {
if (msg.callback != null) {//优先回调msg自动的callback
handleCallback(msg);
} else {
if (mCallback != null) {//再尝试回调handler带的callback
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//最后才调用这个空实现,一般我们new一个handler重写这个方法处理发送消息的结果
}
}
- Handler从发送消息到处理消息的大致过程
//1. 第一阶段,就是通过handler发送消息到循环队列
public final boolean sendMessage( Message msg) {}
public boolean sendMessageAtTime( Message msg, long uptimeMillis) {}
private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
msg.target = this;//这个target的值this指向的是Handler,用于后面队列循环到这个消息时回到Handler处理
...
}
//2. 第二阶段,Looper.loop();之后,循环队列开始工作,系统早已调用主线程loop()方法了,所以主线程队列一直在工作或者等待
public static void loop() {
for (;;) {
Message msg = queue.next();//调用队列的next方法获取下一个符号条件的消息
...
try {
msg.target.dispatchMessage(msg);//回到handler的dispatchMessage方法,和上面说的enqueueMessage对应
} catch (Exception exception) {
}
}
msg.recycleUnchecked();//回收消息
}
//2. 第二阶段,MessageQueue.next()方法取出下一个符号条件的消息
Message next() {
int nextPollTimeoutMillis = 0;//等待多久时间,第一次等待时间是0,-1表示无限等待直到被nativeWakt唤醒
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);//等待时间,第一次等待时间是0表示立刻返回,即查询一次
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//本文重点:如果msg的target为空,表示这个msg是屏障消息
do {//如果是屏障消息,遍历找到下一个异步消息为止。别忘记UI刷新发送的就是异步消息哦,可能查找的就是UI刷新消息呢
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.将消息从链表取出,并标记为已经使用,然后跳出循环返回到上一层Looper.loop();
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {// No more messages.没有消息,-1无限等待直到被唤醒
nextPollTimeoutMillis = -1;
}
}
...省略掉IdleHandler的处理,就是没有消息可处理时,查询是否添加了IdleHandler,就有处理IdleHandler回调
}
}
- 到处已经分析完标题的疑问,可能还有人有疑问,总结一下
1.handler消息分为三类:
同步消息:就是我们平时使用的postMessage,sendMessage,Message的isAsynchronous()返回false,Message的target不能为空。
异步消息:同步消息通过Message的setAsynchronous(boolean async)方法可以变为异步消息,target同样不能为空。
屏障消息:同步消息的target为空时变成屏障消息。但是平时不能使用target为空的消息,因为使用完后需要对应的移除屏障消息,所以只能系统使用。
2.屏障消息的作用
屏障的作用自然是屏蔽遮挡作用,就是一个标记作用,当MessageQueue.next()方法取到的是屏障消息时,就是while循环只取异步消息,也就是遇到屏障消息时过滤不考虑同步消息,只处理异步消息,这样屏障消息就起到标记作用,提升异步消息处理优先级的作用。
3.UI刷新是异步消息,配合屏障消息的使用,异步消息优先处理,所以我们平时发送的普通消息是同步消息不会影响到UI刷新
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //插入屏障消息
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//然后再发送异步消息
4.屏障消息是target为空的消息,屏障消息使用完需要手动移除,没有暴露给开发者移除api,所以只能系统使用
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//移除屏障消息
mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}