Android性能優化-界面渲染原理淺析

app應用作為一個離用戶最近的應用,其流暢度是至關重要的。谷歌官方在每個版本的更新中都有關于流暢度的優化,其中android4.1是一個里程,在這個版本中,提出了Project Butter概念。

Project Butter對Android Display系統進行了重構,引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer。

  • VSYNC(垂直同步):定時產生一個中斷信號
  • Triple Buffer:當雙Buffer不夠時,分配第三個Buffer
  • Choreographer: 用來接受一個VSYNC信號來統一協調UI更新

為何是16ms

android系統每隔16ms更新一次UI,相當于每秒更新60次,這也是我們常說的60幀的由來。程序設置為60幀刷新是因為普通人的人眼與大腦之間的協作無法感知超過60fps的畫面更新。

如何渲染界面的

  • CPU(中央處理器) :多緩存多分支,適用于復雜的邏輯運算,主要負責Measure,Layout,Record,Execute的計算操作
  • GPU(圖像處理器):眾核少緩存,適用于結構單一的數據處理,主要負責Rasterization(柵格化)操作

想要了解更多可以參考 CPU 和 GPU 的區別是什么?

那么Android是如何把圖像繪制到界面上的呢?

這里需要引入一個Resterization柵格化的概念。

image

Resterization柵格化是繪制那些Button,Shape,Path,String,Bitmap等組件最基礎的操作。它把那些組件拆分到不同的像素上進行顯示。這是一個很費時的操作,GPU的引入就是為了加快柵格化的操作。

CPU負責把UI組件計算成Polygons,Texture紋理,然后交給GPU進行柵格化渲染。

整個流程如下

image

然而每次從CPU轉移到GPU是一件很麻煩的事情,所幸的是OpenGL ES可以把那些需要渲染的紋理Hold在GPU Memory里面,在下次需要渲染的時候直接進行操作。所以如果你更新了GPU所hold住的紋理內容,那么之前保存的狀態就丟失了。

在Android里面那些由主題所提供的資源,例如Bitmaps,Drawables都是一起打包到統一的Texture紋理當中,然后再傳遞到GPU里面,這意味著每次你需要使用這些資源的時候,都是直接從紋理里面進行獲取渲染的。當然隨著UI組件的越來越豐富,有了更多演變的形態。例如顯示圖片的時候,需要先經過CPU的計算加載到內存中,然后傳遞給GPU進行渲染。文字的顯示更加復雜,需要先經過CPU換算成紋理,然后再交給GPU進行渲染,回到CPU繪制單個字符的時候,再重新引用經過GPU渲染的內容。動畫則是一個更加復雜的操作流程。

為了能夠使得App流暢,我們需要在每一幀16ms以內處理完所有的CPU與GPU計算,繪制,渲染等等操作。

負責界面渲染的容器DisplayList

通常來說,Android需要把XML布局文件轉換成GPU能夠識別并繪制的對象。這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數據信息。

在某個View第一次需要被渲染時,DisplayList會因此而被創建,當這個View要顯示到屏幕上時,我們會執行GPU的繪制指令來進行渲染。如果你在后續有執行類似移動這個View的位置等操作而需要再次渲染這個View時,我們就僅僅需要額外操作一次渲染指令就夠了。然而如果你修改了View中的某些可見組件,那么之前的DisplayList就無法繼續使用了,我們需要回頭重新創建一個DisplayList并且重新執行渲染指令并更新到屏幕上。

需要注意的是:任何時候View中的繪制內容發生變化時,都會重新執行創建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。這個流程的表現性能取決于你的View的復雜程度,View的狀態變化以及渲染管道的執行性能。舉個例子,假設某個Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過父View重新計算并擺放其他子View的位置。修改View的大小會觸發整個HierarcyView的重新計算大小的操作。如果是修改View的位置則會觸發HierarchView重新計算其他View的位置。如果布局很復雜,這就會很容易導致嚴重的性能問題。我們需要盡量減少Overdraw。

image

Android Display工作方式

沒有VSYNC的情況, CPU和GPU比較“任性”,當他們處于空閑狀態時才會處理數據

image
  • T0階段:CPU處理第一幀的數據,處理完成之后,GPU緊跟著做柵格化處理
  • T1階段:Display將T0階段處理好的數據,因為第一幀的數據已經在T0階段處理完成,所以T1階段能正常顯示界面,同時CPU和GPU需要處理第二幀的數據。
  • T2階段:這個階段正常情況下應該顯示第二幀的數據,但因為在T1階段,CPU和GPU沒來的急處理完數據,導致該階段顯示不了第二幀的數據,這時只能延續第一幀的數據,這也是造成界面卡頓的主要原因.

出現上述問題的原因,究其原因還是CPU和GPU沒能及時處理數據,為了解決這個問題,引入了VSYNC

加入VSYNC(垂直同步)之后,Display的工作方式

文章開頭說過,VSYNC的作用是定時產生一個中斷信號,用來提醒CPU和GPU要開始工作了。

image

如圖所圖,在每個時間間隔的開始,CPU和GPU開始工作,處理下一幀的數據,用于Display顯示.正常情況下界面都能正常且平滑的顯示.

但如果在一個時間間隔即(16ms)內CPU和GPU處理不完下一幀的數據,還是會出現卡頓的情況,如下圖

image

T0,T1顯示的都是第一幀的數據,T2,T3顯示的都是第二幀的數據,刷新率從16ms變大到了32ms,從而引起卡頓感覺。另外在T1階段時,CPU并沒有進入工作,這是因為早期系統設計的是雙Buffer,此刻A Buffer被Display使用,B Buffer被GPU在用,所以CPU無事可做,只能閑置,另外由于VSYNC的存在,過了VSYNC的時間點,就算有多余的Buffer存在,CPU也不會重新進入工作.

于是就有了Triple Buffer,Triple Buffer的作用是當雙Buffer不夠時,分配第三個Buffer。

image

在加入C Buffer之后,在T1這個時間點CPU就不至于處于閑置狀態,雖然在T1時間,Display繪制的還是第一幀的數據,不過接下來的幾幀就顯得比較順暢了。

既然多Buffer的作用明顯,那為什么不多加幾個Buffer呢?實際上Buffer并不是越多越好的,由上圖可知,三個Buffer已經能解決大部分問題了,追加更多的Buffer并不能有效的提高效率,反而會因為多加的Buffer影響效率。

Choreographer: 用來接受一個VSYNC信號來統一協調UI更新

ChoreographerProject Butter也十分重要,除了統一協調UI更新之外,還可以用來監測UI是否發生卡頓

Android系統每隔16.6ms發出VSYNC信號,來通知界面進行輸入、動畫、繪制等動作,每一次同步的周期為16.6ms,代表一幀的刷新頻率,理論上來說兩次回調的時間周期應該在16.6ms,如果超過了16.6ms我們則認為發生了卡頓,利用兩次回調間的時間周期來判斷是否發生卡頓 這個方案的原理主要是通過Choreographer類設置它的FrameCallback函數,當每一幀被渲染時會觸發回調FrameCallback, FrameCallback回調void doFrame (long frameTimeNanos)函數。一次界面渲染會回調doFrame方法,如果兩次doFrame之間的間隔大于16.6ms說明發生了卡頓。

具體可以參考. Android 流暢度檢測原理簡析

參考文章

Android N中UI硬件渲染(hwui)的HWUI_NEW_OPS(基于Android 7.1)

Android Project Butter分析

Android性能優化典范

Android 流暢度檢測原理簡析

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容