主線程有耗時操做,耽誤了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信號到達,將上面計算好的數據渲染到屏幕上,同時若是有必要開始下一幀的數據處理。