UI 優(yōu)化系列專題,來聊一聊 Android 渲染相關(guān)知識(shí)料按,主要涉及 UI 渲染背景知識(shí)奄侠、如何優(yōu)化 UI 渲染兩部分內(nèi)容。
UI 優(yōu)化系列專題
- UI 渲染背景知識(shí)
《View 繪制流程之 setContentView() 到底做了什么载矿?》
《View 繪制流程之 DecorView 添加至窗口的過程》
《深入 Activity 三部曲(3)View 繪制流程》
《Android 之 LayoutInflater 全面解析》
《關(guān)于渲染垄潮,你需要了解什么?》
《Android 之 Choreographer 詳細(xì)分析》
- 如何優(yōu)化 UI 渲染
《Android 之如何優(yōu)化 UI 渲染(上)》
《Android 之如何優(yōu)化 UI 渲染(下)》
現(xiàn)在我們已經(jīng)很少能夠聽到關(guān)于 Android UI 卡頓的話題了闷盔,這得益于 Google 長(zhǎng)期以來對(duì) Android 渲染性能的重視弯洗,基本每次 Google I/O 都會(huì)花很多篇幅講這一塊。隨著時(shí)間的推移逢勾,Android 系統(tǒng)一直在不斷進(jìn)化牡整、壯大,并且日趨完善溺拱。
其中逃贝,Google 在 2012 年的 I/O 大會(huì)上宣布了 Project Butter 黃油計(jì)劃,那個(gè)曾經(jīng)嚴(yán)重影響 Android 口碑的 UI 流程性問題迫摔,首先在這得到有效的控制沐扳,并且在 Android 4.1 中正式開啟了這個(gè)機(jī)制。
Project Butter
Project Butter 對(duì) Android Display 系統(tǒng)進(jìn)行了重構(gòu)句占,引入了三個(gè)核心元素沪摄,即 VSYNC、Triple Buffer 和 Choreographer纱烘。
其中 VSYNC 是理解 Project Butter 的核心杨拐。接下來,我們就圍繞 VSYNC 開始介紹 Project Butter 對(duì) Android Display 系統(tǒng)做了哪些優(yōu)化凹炸。
VSYNC
VSYNC 最初是由 GPU 廠商開發(fā)的一種戏阅,用于防止屏幕撕裂的技術(shù)方案,全稱 Vertical Synchronization啤它,該方案很早就已經(jīng)被廣泛應(yīng)用于 PC 上。我們可以把它理解為一種時(shí)鐘中斷。
1. 前世
VSYNC 是一種圖形技術(shù)变骡,它可以同步 GPU 的幀速率和顯示器的刷新頻率离赫,所以在理解 VSYNC 產(chǎn)生的原因及其作用之前,我們有必要先來了解下這兩個(gè)概念塌碌。
- 刷新頻率(Refresh Rate)
表示屏幕在一秒內(nèi)刷新畫面的次數(shù)渊胸, 刷新頻率取決于硬件的固定參數(shù),單位 Hz(赫茲)台妆。例如常見的 60 Hz翎猛、144 Hz,即每秒鐘刷新 60 次或 144 次接剩。
逐行掃描
顯示器并不是一次性將畫面顯示到屏幕上切厘,而是從左到右邊,從上到下逐行掃描顯示懊缺,不過這一過程快到人眼無法察覺到變化疫稿。以 60 Hz 刷新率的屏幕為例,即 1000 / 60 ≈ 16ms鹃两。
- 幀速率 (Frame Rate)
表示 GPU 在一秒內(nèi)繪制操作的幀數(shù)遗座,單位 fps。例如在電影界采用 24 幀的速度足夠使畫面運(yùn)行的非常流暢俊扳。而 Android 系統(tǒng)則采用更加流程的 60 fps途蒋,即每秒鐘繪制 60 幀畫面。更多內(nèi)容參考《Why 60 fps》馋记。
屏幕撕裂
現(xiàn)在碎绎,刷新頻率和幀率需要一起合作,才能使圖形內(nèi)容呈現(xiàn)在屏幕上抗果,GPU 會(huì)獲取圖形數(shù)據(jù)進(jìn)行繪制筋帖, 然后硬件負(fù)責(zé)把圖像內(nèi)容呈現(xiàn)到屏幕上,這一過程在應(yīng)用程序的生命周期內(nèi)一遍又一遍的發(fā)生冤馏。
如上圖日麸,CPU / GPU 生成圖像的 Buffer 數(shù)據(jù),屏幕從 Buffer 中讀取數(shù)據(jù)刷新后顯示逮光。理想情況下幀率和刷新頻率保持一致代箭,即每繪制完成一幀,顯示器顯示一幀涕刚。不幸的是嗡综,刷新頻率和幀率并不總是能夠保持相對(duì)同步,如果幀速率實(shí)際比刷新率快杜漠,例如幀速率是 120 fps极景,顯示器的刷新頻率為 60 Hz察净。此時(shí)將會(huì)發(fā)生一些視覺上的問題。
當(dāng) GPU 利用一塊內(nèi)存區(qū)域?qū)懭胍粠瑪?shù)據(jù)時(shí)盼樟,從頂部開始新一幀覆蓋前一幀氢卡,并立刻輸出一行內(nèi)容。當(dāng)屏幕刷新時(shí)晨缴,此時(shí)它并不知道圖像緩沖區(qū)的狀態(tài)译秦,因此從緩沖區(qū)抓取的幀并不是完整的一幀畫面(繪制和屏幕讀取使用同一個(gè)緩沖區(qū))。此時(shí)屏幕顯示的圖像會(huì)出現(xiàn)上半部分和下半部分明顯偏差的現(xiàn)象击碗,這種情況被稱之為 “tearing”(屏幕撕裂)筑悴。
- 發(fā)生 “tearing” 現(xiàn)象的根源是由于幀速率與刷新頻率不一致導(dǎo)致。
Double Buffer(雙重緩存)
那如何防止 “tearing” 現(xiàn)象的發(fā)生呢稍途?由于圖像繪制和讀取使用的是同一個(gè)緩沖區(qū)阁吝,所以屏幕刷新時(shí)可能讀取到的是不完整的一幀畫面。解決方案是采用 Double Buffer晰房。
Double Buffer(雙緩沖)背后的思想是讓繪制和顯示器擁有各自的圖像緩沖區(qū)求摇。GPU 始終將完成的一幀圖像數(shù)據(jù)寫入到 Back Buffer,而顯示器使用 Frame Buffer殊者,當(dāng)屏幕刷新時(shí)与境,F(xiàn)rame Buffer 并不會(huì)發(fā)生變化,Back Buffer 根據(jù)屏幕的刷新將圖形數(shù)據(jù) copy 到 Frame Buffer猖吴,這便是 VSYNC 的用武之地摔刁。
- 注意,VSYNC 信號(hào)負(fù)責(zé)調(diào)度從 Back Buffer 到 Frame Buffer 的交換操作海蔽,這里并不是真正的數(shù)據(jù) copy共屈,實(shí)際是交換各自的內(nèi)存地址,可以認(rèn)為該操作是瞬間完成党窜。
在 Android 4.1 之前拗引,Android 便使用的雙緩沖機(jī)制。怎么理解呢幌衣?一般來說矾削,在同一個(gè) View Hierarchy 內(nèi)的不同 View 共用一個(gè) Window,也就是共用同一個(gè) Surface豁护。
每個(gè) Surface 都會(huì)有一個(gè) BufferQueue 緩存隊(duì)列哼凯,但是這個(gè)隊(duì)列會(huì)由 SurfaceFlinger 管理,通過匿名共享內(nèi)存機(jī)制與 App 應(yīng)用層交互楚里。
整個(gè)流程如下:
每個(gè) Surface 對(duì)應(yīng)的 BufferQueue 內(nèi)部都有兩個(gè) Graphic Buffer断部,一個(gè)用于繪制一個(gè)用于顯示。系統(tǒng)會(huì)把內(nèi)容先繪制到離屏緩沖區(qū)(OffScreen Buffer)班缎,在需要顯示時(shí)蝴光,才把離屏緩沖區(qū)的內(nèi)容通過 Swap Buffer 復(fù)制到 Front Graphic Buffer 中她渴。
這樣 SurfaceFlinge 就拿到了某個(gè) Surface 最終要顯示的內(nèi)容,但是同一時(shí)間我們可能會(huì)有多個(gè) Surface虱疏。這里面可能是不同應(yīng)用的 Surface惹骂,也可能是同一個(gè)應(yīng)用里面類似 SurfaceView 和 TextureView苏携,它們都會(huì)有自己獨(dú)立的 Surface做瞪。
這個(gè)時(shí)候 SurfaceFlinger 把所有 Surface 要顯示的內(nèi)容統(tǒng)一交給 Hardware Composer,它會(huì)根據(jù)位置右冻、Z-Order 順序等信息合成為最終屏幕需要顯示的內(nèi)容装蓬,而這個(gè)內(nèi)容會(huì)交給系統(tǒng)的幀緩沖區(qū) Frame Buffer 來顯示(Frame Buffer 是非常底層的,可以理解為屏幕顯示的抽象)纱扭。
Android 一直使用 VSYNC 來防止屏幕畫面發(fā)生撕裂現(xiàn)象
2. 今生
但是 UI 繪制任務(wù)可能會(huì)因?yàn)?CPU 在忙別的事情牍帚,導(dǎo)致沒來得及處理。所以從 Android 4.1 開始乳蛾, VSYNC 則更進(jìn)一步暗赶,現(xiàn)在 VSYNC 脈沖信號(hào)開始用于下一幀的所有處理。
Project Butter 首先對(duì) Android Display 系統(tǒng)的 SurfaceFlinger 進(jìn)行了改造肃叶,目標(biāo)是提供 VSYNC 中斷蹂随。每收到 VSYNC 中斷后,CPU 會(huì)立即準(zhǔn)備 Buffer 數(shù)據(jù)因惭,由于大部分顯示設(shè)備刷新頻率都是 60 Hz(一秒刷新 60 次)岳锁,也就是說一幀數(shù)據(jù)的準(zhǔn)備工作都要在 16ms 內(nèi)完成。
這樣應(yīng)用總是在 VSYNC 邊界上開始繪制蹦魔,而 SurfaceFlinger 總是在 VSYNC 邊界上進(jìn)行合成激率。這樣可以消除卡頓,并提升圖形的視覺表現(xiàn)勿决。
Triple Buffer(三重緩存)
如果理解了雙緩沖機(jī)制的原理乒躺,那就非常容易理解什么是三緩沖區(qū)了。如果只有兩個(gè) Graphic Buffer 緩沖區(qū) A 和 B低缩,如果 CPU / GPU 繪制過程較長(zhǎng)嘉冒,超過一個(gè) VSYNC 信號(hào)周期。
由上圖可知:
在第二個(gè) 16 ms 時(shí)間段內(nèi)表制,Display 本應(yīng)該顯示 B 幀健爬,但卻因?yàn)?GPU 還在處理 B 幀,導(dǎo)致 A 幀被重復(fù)顯示么介。
同理娜遵,在第二個(gè) 16 ms 時(shí)間段內(nèi),CPU 無所事事壤短,因?yàn)?A Buffer 被 Display 在使用设拟。 B Buffer 被 GPU 在使用慨仿。注意,一旦過了 VSYNC 時(shí)間點(diǎn)纳胧,CPU 就不能被觸發(fā)處理繪制工作了镰吆。
為什么 CPU 不能在第二個(gè) 16ms 處理繪制工作呢?原因是只有兩個(gè) Buffer跑慕,緩沖區(qū) B 中的數(shù)據(jù)還沒有準(zhǔn)備完成万皿,所以只能繼續(xù)展示 A 緩沖區(qū)的內(nèi)容,這樣緩沖區(qū) A 和 B 都分別被顯示設(shè)備和 GPU 占用核行,CPU 則無法準(zhǔn)備下一幀的數(shù)據(jù)牢硅。如果再提供一個(gè)緩沖區(qū),CPU芝雪、GPU 和顯示設(shè)備都能使用各自的緩沖區(qū)工作减余,互不影響。
簡(jiǎn)單來說惩系,三重緩沖機(jī)制就是在雙緩沖機(jī)制基礎(chǔ)上增加了一個(gè) Graphic Buffer 緩沖區(qū)位岔,這樣可以最大限度的利用空閑時(shí)間,帶來的壞處是多使用的一個(gè) Graphic Buffer 所占用的內(nèi)存堡牡。
由上圖可知:
在第二個(gè) 16ms 時(shí)間段抒抬,CPU 使用 C Buffer 完成繪圖工作,雖然還是會(huì)多顯示一次 A 幀悴侵,但后續(xù)顯示就比較順暢了瞧剖,有效避免 Jank 的進(jìn)一步加劇。
注意:是不是 Buffer 越多越好呢可免?這個(gè)是否定的抓于,Buffer 正常還是兩個(gè),當(dāng)出現(xiàn) Jank 后三個(gè)足以浇借。
Choreographer
Choreographer 也是 Project Butter 計(jì)劃新增的機(jī)制捉撮,用于配合系統(tǒng)的 VSYNC 中斷信號(hào)。它本質(zhì)是一個(gè) Java 類妇垢,如果直譯的話為舞蹈指導(dǎo)巾遭,這是一個(gè)極富詩(shī)意的表達(dá),看到這個(gè)詞不得不贊嘆設(shè)計(jì)者除了 Coding 之外的廣泛視野闯估。舞蹈是有節(jié)奏的灼舍,節(jié)奏使舞蹈的每個(gè)動(dòng)作更加協(xié)調(diào)和連貫;視圖刷新也是如此涨薪。
Choreographer 可以接收系統(tǒng)的 VSYNC 信號(hào)骑素,統(tǒng)一管理應(yīng)用的輸入、動(dòng)畫和繪制等任務(wù)的執(zhí)行時(shí)機(jī)刚夺。Android 的 UI 繪制任務(wù)將在它的統(tǒng)一指揮下献丑,井然有序的完成末捣。業(yè)界一般通過它來監(jiān)控應(yīng)用的幀率。
Choreographer 的構(gòu)造方法:
private Choreographer(Looper looper, int vsyncSource) {
// 當(dāng)前線程的Looper
mLooper = looper;
// 創(chuàng)建該Looper的Handler
mHandler = new FrameHandler(looper);
// 是否開啟VSYNC创橄,開啟VSYNC后將通過FrameDisplayEventReceiver接受
// VSYNC脈沖
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
// 計(jì)算一幀的時(shí)間
// Android手機(jī)屏幕采用60Hz的刷新頻率
// 這里是納秒 ≈16000000ns 還是16ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 創(chuàng)建一個(gè)CallbackQueu的數(shù)組箩做,默認(rèn)為4
// CallbackQueue中存放要執(zhí)行的輸入、動(dòng)畫妥畏、遍歷繪制等任務(wù)
// 也就是 CALLBACK_INPUT邦邦、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
Choreographer 是線程單例的咖熟,而且必須要和一個(gè) Looper 綁定圃酵,因?yàn)槠鋬?nèi)部有一個(gè) Handler 需要和當(dāng)前繪制線程的 Looper 綁定柳畔。
DisplayEventReceiver 是一個(gè) abstract class馍管,在其構(gòu)造方法內(nèi)會(huì)通過 JNI 創(chuàng)建一個(gè) IDisplayEventConnection 的 VSYNC 的監(jiān)聽者。
另外 DisplayEventReceiver 中包含兩個(gè)非常重要的方法:一個(gè)用于需要繪制任務(wù)時(shí)薪韩,申請(qǐng) VSYNC 信號(hào)的 scheduleVsync 方法确沸,另一個(gè)用于接收 VSYNC 信號(hào)的 onVsync 方法。FrameDisplayEventReceiver 是 DisplayEventReceiver 的唯一實(shí)現(xiàn)類俘陷,并重寫 onVsync 方法用于通知 Choreographer罗捎。
Choreographer 的主要功能是,當(dāng)收到 VSYNC 信號(hào)時(shí)拉盾,去調(diào)用通過 postCallback 設(shè)置的回調(diào)方法桨菜。目前一共定義了四種類型的回調(diào),它們分別是:
CALLBACK_INPUT:優(yōu)先級(jí)最高捉偏,和輸入事件處理有關(guān)倒得;
CALLBACK_ANIMATION:優(yōu)先級(jí)其次,和 Animation 的處理有關(guān)夭禽;
CALLBACK_TRAVERSAL:優(yōu)先級(jí)最低霞掺,和 UI 等空間繪制有關(guān);
CALLBACK_COMMIT:最后執(zhí)行讹躯,和提交任務(wù)有關(guān)(在 API Level 23 添加)菩彬。
優(yōu)先級(jí)的高低和處理順序有關(guān)。當(dāng)收到 VSYNC 信號(hào)時(shí)潮梯,Choreographer 將首先處理 INPUT 類型的回調(diào)骗灶,然后 ANIMATION 類型,最后才是 TRAVERSAL 類型秉馏。
另外耙旦,Android 在 4.1 還對(duì) Handler 機(jī)制進(jìn)行了略微改造,使之支持 Asynchronous Message(異步消息) 和 Synchronization Barrier(同步屏障)沃饶。一般情況下同步消息和異步消息的處理方式并沒有什么區(qū)別母廷,只有在設(shè)置了同步屏障時(shí)才會(huì)出現(xiàn)差異轻黑。同步屏障為 Handler 消息機(jī)制增加了一種簡(jiǎn)單的優(yōu)先級(jí)關(guān)系,異步消息的優(yōu)先級(jí)要高于同步消息琴昆。簡(jiǎn)單點(diǎn)說氓鄙,設(shè)置了同步屏障之后,Handler 只會(huì)處理異步消息业舍。
以 View 的繪制流程為例:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 同步屏障抖拦,阻塞所有的同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 將 UI 繪制任務(wù)發(fā)送到 Choreograhper
// 注意mTraversaRunnable是一個(gè)Runnable對(duì)象
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// ...
}
}
scheduleTraversals 首先禁止了后續(xù)消息的處理能力,一旦設(shè)置了消息隊(duì)列的 postSyncBarrier舷暮,所有非 Asynchronous 的消息將被停止派發(fā)态罪。
UI 繪制任務(wù)設(shè)置了 CALLBACK 類型為 TRAVERSAL 類型的任務(wù),即 mTraversalRunnable:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//開始執(zhí)行繪制遍歷
doTraversal();
}
}
Choreographer 的 postCallback 方法將會(huì)申請(qǐng)一次 VSYNC 中斷信號(hào)下面,通過 DisplayEventReceiver 的 scheduleVsync 方法复颈。當(dāng) VSYNC 信號(hào)到達(dá)時(shí),便會(huì)回調(diào) Choreographer 的 doFrame 方法沥割,內(nèi)部會(huì)觸發(fā)已經(jīng)添加的回調(diào)任務(wù):
// 回調(diào) INPUT 任務(wù)
doCallbacks(Choreographer.CALLBACK_INPUT, mframeTimeNanos);
// 回調(diào) ANIMATION
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos)耗啦;
// 回調(diào) View 繪制任務(wù) TRAVERSAL
doCallbacks(Choreographer,CALLBACK_TRAVERSAL, frameTimeNanos);
// API Level 23 新增,COMMIT
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
此時(shí) UI 繪制任務(wù) doTraversal 方法被回調(diào)机杜,即在 Android 4.1 之后帜讲, UI 繪制任務(wù)被放置到了 VSYNC 中斷處理中了。Choreographer 確實(shí)做到了統(tǒng)一協(xié)調(diào)管理 UI 的繪制工作椒拗。有關(guān) Choreographer 更詳細(xì)的分析似将,可以參考《Android 之 Choreographer 詳細(xì)分析》。
總結(jié)
在從根本解決 Android UI 不流暢的問題上蚀苛,Project Butter 黃油計(jì)劃率先邁出了最重要一步在验,Android 的渲染性能也確實(shí)有了很大改善。
不過優(yōu)化是無止境的枉阵,Google 在后續(xù)版本中又引入了一些比較大的改變译红,例如 Android 5.0 的 RenderThread,Android 將所有的繪制任務(wù)都放到了該線程兴溜,這樣即便主線程有耗時(shí)的操作也可以保證動(dòng)畫流暢性侦厚。
關(guān)于 UI 渲染所涉及的內(nèi)容非常多,而且 Android 渲染框架演進(jìn)的非匙净眨快刨沦,文章最后也會(huì)附上一些擴(kuò)展資料,便于更好的學(xué)習(xí)理解膘怕。
文中如有不妥或有更好的分析結(jié)果想诅,歡迎您的留言或指正。文章如果對(duì)你有幫助,請(qǐng)留個(gè)贊吧来破。
擴(kuò)展閱讀
- 關(guān)于 UI 渲染篮灼,你需要了解什么?
- Android 之 Choreographer 詳細(xì)分析
- Android 之如何優(yōu)化 UI 渲染(上)
- 深入 Activity 三部曲(3)之 View 繪制流程
- 三重緩沖:為什么我們喜歡它
其他系列專題