基本概念
首先,先來過一下一些基本概念,摘抄自網(wǎng)上文章android屏幕刷新顯示機(jī)制:
在一個(gè)典型的顯示系統(tǒng)中存捺,一般包括CPU、GPU曙蒸、display三個(gè)部分捌治, CPU負(fù)責(zé)計(jì)算數(shù)據(jù),把計(jì)算好數(shù)據(jù)交給GPU,GPU會(huì)對圖形數(shù)據(jù)進(jìn)行渲染逸爵,渲染好后放到buffer里存起來,然后display(有的文章也叫屏幕或者顯示器)負(fù)責(zé)把buffer里的數(shù)據(jù)呈現(xiàn)到屏幕上趋艘。
顯示過程,簡單的說就是CPU/GPU準(zhǔn)備好數(shù)據(jù)瓷胧,存入buffer杂数,display每隔一段時(shí)間去buffer里取數(shù)據(jù),然后顯示出來那伐。display讀取的頻率是固定的,比如每個(gè)16ms讀一次诉探,但是CPU/GPU寫數(shù)據(jù)是完全無規(guī)律的。
上述內(nèi)容概括一下阳液,大體意思就是說,屏幕的刷新包括三個(gè)步驟:CPU 計(jì)算屏幕數(shù)據(jù)鹰溜、GPU 進(jìn)一步處理和緩存、最后 display 再將緩存中(buffer)的屏幕數(shù)據(jù)顯示出來墓陈。
對于 Android 而言,第一個(gè)步驟:CPU 計(jì)算屏幕數(shù)據(jù)指的也就是 View 樹的繪制過程,也就是 Activity 對應(yīng)的視圖樹從根布局 DecorView 開始層層遍歷每個(gè) View利花,分別執(zhí)行測量兑徘、布局欲侮、繪制三個(gè)操作的過程刁俭。
Display 這一行可以理解成屏幕如孝,所以可以看到,底層是以固定的頻率發(fā)出 VSync 信號的,而這個(gè)固定頻率就是我們常說的每 16.6ms 發(fā)送一個(gè) VSync 信號,至于什么叫 VSync 信號腔稀,我們可以不用深入去了解窄陡,只要清楚這個(gè)信號就是屏幕刷新的信號就可以了。
看圖润歉,CPU 藍(lán)色的這行,CPU 這塊的耗時(shí)其實(shí)就是我們 app 繪制當(dāng)前 View 樹的時(shí)間颈抚,而這段時(shí)間就跟我們自己寫的代碼有關(guān)系了贩汉,如果你的布局很復(fù)雜匹舞,層次嵌套很多晰绎,每一幀內(nèi)需要刷新的 View 又很多時(shí)锄弱,那么每一幀的繪制耗時(shí)自然就會(huì)多一點(diǎn)祸憋。
看圖会宪,CPU 藍(lán)色這行里也有一些數(shù)字,其實(shí)這些數(shù)字跟 Display 黃色的那一行里的數(shù)字是對應(yīng)的拦赠,在 Display 里這些數(shù)字表示的是每一幀的畫面巍沙,那么在 CPU 這一行里,其實(shí)就是在計(jì)算對應(yīng)幀的畫面數(shù)據(jù)荷鼠,也叫屏幕數(shù)據(jù)句携。也就是說,在當(dāng)前幀內(nèi)允乐,CPU 是在計(jì)算下一幀的屏幕畫面數(shù)據(jù)矮嫉,當(dāng)屏幕刷新信號到的時(shí)候削咆,屏幕就去將 CPU 計(jì)算的屏幕畫面數(shù)據(jù)顯示出來;同時(shí) CPU 也接收到屏幕刷新信號蠢笋,所以也開始去計(jì)算下一幀的屏幕畫面數(shù)據(jù)拨齐。
CPU 跟 Display 是不同的硬件,它們是可以并行工作的昨寞。要理解的一點(diǎn)是瞻惋,我們寫的代碼,只是控制讓 CPU 在接收到屏幕刷新信號的時(shí)候開始去計(jì)算下一幀的畫面工作援岩。而底層在每一次屏幕刷新信號來的時(shí)候都會(huì)去切換這一幀的畫面熟史,這點(diǎn)我們是控制不了的,是底層的工作機(jī)制窄俏。
當(dāng)我們的 app 界面沒有必要再刷新時(shí)(比如用戶不操作了蹂匹,當(dāng)前界面也沒動(dòng)畫),這個(gè)時(shí)候凹蜈,我們 app 是接收不到屏幕刷新信號的限寞,所以也就不會(huì)讓 CPU 去計(jì)算下一幀畫面數(shù)據(jù),但是底層仍然會(huì)以固定的頻率來切換每一幀的畫面仰坦,只是它后面切換的每一幀畫面都一樣履植,所以給我們的感覺就是屏幕沒刷新。
ViewRootImpl 和DecorView的綁定
分析 View#invalidate()
時(shí)悄晃,也可以看到內(nèi)部其實(shí)是有一個(gè) do{}while() 循環(huán)來不斷尋找 mParent玫霎,所以最終才會(huì)走到 ViewRootImpl 里去,那么可能大伙就會(huì)疑問了妈橄,為什么 DecorView 的 mParent 會(huì)是 ViewRootImpl 呢庶近?換個(gè)問法也就是,在什么時(shí)候?qū)?DevorView 和 ViewRootImpl 綁定起來眷蚓?
Activity 的啟動(dòng)是在 ActivityThread 里完成的鼻种,handleLaunchActivity()
會(huì)依次間接的執(zhí)行到 Activity 的 onCreate()
, onStart()
, onResume()
。在執(zhí)行完這些后 ActivityThread 會(huì)調(diào)用 WindowManager#addView()
沙热,而這個(gè) addView()
最終其實(shí)是調(diào)用了 WindowManagerGlobal 的 addView()
方法叉钥。
這里初始化了一個(gè) ViewRootImpl,然后調(diào)用了它的 setView()
方法篙贸,將 DevorView 作為參數(shù)傳遞了進(jìn)去投队,這個(gè)方法里還調(diào)用了一個(gè) requestLayout()
方法,進(jìn)而又調(diào)用了一個(gè) scheduleTraversals()
爵川。
在 setView()
方法里調(diào)用了 DecorView 的 assignParent()
方法敷鸦,參數(shù)是 ViewParent,而 ViewRootImpl 是實(shí)現(xiàn)了 ViewParent 接口的,所以在這里就將 DecorView 和 ViewRootImpl 綁定起來了轧膘。
每個(gè)Activity 的根布局都是 DecorView钞螟,而 DecorView 的 parent 又是 ViewRootImpl兔甘,所以在子 View 里執(zhí)行 invalidate()
之類的操作谎碍,循環(huán)找 parent 時(shí),最后都會(huì)走到 ViewRootImpl 里來洞焙。
即使是界面上一個(gè)小小的 View 發(fā)起了重繪請求時(shí)蟆淀,都要層層走到 ViewRootImpl,由它來發(fā)起重繪請求澡匪,然后再由它來開始遍歷 View 樹熔任,一直遍歷到這個(gè)需要重繪的 View 再調(diào)用它的 onDraw()
方法進(jìn)行繪制。
總結(jié)一下:其實(shí)打開一個(gè) Activity唁情,當(dāng)它的 onCreate---onResume 生命周期都走完后疑苔,才將它的 DecoView 與新建的一個(gè) ViewRootImpl 對象綁定起來,同時(shí)開始安排一次遍歷 View 任務(wù)也就是繪制 View 樹的操作等待執(zhí)行甸鸟,然后將 DecoView 的 parent 設(shè)置成 ViewRootImpl 對象惦费。
ViewRootImpl#scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//往主線程的消息隊(duì)列中發(fā)送一個(gè)同步屏障,攔截這個(gè)時(shí)刻之后所有的同步消息的執(zhí)行抢韭,但不會(huì)攔截異步消息薪贫,以此來盡可能的保證當(dāng)接收到屏幕刷新信號時(shí)可以盡可能第一時(shí)間處理遍歷繪制 View 樹的工作;
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//把 performTraversals() 封裝到 Runnable 里面刻恭,然后調(diào)用 Choreographer 的 postCallback() 方法瞧省;postCallback() 方法會(huì)先將這個(gè) Runnable 任務(wù)以當(dāng)前時(shí)間戳放進(jìn)一個(gè)待執(zhí)行的隊(duì)列里,然后如果當(dāng)前是在主線程就會(huì)直接調(diào)用一個(gè)native 層方法鳍贾,如果不是在主線程鞍匾,會(huì)發(fā)一個(gè)最高優(yōu)先級的 message 到主線程,讓主線程第一時(shí)間調(diào)用這個(gè) native 層的方法骑科;
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
- 界面上任何一個(gè) View 的刷新請求最終都會(huì)走到 ViewRootImpl 中的 scheduleTraversals() 里來安排一次遍歷繪制 View 樹的任務(wù)候学;
- scheduleTraversals() 會(huì)先過濾掉同一幀內(nèi)的重復(fù)調(diào)用,在同一幀內(nèi)只需要安排一次遍歷繪制 View 樹的任務(wù)即可纵散,這個(gè)任務(wù)會(huì)在下一個(gè)屏幕刷新信號到來時(shí)調(diào)用 performTraversals() 遍歷 View 樹梳码,遍歷過程中會(huì)將所有需要刷新的 View 進(jìn)行重繪;
- 接著 scheduleTraversals() 會(huì)往主線程的消息隊(duì)列中發(fā)送一個(gè)同步屏障伍掀,攔截這個(gè)時(shí)刻之后所有的同步消息的執(zhí)行掰茶,但不會(huì)攔截異步消息,以此來盡可能的保證當(dāng)接收到屏幕刷新信號時(shí)可以盡可能第一時(shí)間處理遍歷繪制 View 樹的工作蜜笤;
- 發(fā)完同步屏障后 scheduleTraversals() 才會(huì)開始安排一個(gè)遍歷繪制 View 樹的操作濒蒋,作法是把 performTraversals() 封裝到 Runnable 里面,然后調(diào)用 Choreographer 的 postCallback() 方法;
- postCallback() 方法會(huì)先將這個(gè) Runnable 任務(wù)以當(dāng)前時(shí)間戳放進(jìn)一個(gè)待執(zhí)行的隊(duì)列里沪伙,然后如果當(dāng)前是在主線程就會(huì)直接調(diào)用一個(gè)native 層方法瓮顽,如果不是在主線程,會(huì)發(fā)一個(gè)最高優(yōu)先級的 message 到主線程围橡,讓主線程第一時(shí)間調(diào)用這個(gè) native 層的方法暖混;
- native 層的這個(gè)方法是用來向底層注冊監(jiān)聽下一個(gè)屏幕刷新信號,當(dāng)下一個(gè)屏幕刷新信號發(fā)出時(shí)翁授,底層就會(huì)回調(diào) Choreographer 的onVsync() 方法來通知上層 app拣播;
- onVsync() 方法被回調(diào)時(shí),會(huì)往主線程的消息隊(duì)列中發(fā)送一個(gè)執(zhí)行 doFrame() 方法的消息收擦,這個(gè)消息是異步消息贮配,所以不會(huì)被同步屏障攔截住塞赂;
- doFrame() 方法會(huì)去取出之前放進(jìn)待執(zhí)行隊(duì)列里的任務(wù)來執(zhí)行泪勒,取出來的這個(gè)任務(wù)實(shí)際上是 ViewRootImpl 的 doTraversal() 操作;
- 上述第4步到第8步涉及到的消息都手動(dòng)設(shè)置成了異步消息宴猾,所以不會(huì)受到同步屏障的攔截圆存;
- doTraversal() 方法會(huì)先移除主線程的同步屏障,然后調(diào)用 performTraversals() 開始根據(jù)當(dāng)前狀態(tài)判斷是否需要執(zhí)行performMeasure() 測量鳍置、perfromLayout() 布局辽剧、performDraw() 繪制流程,在這幾個(gè)流程中都會(huì)去遍歷 View 樹來刷新需要更新的View税产;