1、概述
不論電腦,電視,手機(jī),我們看到的畫面都是由一幀幀的畫面組成的。FPS是圖像領(lǐng)域中的定義,是指畫面每秒傳輸幀數(shù),通俗來講就是指動畫或視頻的畫面數(shù)。每秒鐘幀數(shù)愈多,所顯示的動作就會愈流暢。通常,要避免動作不流暢的最低是30。FPS”也可以理解為我們常說的“刷新率(單位為Hz)”。而在Android系統(tǒng)中每隔16.6ms會發(fā)送一次VSYNC信號有可能會觸發(fā)UI的渲染。具體概念原理和流程在下面會詳細(xì)講解。
2、屏幕顯示機(jī)制
在一個典型的顯示系統(tǒng)中,一般包括CPU、GPU、display三個部分, CPU一般負(fù)責(zé)計算數(shù)據(jù),把計算好數(shù)據(jù)交給GPU,GPU會對圖形數(shù)據(jù)進(jìn)行渲染,渲染好后放到buffer里存起來,然后display(有的文章也叫屏幕或者顯示器)負(fù)責(zé)把buffer里的數(shù)據(jù)呈現(xiàn)到屏幕上。很多時候,我們可以把CPU、GPU放在一起說,那么就是包括2部分,CPU/GPU 和display。
2.1 繪制模型
① 軟件繪制模型:
這里由CPU主導(dǎo)繪圖,該模型先讓視圖結(jié)構(gòu)(view hierarchy)失效,再繪制整個視圖結(jié)構(gòu)。當(dāng)應(yīng)用程序需要更新它的部分UI時,都會調(diào)用內(nèi)容發(fā)生改變的View對象的invalidate()方法。無效(invalidation)消息請求會在View對象層次結(jié)構(gòu)中傳遞,以便計算出需要重繪的屏幕區(qū)域(臟區(qū))。然后,Android系統(tǒng)會在View層次結(jié)構(gòu)中繪制所有的跟臟區(qū)相交的區(qū)域。這種繪制的缺點(diǎn)在于繪制了不需要重繪的視圖。
② 硬件加速繪制模型
這里由GPU主導(dǎo)繪圖,該模型先讓視圖結(jié)構(gòu)失效,再記錄和更新顯示列表(Display List),這兩步是在CPU當(dāng)中完成的。最后在GUP中繪制顯示列表。這種模式下,Android系統(tǒng)依然會使用invalidate()方法和draw()方法來請求屏幕更新和展現(xiàn)View對象。但Android系統(tǒng)并不是立即執(zhí)行繪制命令,而是首先把這些View的繪制函數(shù)作為繪制指令記錄一個顯示列表中,然后再讀取顯示列表中的繪制指令調(diào)用OpenGL相關(guān)函數(shù)完成實(shí)際繪制。另一個優(yōu)化是,Android系統(tǒng)只需要針對由invalidate()方法調(diào)用所標(biāo)記的View對象的臟區(qū)進(jìn)行記錄和更新顯示列表。沒有失效的View對象就簡單重用先前顯示列表記錄的繪制指令來進(jìn)行簡單的重繪工作。使用顯示列表的目的是,把視圖的各種繪制函數(shù)翻譯成繪制指令保存起來,對于沒有發(fā)生改變的視圖把原先保存的操作指令重新讀取出來重放一次就可以了,提高了視圖的顯示速度。而對于需要重繪的View,則更新顯示列表,然后再調(diào)用OpenGL完成繪制。這種模型提高了Android系統(tǒng)顯示和刷新的速度,但是它的兼容性沒有軟件模型好,同時更消耗內(nèi)存也更加耗電。
2.2 VSYNC信號同步處理數(shù)據(jù)
在android 4.1以前UI不流暢問題較為嚴(yán)重,在4.1版本以后Android對顯示系統(tǒng)進(jìn)行了重構(gòu),引入了三個核心元素:VSYNC, Tripple Buffer和Choreographer。VSYNC是Vertical Synchronized的縮寫,是一種定時中斷;Tripple Buffer是顯示數(shù)據(jù)的緩沖區(qū);Choreographer起調(diào)度作用,將繪制工作統(tǒng)一到VSYNC的某個時間點(diǎn)上,使應(yīng)用的繪制工作有序進(jìn)行。
Android在繪制UI時,會采用一種稱為“雙緩沖”的技術(shù),雙緩沖即使用兩個緩沖區(qū)(在SharedBufferStack中),其中一個稱為Front Buffer,另外一個稱為Back Buffer。UI總是先在Back Buffer中繪制,然后再和Front Buffer交換,渲染到顯示設(shè)備中。
2.2.1 沒有VSYNC同步信號
理想情況下,一個刷新會在16ms內(nèi)完成(60FPS),也就是兩個VSync信號之間。這樣并不會出現(xiàn)問題,如上圖第一個16ms開始,此時Display顯示第0幀,CPU處理完第1幀后,GPU緊接其后處理繼續(xù)第1幀。在第二個16ms,第1幀能正常顯示在Display中。但在第二個16ms階段CPU和GPU卻并未及時去繪制第2幀數(shù)據(jù)(前面的空白區(qū)表示CPU和GPU忙其它的事),直到在本周期快結(jié)束時,CPU/GPU才去處理第2幀數(shù)據(jù)。這時當(dāng)進(jìn)入第三個16ms,此時Display應(yīng)該顯示第2幀數(shù)據(jù),但由于CPU和GPU還沒有處理完第2幀數(shù)據(jù),故Display只能繼續(xù)顯示第一幀的數(shù)據(jù),結(jié)果使得第1幀多畫了一次(對應(yīng)時間段上標(biāo)注了一個Jank),導(dǎo)致錯過了顯示第二幀。這種情況就會導(dǎo)致UI的卡頓感。
通過上述分析可知,此處發(fā)生Jank的關(guān)鍵問題在于,第1個16ms段內(nèi),CPU/GPU沒有及時處理第2幀數(shù)據(jù)。 為解決這個問題,Android 4.1中引入了VSYNC,核心目的是解決刷新不同步的問題。
2.2.2 加入 VSYNC同步信號
在加入VSYNC信號同步后,每收到VSYNC中斷,CPU就立刻開始處理各幀數(shù)據(jù)。這樣就解決了刷新不同步的問題。
但是上圖中仍然存在一個問題:如果在兩個同步信號之間的這段時間里也就是說16ms中CPU和GPU處理繪制不完這就又會遇到卡頓的現(xiàn)象,如下圖:
在第二個16ms時間段,Display本應(yīng)顯示B幀,但卻因為GPU還在處理B幀,導(dǎo)致A幀被重復(fù)顯示。同理,在第二個16ms時間段內(nèi),CPU無所事事,因為A Buffer被Display在使用。B Buffer被GPU在使用。同時,一旦過了VSYNC時間點(diǎn),CPU就不能再被觸發(fā)以處理繪制工作了。要等下一個VSYNC信號。由于只有兩個Buffer(Android 4.1之前)GPU和CPU不能同時進(jìn)行處理。為了解決這一問題,于是在Android4.1以后,引出了第三個緩沖區(qū):Tripple Buffer。
2.2.3 加入Tripple Buffer
第二個16ms時間段,CPU使用C Buffer繪圖。雖然還是會多顯示A幀一次,但后續(xù)顯示就會流暢許多。雖然android4.1以后優(yōu)化了繪制機(jī)制,但最好還是在16ms內(nèi)能將展示界面繪制出來。
3、相關(guān)源碼分析
Android中提供了View的invalidate()和postInvalidate()方法來讓我們可以手動刷新界面。從這兩個方法一步步向下查找最后都會調(diào)用ViewRootImpl的scheduleTraversals()。ViewRootImpl是一個非常重要的類,重點(diǎn)介紹一下。
3.1 ViewRootImpl
Android 設(shè)備呈現(xiàn)到界面上的大多數(shù)情況下都是一個 Activity,真正承載視圖的是一個 Window,每個 Window 都有一個 DecorView,當(dāng)調(diào)用 setContentView() 時其實(shí)是將自己寫的布局文件添加到以 DecorView 為根布局的一個 ViewGroup 里,構(gòu)成一顆 View 樹。每個 Activity 對應(yīng)一棵以 DecorView 為根布局的 View 樹,DecorView 類里面有個 mParent屬性,它的類型就是 ViewRootImpl,而且每個界面上的 View 的刷新,繪制,點(diǎn)擊事件的分發(fā)其實(shí)都是由 ViewRootImpl 作為發(fā)起者的,由 ViewRootImpl 控制這些操作從 DecorView 開始遍歷 View 樹去分發(fā)處理。
ViewRootImpl和DecorView 的綁定時機(jī)是在執(zhí)行完Android 的onResume()后,ActivityThread 會調(diào)用WindowManager的addView()方法。進(jìn)而調(diào)用WindowManagerGlobal 的 addView()方法。
// WindowManagerGlobal .class
// 這時候傳入的參數(shù)view 是DecorView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
synchronized (mLock) {
// 初始化ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
...
// 存儲DecorView和ViewRootImpl
mViews.add(view);
mRoots.add(root);
...
// 綁定 DecorView和ViewRootImpl
root.setView(view, wparams, panelParentView);
...
}
}
WindowManager的addView()等方法其實(shí)最后都是調(diào)用的WindowManagerGlobal 的相關(guān)方法,而WindowManagerGlobal 維護(hù)著所有 Activity 的 DecorView 和 ViewRootImpl都存儲在mViews和mRoots中。接下來看一下 root.setView(view, wparams, panelParentView)
// ViewRootImpl.class
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
}
...
// 發(fā)起布局請求
requestLayout();
...
// 將當(dāng)前ViewRootImpl對象傳入DecorView中
view.assignParent(this);
...
}
}
}
進(jìn)入到view.assignParent(this):
// View.class
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
這里將ViewRootImpl賦值給DecorView的mParent 參數(shù),從而將兩者綁定起來。而子View 里面調(diào)用的invalidate()等方法最終通過while循環(huán)尋找parent 最后都會找到這個ViewRootImpl,再由它來進(jìn)行操作。這個綁定操作是在執(zhí)行完onResume()方法之后才被發(fā)起執(zhí)行的,這也解釋了在onCreate、onStart、onResume中不能獲取View寬高的原因。ViewRootImpl 還未創(chuàng)建,就不存在渲染操作,也就不存在View的測量步驟了。換句話說一個 Activity 界面的繪制,其實(shí)是在 onResume() 之后才開始的。
回到ViewRootImpl的setView()方法,里面有這么一行代碼:requestLayout(),進(jìn)入查看其方法體:
// ViewRootImpl.class
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
同invalidate()和postInvalidate()這里也調(diào)用了scheduleTraversals()方法。這個方法是渲染屏幕的關(guān)鍵方法,由它來發(fā)起一次繪制View樹的任務(wù)請求。
3.1.1 scheduleTraversals()
// ViewRootImpl.class
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 發(fā)送同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
mTraversalScheduled這個變量是為了過濾一幀內(nèi)重復(fù)的刷新請求,初始值是false,在開始這一幀的繪制流程時候也會重新置為false(doTraversal()中,一會兒分析),同時,在取消遍歷繪制 View 操作 unscheduleTraversals() 里也會設(shè)置為false。也就是說一般情況下在開始這一幀的正式繪制前,在這期間重復(fù)調(diào)用scheduleTraversals()只有一次會生效。這么設(shè)計的原因是前面已經(jīng)說了和ViewRootImpl綁定的是DecorView,當(dāng)刷新時候會對整個DecorView進(jìn)行一次處理,所以不同view觸發(fā)的scheduleTraversals()作用都是一樣的,所以在這一幀里面只要有一次和多次刷新請求效果是一樣的。
現(xiàn)在我們接著往下看
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
這行代碼的意思是向主線程發(fā)送了一個同步屏障。同步屏障的作用可以理解成攔截同步消息的執(zhí)行,主線程的 Looper 會一直循環(huán)調(diào)用 MessageQueue 的 next() 來取出隊頭的 Message 執(zhí)行,當(dāng) Message 執(zhí)行完后再去取下一個。當(dāng) next() 方法在取 Message 時發(fā)現(xiàn)隊頭是一個同步屏障的消息時,就會去遍歷整個隊列,只尋找設(shè)置了異步標(biāo)志的消息,如果有找到異步消息,那么就取出這個異步消息來執(zhí)行,否則就讓 next() 方法陷入阻塞狀態(tài)。如果 next() 方法陷入阻塞狀態(tài),那么主線程此時就是處于阻塞狀態(tài)的,也就是沒在干任何事。所以,如果隊頭是一個同步屏障的消息的話,那么在它后面的所有同步消息就都被攔截住了,直到這個同步屏障消息被移除出隊列,否則主線程就一直不會去處理同步屏幕后面的同步消息。而所有消息默認(rèn)都是同步消息,只有手動設(shè)置了異步標(biāo)志,這個消息才會是異步消息。另外,同步屏障消息只能由內(nèi)部來發(fā)送,這個接口并沒有公開給我們使用。
到這里也可以猜想出之后我們肯定會通過發(fā)送一個異步消息來進(jìn)行UI渲染。而這么做的目的也可以理解,就是為了渲染操作能第一時間得到執(zhí)行,而減少丟幀現(xiàn)象。同時也可以推理出在發(fā)送出這個同步屏障之前的其他同步消息還是能進(jìn)行處理的。這里就可能出現(xiàn)同步屏障之前的消息處理過于耗時而導(dǎo)致的丟幀現(xiàn)象。
繼續(xù)往下看下一行代碼:
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
這里調(diào)用了postCallback()函數(shù),其中第一個函數(shù)是個標(biāo)志函數(shù),代表這個回調(diào)是處理布局繪制的。我們來看下第二個參數(shù),它是個 Runnable 對象:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
這個Runnable 做的事情就是調(diào)用了doTraversal()方法:
// ViewRootImpl.class
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
performTraversals();
...
}
}
在doTraversal()方法中的前幾行代碼正好和scheduleTraversals()中的前幾行相反,它首先將mTraversalScheduled設(shè)置為false,同時移除同步屏障??梢圆聹y從這邊開始要進(jìn)行繪制操作了,我們也確實(shí)在這個方法里面看到了執(zhí)行 performTraversals()這個函數(shù):
// ViewRootImpl.class
private void performTraversals() {
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp,mWidth,mHeight);
...
performDraw();
...
}
performTraversals()的方法體很長,但其中最關(guān)鍵的三行代碼如上所示,分別代表對DecorView 的測量、布局、繪制三大流程,而DecorView 依次遞歸遍歷其子view也進(jìn)行performTraversals()的調(diào)用。里面包含著一些邏輯判斷,來確定這三大流程是否都要執(zhí)行。有時可能只需要執(zhí)行 performDraw() 繪制流程,有時可能只執(zhí)行 performMeasure() 測量和 performLayout() 布局流程(一般測量和布局流程是一起執(zhí)行的)。
我們回到先前的
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
第三個參數(shù)是傳一個token,這邊傳null就代表沒有傳token。
3.2 Choreographer
接下來分析下這個postCallback到底做了什么,以及這個函數(shù)的調(diào)用者mChoreographer。
// Choreographer.class
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
// delayMillis傳的參數(shù)是0
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
```
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
···
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// action是前面?zhèn)魅氲腡raversalRunnable 回調(diào)函數(shù)
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
// delayMillis傳入的是0,進(jìn)入這里
scheduleFrameLocked(now);
} else {
···
}
}
}
postCallback()一路下來走到了postCallbackDelayedInternal()中,并且由于傳了 delay = 0 進(jìn)去,所以在 postCallbackDelayedInternal() 里面會先根據(jù)當(dāng)前時間戳將這個 Runnable 保存到一個 mCallbackQueue 隊列里,這個隊列跟 MessageQueue 很相似,里面待執(zhí)行的任務(wù)都是根據(jù)一個時間戳來排序。然后走到了 scheduleFrameLocked() 方法這邊:
// Choreographer.class
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) { // 默認(rèn)為true
···
//如果在Looper主線程上運(yùn)行,則立即安排vsync,
//否則的話發(fā)送一個Message ,盡快從UI線程安排vsync。
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} ...
}
}
這里面進(jìn)去后如果在Looper線程(即mHandler綁定的這個線程)上運(yùn)行,則立即安排vsync,否則在消息隊列頭插入這一消息,以期望mHandler來得到盡快處理。
看一下scheduleVsyncLocked()和這個mHandler怎么處理這個消息:
private final class FrameHandler extends Handler {
...
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
...
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
...
}
}
}
void doScheduleVsync() {
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
可見mHandler處理的消息最后也會調(diào)用到scheduleVsyncLocked(),然后調(diào)用
mDisplayEventReceiver.scheduleVsync();
// FrameDisplayEventReceiver.class
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
最后會調(diào)用到native方法nativeScheduleVsync(mReceiverPtr)中去。這個方法其實(shí)相當(dāng)于向系統(tǒng)訂閱一個接受一個Vsync信號。android系統(tǒng)每過16.6ms會發(fā)送一個Vsync信號。但這個信號并不是所有app都能收到的,只有訂閱了的才能收到。這樣設(shè)計的合理之處在于,當(dāng)UI沒有變化的時候就不會去調(diào)用nativeScheduleVsync(mReceiverPtr)去訂閱,從而也就會收到Vsync信號,也就不會有不必要的繪制操作。同時每次訂閱只能收到一次Vsync信號,如果需要收到下次信號需要重新訂閱。
3.3 FrameDisplayEventReceiver
既然是mDisplayEventReceiver用scheduleVsync()來訂閱Vsync信號的,也是由這個類來接收Vsync信號的。
我們來看下這個類:
// Choreographer.class
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
...
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
...
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
上面這個類中的onVsync()函數(shù)就是來回調(diào)Vsync信號,這個回調(diào)方法里面將這個類自己的run()方法傳給mHandler來進(jìn)行處理,同時將這個消息設(shè)為異步消息。這和我們之前設(shè)置異步屏障來優(yōu)先處理異步消息對上了。我們繼續(xù)看起run方法中執(zhí)行的函數(shù) doFrame(mTimestampNanos, mFrame);
// Choreographer.class
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
...
try {
...
// Choreographer.CALLBACK_TRAVERSAL這個參數(shù)和 mChoreographer.postCallback()里面?zhèn)魅氲囊恢?
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
...
}
...
}
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
...
// 取出之前放入mCallbackQueues的mTraversalRunnable
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
...
// 回調(diào)mTraversalRunnable的run函數(shù)
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
...
}
}
到這里基本上已經(jīng)走通整個流程了,doFrame()里面的doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);函數(shù)會回調(diào)之前 mChoreographer.postCallback()里面?zhèn)魅氲膍TraversalRunnable,進(jìn)而最終調(diào)用performTraversals()來計算下一陣的數(shù)據(jù),等到下一幀將計算的數(shù)據(jù)顯示到屏幕上。
4、總結(jié)
整個UI渲染流程可以歸納為如下過程:
① 界面上任何一個 View 的刷新請求最終都會走到 ViewRootImpl 中的 scheduleTraversals() 里來安排一次遍歷繪制 View 樹的任務(wù)。
② scheduleTraversals() 會先過濾掉同一幀內(nèi)的重復(fù)調(diào)用,確保同一幀內(nèi)只需要安排一次遍歷繪制 View 樹的任務(wù),遍歷過程中會將所有需要刷新的 View 進(jìn)行重繪。
③ scheduleTraversals() 會往主線程的消息隊列中發(fā)送一個同步屏障,攔截這個時刻之后所有的同步消息的執(zhí)行,但不會攔截異步消息,以此來盡可能的保證當(dāng)接收到屏幕刷新信號時可以盡可能第一時間處理遍歷繪制 View 樹的工作。
④ 發(fā)完同步屏障后 scheduleTraversals() 將 performTraversals() 封裝到 Runnable 里面,然后調(diào)用 Choreographer 的 postCallback() 方法。
⑤ postCallback() 方法會先將這個 Runnable 任務(wù)以當(dāng)前時間戳放進(jìn)一個待執(zhí)行的隊列里,然后如果當(dāng)前是在主線程就會直接調(diào)用一個native 層方法,如果不是在主線程,會發(fā)一個最高優(yōu)先級的 message 到主線程,讓主線程第一時間調(diào)用這個 native 層的方法。
⑥ native 層的這個方法是用來向底層訂閱下一個屏幕刷新信號Vsync,當(dāng)下一個屏幕刷新信號發(fā)出時,底層就會回調(diào) Choreographer 的onVsync() 方法來通知上層 app。
⑦ onVsync() 方法被回調(diào)時,會往主線程的消息隊列中發(fā)送一個執(zhí)行 doFrame() 方法的異步消息。
⑧ doFrame() 方法會去取出之前放進(jìn)待執(zhí)行隊列里的任務(wù)來執(zhí)行,取出來的這個任務(wù)實(shí)際上是 ViewRootImpl 的 doTraversal() 操作。
⑨ doTraversal()中首先移除同步屏障,再會調(diào)用performTraversals() 方法根據(jù)當(dāng)前狀態(tài)判斷是否需要執(zhí)行performMeasure() 測量、perfromLayout() 布局、performDraw() 繪制流程,在這幾個流程中都會去遍歷 View 樹來刷新需要更新的View。
⑩ 等到下一個Vsync信號到達(dá),將上面計算好的數(shù)據(jù)渲染到屏幕上,同時如果有必要開始下一幀的數(shù)據(jù)處理。