Android效能優化之渲染優化
簡介
我們在開發的過程中,可能經常會遇到測試的一些反饋,就是APP執行卡頓的問題。我們通常所講的卡頓問題都是因為渲染掉幀的問題引起視覺上的卡頓感。所以瞭解渲染機制,我們在專案的開發過程中,可以有意識的少挖坑。同時要打造一款精品的應用,注意渲染優化也是非常重要的一件事情。
當然目前我們好多同學在開發的工程中,經常會忽略渲染優化這一塊,主要的原因可能是
- 專案沒要求,能滿足功能則可
- 缺少意識,沒有做效能優化的意識
- 缺少用工具分析,主觀感受不強
- 需求的苦海,無法脫身(有多少童鞋戳中淚點)
不管如何,我們都需要對自己有所要求。儘量在開發的過程中注意,少挖坑。對已上線的專案能夠進行優化分析,打造精品。 接下來我們將介紹渲染的底層機制,並針對性地進行優化分析。
渲染機制
視覺感官
我們都可能聽過Android的螢幕重新整理頻率是60fps 也就是16ms需要完成一幀的重新整理。
首先我們理解一下幀的概念。 每一幀都是靜止的圖象,快速連續地顯示幀便形成了運動的假象,因此高的幀率可以得到更流暢、更逼真的動畫。
當物體在快速運動時, 當人眼所看到的影像消失後,人眼仍能繼續保留其影像1/24秒左右的影象,這種現象被稱為視覺暫留現象。是人眼具有的一種性質。人眼觀看物體時,成像於視網膜上,並由視神經輸入人腦,感覺到物體的像。但當物體移去時,視神經對物體的印象不會立即消失,而要延續1/24秒左右的時間,人眼的這種性質被稱為“眼睛的視覺暫留”。
所以以前我們看膠捲電影的時候重新整理的頻率就是24fps。我們看起來就是連續的一個視覺效果。當然這裡越高的幀率,我們可以得到更流暢、逼真的畫面。
VSYNC
Android系統每隔16ms發出VSYNC訊號,觸發對UI進行渲染, 如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,為了能夠實現60fps,這意味著程式的大多數操作都必須在16ms內完成。如果超過了16ms那麼可能就出現丟幀的情況。

image
VSYNC有兩個概念
- Refresh Rate:螢幕在一秒時間內重新整理螢幕的次數----由硬體的引數決定,比如60HZ.
- Frame Rate:GPU在一秒內繪製操作的幀數,比如:60fps。
通常來說,幀率超過重新整理頻率只是一種理想的狀況,在超過60fps的情況下,GPU所產生的幀資料會因為等待VSYNC的重新整理資訊而被Hold住,這樣能夠保持每次重新整理都有實際的新的資料可以顯示。但是我們遇到更多的情況是幀率小於重新整理頻率。在這種情況下,某些幀顯示的畫面內容就會與上一幀的畫面相同,造成卡頓的現象。

image
簡單來說,VSYNC也叫垂直重新整理,是一個訊號。會觸發渲染。這個過程需要我們螢幕的重新整理頻率(一般60fps)和我們GPU所產生的幀數能夠進行同步,那麼UI的渲染就能流暢。如果我們自己定義的佈局或者自定義控制元件的渲染時間超過了16ms每幀,那麼就可能導致螢幕重新整理的時候,我們的GPU還不能產生新的幀,使用者看的還是舊的幀。這就造成了我們視覺上的卡頓,影響使用者體驗。
渲染管線
我們定義好了一個xml的佈局介面後,是怎樣最終呈現在我們的手機螢幕上的呢?
這裡我們藉助Google官方的效能優化的一張示例圖來說明。

