說說Android的UI刷新機(jī)制的實(shí)現(xiàn)
本文主要解決以下幾個(gè)問題:
- 我們都知道Android的刷新頻率是60幀/秒,這是不是意味著每隔16ms就會(huì)調(diào)用一次onDraw方法?
- 如果界面不需要重繪,那么16ms到后還會(huì)刷新屏幕嗎?
- 我們調(diào)用invalidate()之后會(huì)馬上進(jìn)行屏幕刷新嗎?
- 我們說丟幀是因?yàn)橹骶€程做了耗時(shí)操作,為什么主線程做了耗時(shí)操作就會(huì)引起丟幀?
- 如果在屏幕快要刷新的時(shí)候才去OnDraw()繪制,會(huì)丟幀嗎?
好了,帶著以上問題,我們進(jìn)入源碼來找尋答案。
一、屏幕繪制流程
屏幕繪制機(jī)制的基本原理可以概括如下:

整個(gè)屏幕繪制的基本流程是:
- 應(yīng)用向系統(tǒng)服務(wù)申請(qǐng)buffer
- 系統(tǒng)服務(wù)返回buffer
- 應(yīng)用繪制后提交buffer給系統(tǒng)服務(wù)
如果放到Android中來,那么就是:

在Android中,一塊Surface對(duì)應(yīng)一塊內(nèi)存,當(dāng)內(nèi)存申請(qǐng)成功后,App端才有繪圖的地方。由于Android的view繪制不是今天的重點(diǎn),所以這里點(diǎn)到為止~
二、屏幕刷新分析
屏幕刷新的時(shí)機(jī)是當(dāng)Vsync信號(hào)到來的時(shí)候,具體如圖:

