基本概念
- CPU:執(zhí)行應(yīng)用層的measure护侮、layout、draw等操作,繪制完成后將數(shù)據(jù)提交給GPU
- GPU:進(jìn)一步處理數(shù)據(jù),并將數(shù)據(jù)緩存起來
- 屏幕:由一個(gè)個(gè)像素點(diǎn)組成酸役,以固定的頻率(16.6ms贱呐,即1秒60幀)從緩沖區(qū)中取出數(shù)據(jù)來填充像素點(diǎn)
總結(jié)一句話就是:CPU 繪制后提交數(shù)據(jù)吼句、GPU 進(jìn)一步處理和緩存數(shù)據(jù)惕艳、最后屏幕從緩沖區(qū)中讀取數(shù)據(jù)并顯示:
雙緩沖機(jī)制
在屏幕刷新中远搪,Android系統(tǒng)引入了雙緩沖機(jī)制谁鳍。
- GPU只向Back Buffer中寫入繪制數(shù)據(jù)倘潜,且GPU會定期交換Back Buffer和Frame Buffer涮因,也就是讓Back Buffer 變成Frame Buffer交給屏幕進(jìn)行繪制养泡,讓原先的Frame Buffer變成Back Buffer進(jìn)行數(shù)據(jù)寫入澜掩。
- 交換的頻率也是60次/秒肩榕,這就與屏幕的刷新頻率保持了同步株汉。
雖然我們引入了雙緩沖機(jī)制哥童,但是我們知道贮懈,當(dāng)布局比較復(fù)雜,或設(shè)備性能較差的時(shí)候抡医,CPU并不能保證在16.6ms內(nèi)就完成繪制數(shù)據(jù)的計(jì)算忌傻,所以這里系統(tǒng)又做了一個(gè)處理水孩。
- 當(dāng)你的應(yīng)用正在往Back Buffer中填充數(shù)據(jù)時(shí)俘种,系統(tǒng)會將Back Buffer鎖定宙刘。如果到了GPU交換兩個(gè)Buffer的時(shí)間點(diǎn)怖现,你的應(yīng)用還在往Back Buffer中填充數(shù)據(jù),GPU會發(fā)現(xiàn)Back Buffer被鎖定了吊输,它會放棄這次交換季蚂。
這樣做的后果就是手機(jī)屏幕仍然顯示原先的圖像扭屁,這就是我們常常說的丟幀料滥,所以為了避免丟幀的發(fā)生,我們就要盡量減少布局層級践宴,減少不必要的View的invalidate調(diào)用阻肩,減少大量對象的創(chuàng)建(GC也會占用CPU時(shí)間)等等烤惊。
Choreographer
Android在每一幀中實(shí)際上只是在完成三個(gè)操作,分別是輸入(Input)伦泥、動畫(Animation)不脯、繪制(Draw)防楷。
在Android4.1(API 16)之后冲簿,Android系統(tǒng)開始加入Choreographer這個(gè)類峦剔,這個(gè)類名翻譯過來是“舞蹈指導(dǎo)”吝沫,字面上的意思就是指揮以上三個(gè)UI操作一起完成一支舞蹈。
這個(gè)類就可以解決vsync和繪制不同步的問題,其實(shí)它的原理用一句話總結(jié)就是往Choreographer里發(fā)一個(gè)消息一屋,最快也要等到下一個(gè)vsync信號來的時(shí)候才會開始處理消息。
Activity中的布局首次繪制诽嘉,以及每次調(diào)用View 的 invalidate() 時(shí),都會調(diào)用到ViewRootImp#requestLayout()悦冀,所以我們接下來分析一下ViewRootImp#requestLayout()里面做了什么
ViewRootImp#requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//檢查是否是主線程踏烙,不然會拋出異常
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
// ViewRootImpl 構(gòu)造函數(shù)的時(shí)候初始化 mThread
// 也就是 mThread 是ViewRootImpl創(chuàng)建的那個(gè)線程
// 通常ViewRootImpl是在主線程創(chuàng)建的
// 所以更新UI要在UI線程(主線程)操作
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
...
}
ViewRootImp#scheduleTraversals()
如果在同一幀中出現(xiàn)多次requestLayout()調(diào)用寒屯,其實(shí)最終也只會繪制一次处面,為什么呢?我們可以看到下面有個(gè)mTraversalScheduled標(biāo)志位砸紊,稍后我們可以看看這個(gè)標(biāo)志位是哪里被置為false的
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//添加同步消息屏障沼溜,這個(gè)方法也比較關(guān)鍵,這里先不關(guān)心,我們說完Choreographer再分析
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//向Choreographer中發(fā)送消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//...
}
}
Choreographer#postCallbackDelayedInternal()
mChoreographer.postCallback()接著會調(diào)用這個(gè)方法
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//將消息以當(dāng)前的時(shí)間戳放進(jìn)mCallbackQueue 隊(duì)列里
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
//如果沒有設(shè)置消息延時(shí)亡驰,直接執(zhí)行
scheduleFrameLocked(now);
} else {
//消息延時(shí)戒职,但是最終依然會調(diào)用scheduleFrameLocked
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
Choreographer#scheduleFrameLocked()
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
//如果當(dāng)前線程是Choreographer的工作線程,我理解就是主線程
scheduleVsyncLocked();
} else {
//否則發(fā)一條消息到主線程
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
//設(shè)置消息為異步消息,其實(shí)就是一個(gè)標(biāo)志位,具體作用我們后面會講
msg.setAsynchronous(true);
//插到消息隊(duì)列頭部,可以理解為設(shè)置最高優(yōu)先級
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
接下來最終會調(diào)用到一個(gè)native方法
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
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);
}
}
@FastNative
private static native void nativeScheduleVsync(long receiverPtr);
native方法我們在Android Studio中不能直接查看糊识,這里我們換一種思路。
前面Choreographer#postCallbackDelayedInternal()方法中,我們看到了將消息以當(dāng)前的時(shí)間戳放進(jìn)隊(duì)列里,那消息什么時(shí)候被取出來執(zhí)行呢?
CallbackQueue
private final class CallbackQueue {
private CallbackRecord mHead;
public boolean hasDueCallbacksLocked(long now) {
return mHead != null && mHead.dueTime <= now;
}
//這就是取出消息的方法
public CallbackRecord extractDueCallbacksLocked(long now) {
CallbackRecord callbacks = mHead;
if (callbacks == null || callbacks.dueTime > now) {
return null;
}
CallbackRecord last = callbacks;
CallbackRecord next = last.next;
while (next != null) {
if (next.dueTime > now) {
last.next = null;
break;
}
last = next;
next = next.next;
}
mHead = next;
return callbacks;
}
//添加消息
public void addCallbackLocked(long dueTime, Object action, Object token) {...}
//刪除消息
public void removeCallbacksLocked(Object action, Object token) {...}
}
跟蹤代碼發(fā)現(xiàn),這個(gè)CallbackQueue#extractDueCallbacksLocked()會被Choreographer#doCallbacks()調(diào)用搞坝,Choreographer#doCallbacks()又會被Choreographer#doFrame()調(diào)用峰弹,最終我們跟到了FrameDisplayEventReceiver類。
FrameDisplayEventReceiver
因?yàn)樯厦娴膎ative方法我們沒有跟進(jìn)去分析舀射,擔(dān)心給大家繞暈了,我們會用一個(gè)新的章節(jié)來分析native層做的事情,這里先直接給出結(jié)論:
nativeScheduleVsync()會向SurfaceFlinger注冊Vsync信號的監(jiān)聽署惯,VSync信號由SurfaceFlinger實(shí)現(xiàn)并定時(shí)發(fā)送轻猖,當(dāng)Vsync信號來的時(shí)候就會回調(diào)FrameDisplayEventReceiver#onVsync()域那,這個(gè)方法給發(fā)送一個(gè)帶時(shí)間戳Runnable消息咙边,這個(gè)Runnable消息的run()實(shí)現(xiàn)就是FrameDisplayEventReceiver# run(), 接著就會執(zhí)行doFrame()次员。
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
scheduleVsync();
return;
}
long now = System.nanoTime();
if (timestampNanos > now) {
timestampNanos = now;
}
if (mHavePendingVsync) {
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
//設(shè)置異步消息
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
doFrame()會計(jì)算當(dāng)前時(shí)間與時(shí)間戳的間隔败许,間隔越大表示這一幀處理的時(shí)間越久,如果間隔超過一個(gè)周期淑蔚,就會去計(jì)算跳過了多少幀市殷,并打印出一個(gè)日志,這個(gè)日志我想很多人可能都見過:
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
最終doFrame()會從mCallbackQueue 中取出消息并按照時(shí)間戳順序調(diào)用mTraversalRunnable的run()函數(shù)刹衫,mTraversalRunnable就是最初被加入到Choreographer中的Runnable()
//ViewRootImp
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
TraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
ViewRootImp#doTraversal()
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步消息屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
同步消息屏障
還記不記得前面說有mHandler.getLooper().getQueue().postSyncBarrier()這個(gè)方法還沒有進(jìn)行分析醋寝,這個(gè)方法的作用是什么呢搞挣?
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//☆
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//向Choreographer中發(fā)送消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//...
}
}
我們知道,Android是基于消息機(jī)制的音羞,每一個(gè)操作都是一個(gè)Message囱桨,如果在觸發(fā)繪制的時(shí)候,消息隊(duì)列中還有很多消息沒有被執(zhí)行嗅绰,那是不是意味著要等到消息隊(duì)列中的消息執(zhí)行完成后舍肠,繪制消息才能被執(zhí)行到,那么依然無法保證Vsync信號和繪制的同步办陷,所以依然可能出現(xiàn)丟幀的現(xiàn)象.
還記不記得我們之前在Choreographer#scheduleFrameLocked()和FrameDisplayEventReceiver#onVsync()中提到貌夕,我們會給與Message有關(guān)的繪制請求設(shè)置成異步消息(msg.setAsynchronous(true)),為什么要這么做呢民镜?
這時(shí)候MessageQueue#postSyncBarrier()就發(fā)揮它的作用了啡专,簡單來說,它的作用就是一個(gè)同步消息屏障制圈,能夠把我們的異步消息(也就是繪制消息)的優(yōu)先級提到最高们童。
MessageQueue#postSyncBarrier()
主線程的 Looper 會一直循環(huán)調(diào)用 MessageQueue 的 next() 來取出隊(duì)頭的 Message 執(zhí)行,當(dāng) Message 執(zhí)行完后再去取下一個(gè)鲸鹦。
當(dāng) next() 方法在取 Message 時(shí)發(fā)現(xiàn)隊(duì)頭是一個(gè)同步屏障的消息時(shí)慧库,就會去遍歷整個(gè)隊(duì)列,只尋找設(shè)置了異步標(biāo)志的消息馋嗜,如果有找到異步消息齐板,那么就取出這個(gè)異步消息來執(zhí)行,否則就讓 next() 方法陷入阻塞狀態(tài)葛菇。
如果 next() 方法陷入阻塞狀態(tài)甘磨,那么主線程此時(shí)就是處于空閑狀態(tài)的,也就是沒在干任何事眯停。
所以济舆,如果隊(duì)頭是一個(gè)同步屏障的消息的話,那么在它后面的所有同步消息就都被攔截住了莺债,直到這個(gè)同步屏障消息被移除滋觉,否則主線程就一直不會去處理同步屏障后面的同步消息
那這么同步屏障是什么時(shí)候被移除的呢?
其實(shí)我們就是在我們上面提到的ViewRootImp#doTraversal()方法中齐邦。
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
總結(jié)
- 界面上任何一個(gè) View 的刷新請求最終都會走到 ViewRootImpl 中的 scheduleTraversals() 里來安排一次遍歷繪制 View 樹的任務(wù)椎侠;
- scheduleTraversals() 會先過濾掉同一幀內(nèi)的重復(fù)調(diào)用,在同一幀內(nèi)只需要安排一次遍歷繪制 View 樹的任務(wù)即可措拇,這個(gè)任務(wù)會在下一個(gè)屏幕刷新信號到來時(shí)調(diào)用 performTraversals() 遍歷 View 樹我纪,遍歷過程中會將所有需要刷新的 View 進(jìn)行重繪;
- 接著 scheduleTraversals() 會往主線程的消息隊(duì)列中發(fā)送一個(gè)同步屏障,攔截這個(gè)時(shí)刻之后所有的同步消息的執(zhí)行宣羊,但不會攔截異步消息璧诵,以此來盡可能的保證當(dāng)接收到屏幕刷新信號時(shí)可以盡可能第一時(shí)間處理遍歷繪制 View 樹的工作;
- 發(fā)完同步屏障后 scheduleTraversals() 才會開始安排一個(gè)遍歷繪制 View 樹的操作仇冯,作法是把 performTraversals() 封裝到 Runnable 里面之宿,然后調(diào)用 Choreographer 的 postCallback() 方法;
- postCallback() 方法會先將這個(gè) Runnable 任務(wù)以當(dāng)前時(shí)間戳放進(jìn)一個(gè)待執(zhí)行的隊(duì)列里苛坚,然后如果當(dāng)前是在主線程就會直接調(diào)用一個(gè)native 層方法比被,如果不是在主線程,會發(fā)一個(gè)最高優(yōu)先級的 message 到主線程泼舱,讓主線程第一時(shí)間調(diào)用這個(gè) native 層的方法等缀;
- native 層的這個(gè)方法是用來向底層注冊監(jiān)聽下一個(gè)屏幕刷新信號,當(dāng)下一個(gè)屏幕刷新信號發(fā)出時(shí)娇昙,底層就會回調(diào) Choreographer 的onVsync() 方法來通知上層 app尺迂;
- onVsync() 方法被回調(diào)時(shí),會往主線程的消息隊(duì)列中發(fā)送一個(gè)執(zhí)行 doFrame() 方法的消息冒掌,這個(gè)消息是異步消息噪裕,所以不會被同步屏障攔截住股毫;
- doFrame() 方法會去取出之前放進(jìn)待執(zhí)行隊(duì)列里的任務(wù)來執(zhí)行膳音,取出來的這個(gè)任務(wù)實(shí)際上是 ViewRootImpl 的 doTraversal() 操作;
- 上述第4步到第8步涉及到的消息都手動設(shè)置成了異步消息铃诬,所以不會受到同步屏障的攔截祭陷;
- doTraversal() 方法會先移除主線程的同步屏障,然后調(diào)用 performTraversals() 開始根據(jù)當(dāng)前狀態(tài)判斷是否需要執(zhí)行performMeasure() 測量趣席、perfromLayout() 布局兵志、performDraw() 繪制流程,在這幾個(gè)流程中都會去遍歷 View 樹來刷新需要更新的View吩坝;