image
CPU負責把UI元件計算成Polygons,Texture紋理,然後交給GPU進行柵格化渲染。最終在螢幕進行顯示。
這個地方CPU主要是將我們的佈局檔案的View Tree進行測量和繪製,最後形成Ploygons(多邊形)及Texture(紋理貼圖)
柵格化是繪製那些Button,Shape,Path,String,Bitmap等元件最基礎的操作。它把那些元件拆分到不同的畫素上進行顯示。這是一個很費時的操作,GPU的引入就是為了加快柵格化的操作
Android在效能優化已經做了很多工作。在CPU將Ploygons和Texture傳遞到GPU是一個很耗時的過程。所以Android將Bitmaps,Drawables都是一起打包到統一的Texture紋理當中,然後再傳遞到 GPU裡面,這意味著每次你需要使用這些資源的時候,都是直接從紋理裡面進行獲取渲染的。
Tip
專案裡曾經遇到一個問題,對一個圖示染色了。然後其他使用到改圖示的地方也同樣變成染色後的圖示了。這個地方就是因為GPU有快取的緣故。還有遇到過另外一個坑就是染色後的圖示再紅米的一個手機上無效,估計這個地方不同的硬體快取的機制可能還不一樣。所以如果專案中有用到圖示的染色需要注意。
如何在我們的專案中進行渲染優化?
知道了我們的渲染的機制,我們知道整一個渲染的的流程,基本都是系統在處理,流程我們沒辦法進行干預。那麼我們就需要針對渲染的原理,進行一些針對性的優化操作,減少我們每一幀的渲染時間,使應用更加流暢。所以平時除了我們都知道的阻塞UI執行緒導致卡頓,其實對於CPU及記憶體的不合理使用,也同樣會造成我們的卡頓。接下來我們來一一進行分析。
記憶體優化
程式在任意幀內執行GCs所用的時間越多,消除少於16毫秒的呈像障礙,所必需的時間就會變少,如果有許多GCs或一大串指令一個接一個地操作,幀象時間很可能會超過16毫秒的呈像障礙,這會導致隱形的碰撞或閃躲。記憶體在斷時間的抖動也會造成我們的卡頓現象。

image
所以如果要減少任意幀內啟動GC的次數,需要著重優化程式的記憶體使用量。
我們在實際的專案中了已通過Monitor進行記憶體的抖動分析,再通過分析原始碼來看是否在某一時刻重複建立大量的物件,導致GC的回收。

image
Tip
- 避免在迴圈裡面重複建立物件
- 操作大量的字元,慎用String進行+=,多使用StringBuilder及StringBuffer
- 多用池進行進行物件的複用
計算優化
這是一個很淺顯的道理,我們知道渲染的過程需要CPU參與Ploygons與Texture的生成,假如我們將CPU的使用率長時間壓榨得很高,自然就會影響我們的渲染,造成UI卡頓。
那麼怎麼來分析我們的計算優化呢?
首先一個很簡單,可以看看是否在執行某個操作的時候,過分的壓榨了CPU的使用率,我們通過Android Monitor可以看到瞬時的CPU的使用率。 觀察到CPU使用率的異常後,我們可以通過Traceview工具來查詢並確定哪些是阻礙應用程式效能問題的程式碼。

image
同樣開DDMS檢視選擇我們要分析的應用,這裡箭頭所指向看上去像是三面箭頭,上面有紅色的圓點,如果按這些按鈕,會出現一些提示,說將開始進行方法分析。這是TraceView的啟動方法,我們點選它。將出現一個彈出視窗,提示有兩種方法來分析你的應用程式。你可以記錄每個方法的輸入和輸出,他們對資源的要求很高,或者,你也利用示例進行一些分析。其含義是,預設情況下分析程式,將會每1000毫秒偵測一次你的應用程式,以發現和記錄實際上在執行的功能,現在,讓我們來使用這些預設設定。我點選一下OK,既然分析程式已經在繼續,我們就與你的應用程式進行互動,看能否記錄一些動作。
我們來看跟蹤檢視,跟蹤檢視有兩個主要組成部分。上方窗格的名稱是timeline面板,下方窗格內有很多的資訊,稱為profile面板。這個時間線能夠很好的顯示程式碼的執行情況,這裡顯示的每一行,實際上對應於一個執行緒。顯示的每一個顏色,對應於一個正在執行的特定方法。例如,我們可以看到,主執行緒的所有活動,我們可以看到方法啟動和停止時間點,更有用的是放大這裡,找到特定的方法,瞭解他們是如何執行的。它們會以這種U型模式顯示出來。這裡的條形表示,方法的啟動時間。右側的條形表示,方法的停止時間。條形的寬度表示方法執行所用的時間。現在,我們選擇一個特定的方法,我們跳轉到跟蹤檢視視窗的底部,這裡,我們看到一些分析資料顯示出來。我們可以看到哪些方法呼叫了我們選定的方法。
底部面板的一些欄位含義如下:

