主线程有耗时操做,耽误了View的绘制。html
参考:android
显示器每隔16ms会刷新一次,可是只有用户发起重绘请求才会调用onDraw。bash
不会,会等待下一个Vsync信号。app
对于底层显示器,每间隔16.6ms接收到VSYNC信号时,就会用buffer中数据进行一次显示。因此必定会刷新。(用的旧的数据)异步
代码发起的View的重绘不会立刻执行,会等待下次VSYNC信号来的时候才开始。何时绘制没影响。oop
mTraversalScheduled这个变量是为了过滤一帧内重复的刷新请求,初始值是false,在开始这一帧的绘制流程时候也会从新置为false(doTraversal()中,一下子分析),同时,在取消遍历绘制 View 操做 unscheduleTraversals() 里也会设置为false。也就是说通常状况下在开始这一帧的正式绘制前,在这期间重复调用scheduleTraversals()只有一次会生效。这么设计的缘由是前面已经说了和ViewRootImpl绑定的是DecorView,当刷新时候会对整个DecorView进行一次处理,因此不一样view触发的scheduleTraversals()做用都是同样的,因此在这一帧里面只要有一次和屡次刷新请求效果是同样的。布局
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true; //防止屡次调用
// 发送同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
performTraversals();
...
}
}
复制代码
界面上任何一个 View 的刷新请求最终都会走到 ViewRootImpl 中的 scheduleTraversals() 里来安排一次遍历绘制 View 树的任务。post
scheduleTraversals() 会先过滤掉同一帧内的重复调用,确保同一帧内只须要安排一次遍历绘制 View 树的任务,遍历过程当中会将全部须要刷新的 View 进行重绘。spa
scheduleTraversals() 会往主线程的消息队列中发送一个同步屏障,拦截这个时刻以后全部的同步消息的执行,但不会拦截异步消息,以此来尽量的保证当接收到屏幕刷新信号时能够尽量第一时间处理遍历绘制 View 树的工做。
发完同步屏障后 scheduleTraversals() 将 performTraversals() 封装到 Runnable 里面,而后调用 Choreographer 的 postCallback() 方法。
postCallback() 方法会先将这个 Runnable 任务以当前时间戳放进一个待执行的队列里,而后若是当前是在主线程就会直接调用一个native 层方法,若是不是在主线程,会发一个最高优先级的 message 到主线程,让主线程第一时间调用这个 native 层的方法。
native 层的这个方法是用来向底层订阅下一个屏幕刷新信号Vsync,当下一个屏幕刷新信号发出时,底层就会回调 Choreographer 的onVsync() 方法来通知上层 app。
onVsync() 方法被回调时,会往主线程的消息队列中发送一个执行 doFrame() 方法的异步消息。
doFrame() 方法会去取出以前放进待执行队列里的任务来执行,取出来的这个任务其实是 ViewRootImpl 的 doTraversal() 操做。
doTraversal()中首先移除同步屏障,再会调用performTraversals() 方法根据当前状态判断是否须要执行performMeasure() 测量、perfromLayout() 布局、performDraw() 绘制流程,在这几个流程中都会去遍历 View 树来刷新须要更新的View。
等到下一个Vsync信号到达,将上面计算好的数据渲染到屏幕上,同时若是有必要开始下一帧的数据处理。