在Android端,是誰在控制 Vsync 的產(chǎn)生?又是誰來通知我們應(yīng)用進(jìn)行刷新的呢? 在Android中, Vysnc 信號(hào)的產(chǎn)生是由底層 HWComposer 負(fù)責(zé)的,而通知應(yīng)用進(jìn)行刷新,是Java層的 Choreographer ,Android整個(gè)屏幕刷新的核心就在于這個(gè) Choreographer 。
下面我們結(jié)合代碼一起來看一下。
每次當(dāng)我們要進(jìn)行ui重繪的時(shí)候,都會(huì)調(diào)用 requestLayout() ,所以,我們從這個(gè)方法入手:
2.1 requestLayout()
----》類名:ViewRootImpl
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//重點(diǎn)
scheduleTraversals();
}
}
2.2 scheduleTraversals()
----》類名:ViewRootImpl
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
可以看到,在這里并沒有立即進(jìn)行重繪,而是做了兩件事情:
- 往消息隊(duì)列里面插入一條SyncBarrier(同步屏障)
- 通過Cherographer post了一個(gè)callback
接下來,我們簡單說一下這個(gè) SyncBarrier (同步屏障)。
異步屏障的作用在于:
- 阻止同步消息的執(zhí)行
- 優(yōu)先執(zhí)行異步消息
為什么要設(shè)計(jì)這個(gè) SyncBarrier 呢?主要原因在于,在Android中,有些消息是十分緊急的,需要馬上執(zhí)行,如果說消息隊(duì)列里面普通消息太多的話,那等到執(zhí)行它的時(shí)候可能早就過了時(shí)機(jī)了。
到這里,可能有人會(huì)跟我一樣,覺得為什么不干脆在Message里搞個(gè)優(yōu)先級(jí),按照優(yōu)先級(jí)來進(jìn)行排序呢?弄個(gè) PriorityQueue 不就完了嗎?
我自己的理解是,在Android中,消息隊(duì)列的設(shè)計(jì)是一個(gè) 單鏈表 ,整個(gè)鏈表的排序是根據(jù)時(shí)間進(jìn)行排序的,如果此時(shí)再加入一個(gè)優(yōu)先級(jí)的排序規(guī)則,一方面會(huì)復(fù)雜會(huì)排序規(guī)則,另一方面,也會(huì)使得消息不可控。因?yàn)閮?yōu)先級(jí)是可以用戶自己在外面填的,那樣不就亂套了嗎?如果用戶每次總填最高的優(yōu)先級(jí),這樣就會(huì)導(dǎo)致系統(tǒng)消息很久才會(huì)消費(fèi),整個(gè)系統(tǒng)運(yùn)作就會(huì)出問題,最后影響用戶體驗(yàn),所以,我自己覺得Android的同步屏障這個(gè)設(shè)計(jì)還是挺巧妙的~
好了,總結(jié)一下,執(zhí)行 scheduleTraversals() 后,會(huì)插入一個(gè)屏障,保證異步消息的優(yōu)先執(zhí)行。
插入一個(gè)小小的思考題: 如果說我們?cè)谝粋€(gè)方法里連續(xù)調(diào)用了 requestLayout() 多次,那么請(qǐng)問:系統(tǒng)會(huì)插入多條屏障或者 post 多個(gè) Callback 嗎? 答案是不會(huì),為什么呢?看到 mTraversalScheduled 這個(gè)變量了嗎?它就是答案~
2.3 Choreographer.postCallback()
先來簡單說一下 Choreographer , Choreographer 中文翻譯叫 編舞者 ,它的主要作用是進(jìn)行系統(tǒng)協(xié)調(diào)的。(大家可以上網(wǎng)google下實(shí)際工作中的編舞者,這個(gè)類名真的起的很貼切了~)
Choreographer 這個(gè)類是應(yīng)用怎么初始化的呢?是通過 getInstance() 方法:
public static Choreographer getInstance() {
return sThreadInstance.get();
}
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
這里貼出來是為了提醒大家, Choreographer 不是單例,而是每個(gè)線程都有單獨(dú)的一份。
好了,回到我們的代碼:
----》類名:Choreographer
//1
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
//2
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
....
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
//3
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
...
}
}
Choreographer post的callback會(huì)放入 CallbackQueue 里面,這個(gè) CallbackQueue 是一個(gè)單鏈表。
首先會(huì)根據(jù)callbackType得到一條 CallbackQueue 單鏈表,之后會(huì)根據(jù)時(shí)間順序,將這個(gè)callback插入到單鏈表中;
2.4 scheduleFrameLocked()
----》類名:Choreographer
private void scheduleFrameLocked(long now) {
...
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
...
}
}
}
scheduleFrameLocked 的作用是:
- 如果當(dāng)前線程就是
Cherographer的工作線程的話,那么就直接執(zhí)行scheduleVysnLocked - 否則,就發(fā)送一個(gè)異步消息到消息隊(duì)列里面去 ,這個(gè)異步消息是不受同步屏障影響的,而且這個(gè)消息還要插入到消息隊(duì)列的頭部,可見這個(gè)消息是非常緊急的
跟蹤源代碼,我們發(fā)現(xiàn),其實(shí) MSG_DO_SCHEDULE_VSYNC 這條消息,最終執(zhí)行的也是 scheduleFrameLocked 這個(gè)方法,所以我們直接跟蹤 scheduleVsyncLocked() 這個(gè)方法。
2.5 scheduleVsyncLocked()
----》類名:Choreographer
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
----》類名:DisplayEventReceiver
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 {
//mReceiverPtr是Native層一個(gè)類的指針地址
//這里這個(gè)類指的是底層NativeDisplayEventReceiver這個(gè)類
//nativeScheduleVsync底層會(huì)調(diào)用到requestNextVsync()去請(qǐng)求下一個(gè)Vsync,
//具體不跟蹤了,native層代碼更長,還涉及到各種描述符監(jiān)聽以及跨進(jìn)程數(shù)據(jù)傳輸
nativeScheduleVsync(mReceiverPtr);
}
}
這里我們可以看到一個(gè)新的類: DisplayEventReceiver ,這個(gè)類的作用是注冊(cè)Vsync信號(hào)的監(jiān)聽,當(dāng)下個(gè)Vsync信號(hào)到來的時(shí)候就會(huì)通知到這個(gè) DisplayEventReceiver 了。
在哪里通知呢?源碼里注釋寫的非常清楚了:
----》類名:DisplayEventReceiver
// Called from native code. <---注釋還是很良心的
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
當(dāng)下一個(gè)Vysnc信號(hào)到來的時(shí)候,會(huì)最終調(diào)用 onVsync 方法:
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
點(diǎn)進(jìn)去一看,是個(gè)空實(shí)現(xiàn),回到類定義,原來是個(gè)抽象類,它的實(shí)現(xiàn)類是: FrameDisplayEventReceiver ,定義在 Cherographer 里面:
----》類名:Choreographer
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
....
}
2.6 FrameDisplayEventReceiver.onVysnc()
----》類名:Choreographer
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
@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() {
....
doFrame(mTimestampNanos, mFrame);
}
}
onVsync 方法往 Cherographer 所在線程的消息隊(duì)列中發(fā)送的一個(gè)消息,這個(gè)消息是就是它自己(它實(shí)現(xiàn)了Runnable),所以最終會(huì)調(diào)用到 doFrame() 方法。
2.7 doFrame(mTimestampNanos, mFrame)
doFrame()的處理分為兩個(gè)階段:
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
//1、階段一
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
...
}
...
}
frameTimeNanos 是當(dāng)前的時(shí)間戳,將當(dāng)前的時(shí)間和開始時(shí)間相減,得到這一幀處理花費(fèi)了多長,如果大于 mFrameIntervalNano ,說明處理耗時(shí)了,之后就打印出我們?nèi)粘R姷降?The application may be doing too much work on its main thread 。
階段二:
void doFrame(long frameTimeNanos, int frame) {
...
try {
//階段2
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
...
}
doFrame() 的第二個(gè)階段做的是處理各種callback,從CallbackQueue里面取出到執(zhí)行時(shí)間的callback進(jìn)行處理,那這個(gè)callback是怎么樣呢?
這里要回憶一下之前的 postCallback() 操作:

這個(gè) Callback 其實(shí)就一個(gè) mTraversalRunnable ,它是一個(gè) Runnable ,最終會(huì)調(diào)用到 run() 方法,實(shí)現(xiàn)界面的真正刷新:
----》類名:ViewRootImpl
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals();
...
}
}
private void performTraversals() {
...
//開始真正的界面繪制
performDraw();
...
}
三、總結(jié)
經(jīng)過漫長的代碼跟蹤,整個(gè)界面刷新流程算是跟蹤完了,下面我們來總結(jié)一下:

四、問題解答
我們都知道Android的刷新頻率是60幀/秒,這是不是意味著每隔16ms就會(huì)調(diào)用一次onDraw方法?
這里60幀/秒是屏幕刷新頻率,但是是否會(huì)調(diào)用onDraw()方法要看應(yīng)用是否調(diào)用requestLayout()進(jìn)行注冊(cè)監(jiān)聽。
如果界面不需要重繪,那么還16ms到后還會(huì)刷新屏幕嗎?
如果不需要重繪,那么應(yīng)用就不會(huì)受到Vsync信號(hào),但是還是會(huì)進(jìn)行刷新,只不過繪制的數(shù)據(jù)不變而已;
我們調(diào)用invalidate()之后會(huì)馬上進(jìn)行屏幕刷新嗎?
不會(huì),到等到下一個(gè)Vsync信號(hào)到來
我們說丟幀是因?yàn)橹骶€程做了耗時(shí)操作,為什么主線程做了耗時(shí)操作就會(huì)引起丟幀
原因是,如果在主線程做了耗時(shí)操作,就會(huì)影響下一幀的繪制,導(dǎo)致界面無法在這個(gè)Vsync時(shí)間進(jìn)行刷新,導(dǎo)致丟幀了。
如果在屏幕快要刷新的時(shí)候才去OnDraw()繪制,會(huì)丟幀嗎?
這個(gè)沒有太大關(guān)系,因?yàn)閂sync信號(hào)是周期的,我們什么時(shí)候發(fā)起onDraw()不會(huì)影響界面刷新;
五、參考文檔
gityuan大神的 Cherographer原理
慕課視頻
到此這篇關(guān)于說說Android的UI刷新機(jī)制的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Android UI刷新機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中EditText實(shí)現(xiàn)不可編輯解決辦法
這篇文章主要介紹了Android中EditText實(shí)現(xiàn)不可編輯解決辦法,需要的朋友可以參考下2014-12-12
Android開發(fā):TextView加入滾動(dòng)條示例
利用scrollview來實(shí)現(xiàn)TextView中滾動(dòng)條效果會(huì)好很多,具體代碼如下,感興趣的朋友可以參考下哈2013-06-06
解決android 顯示內(nèi)容被底部導(dǎo)航欄遮擋的問題
今天小編就為大家分享一篇解決android 顯示內(nèi)容被底部導(dǎo)航欄遮擋的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07
Android中EditText屏蔽第三方輸入法表情的方法示例
最近在工作終于遇到一個(gè)問題,因?yàn)榈谌捷斎敕ū砬榈膯栴}導(dǎo)致Android中TextView的內(nèi)容顯示異常,只能想辦法解決了,下面這篇文章主要記錄了在處理Android中EditText屏蔽第三方輸入法表情的方法,需要的朋友可以參考借鑒,下面來一起看看吧。2017-01-01
OpenGL Shader實(shí)現(xiàn)簡單轉(zhuǎn)場效果詳解
轉(zhuǎn)場效果常出現(xiàn)再視頻剪輯當(dāng)中,用于銜接兩段視頻片段切換的過渡效果。本文將介紹如何利用OpenGL Shader實(shí)現(xiàn)簡單的轉(zhuǎn)場效果,需要的小伙伴可以參考一下2022-02-02
關(guān)于Android實(shí)現(xiàn)簡單的微信朋友圈分享功能
這篇文章主要介紹了關(guān)于Android實(shí)現(xiàn)簡單的微信朋友圈分享功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下2017-02-02
Android短信驗(yàn)證碼監(jiān)聽解決onChange多次調(diào)用的方法
本篇文章主要介紹了Android短信驗(yàn)證碼監(jiān)聽解決onChange多次調(diào)用的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
Android開發(fā)教程之獲取系統(tǒng)輸入法高度的正確姿勢
這篇文章主要給大家介紹了關(guān)于Android開發(fā)教程之獲取系統(tǒng)輸入法高度的正確姿勢,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Android具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
Android利用Flutter實(shí)現(xiàn)立體旋轉(zhuǎn)效果
本文主要介紹了Flutter繪圖如何使用ImageShader填充圖形,并且利用 Matrix4的三維變換加上動(dòng)畫實(shí)現(xiàn)了立體旋轉(zhuǎn)的動(dòng)畫效果,感興趣的可以嘗試一下2022-06-06