Tip
- 優化一些計算的演算法,例如遞迴等
- 使用執行緒池技術,避免過度壓榨CPU
- 使用批處理及快取,優化CPU計算
CPU優化
我們知道CPU在渲染的過程,主要需要處理Ploygons和Texture。在CPU方面,最常見的效能問題是不必要的佈局和失效,這些內容必須在檢視層次結構中進行測量、清除並重新建立,引發這種問題通常有兩個原因:一是重建顯示列表的次數太多,二是花費太多時間作廢檢視層次並進行不必要的重繪,這兩個原因在更新顯示列表或者其他快取GPU資源時導致CPU工作過度。 引用Google官方示例圖。

image
所以我們需要進行優化的點有:
- 減少不必要佈局元素
- 減少過多的佈局巢狀
那麼如何來知道,我們的佈局是否因為CPU過度工作導致我們的渲染卡頓呢? 我們可以通過DDMS裡面的Hierarchy Viewer 來進行我們的佈局分析。
1)通過AS的Tools-Android-Android Device Monitor調起

image
這個時候APP執行到我們需要檢測的介面,這個點選藍色的按鈕,就可以顯示當前介面的View Tree
2)我們可以通過圖2箭頭指向來觀察我們的View佈局、繪製、渲染的時間

image
- 箭頭1為我們當前View節點的介面,我們可以觀察當前節點的渲染時間
- 箭頭2為觸發檢測渲染效能的按鈕
- 箭頭3為渲染效能的顯示,有綠、黃、紅三種顏色
三個圓點分別代表:測量、佈局、繪製三個階段的效能表現。
- 綠色:渲染的管道階段,這個檢視的渲染速度快於至少一半的其他的檢視。
- 黃色:渲染速度比較慢的50%。
- 紅色:渲染速度非常慢。
所以我們可以根據分析檢視自己的佈局,層次是否很深以及渲染比較耗時,然後想辦法能否減少層級以及優化每一個View的渲染時。
Tip
- 避免過來無用的佈局巢狀,特別是ViewGroup層級儘量最小化
- 使用<merge>標籤,減少佈局巢狀
- 使用懶載入佈局 ViewStub,儘量減少使用View的GONE方式
- 注意一些自定義的View的效能,可通過工具的綠黃紅分析
GPU優化
通過上面的流程我們知道,GPU主要乾的事情就是柵格化,所以我們需要儘量儘量避免過度繪製(overdraw)。
我們在開發的過程中,經常會遇到牛逼的設計,需要完善絢麗的UI。高效能和完美的設計,往往會碰到一種效能問題,即過度繪製。過度繪製是一個術語,指的是螢幕上的某個畫素點在同一幀的時間內被繪製了多次。假如我們有一堆重疊的UI卡片,最接近使用者的卡片在最上面,其餘卡片都藏在下面,也就是說我們花大力氣繪製的那些下面的卡片基本都是不可見的。
我們藉助Google官方的一個圖來進行說明

image
Android在螢幕上使用不同顏色,標記過度繪製的區域,如果某個畫素點只渲染了一次,我們看到的是它原來的顏色,隨著過度繪製的增多,標記顏色也會逐漸加深,例如1倍過度繪製會被標記為藍色,2倍、3倍、4倍過度繪製遵循同樣的模式。所以當我們除錯應用程式的使用者介面時,目標就是儘可能的減少過度繪製,將紅色區塊轉變成藍色區塊。
1)通過開發者選項開啟過度繪製檢測

image
2)開啟後就可以檢視應用的繪製情況

image
這裡拿了百度網盤來做例子,還是優化得不錯。
首先我們要從檢視中清除那些,不必要的背景和圖片,他們不會在最終渲染影象中顯示,這些都會影響效能。其次,對檢視中重疊的螢幕區域進行定義,從而降低CPU和GPU的消耗。
Tip
- 由於我們佈局設定了背景,同時用到的MaterialDesign的主題會預設給一個背景。可以在Activity設定getWindow().setBackgroundDrawable(null);
- 儘量保持你的佈局只有一層擁有Background,避免給過多的ViewGroup設定背景
- 如果是自定義控制元件可以通過裁剪來處理(Canvas.clipRect)。
總結
- 儘量瞭解渲染的機制,在開始做專案的時候就少挖坑
- 儘量動手給自己現在的專案進行優化,這樣可以更深刻的理解
- 渲染優化是一個苦逼的體力活,掌握了方法以後,我們需要花時間去一個個調優
喜歡的話請幫忙轉發一下能讓更多有需要的人看到吧,有些技術上的問題大家可以多探討一下。

