Android性能優化-渲染優化

簡介

我們在開發的過程中,可能經常會遇到測試的一些反饋,就是APP運行卡頓的問題。我們通常所講的卡頓問題都是因爲渲染掉幀的問題引起視覺上的卡頓感。所以瞭解渲染機制,我們在項目的開發過程中,可以有意識的少挖坑。同時要打造一款精品的應用,注意渲染優化也是非常重要的一件事情。

當然目前我們好多同學在開發的工程中,經常會忽略渲染優化這一塊,主要的原因可能是

  • 項目沒要求,能滿足功能則可
  • 缺少意識,沒有做性能優化的意識
  • 缺少用工具分析,主觀感受不強
  • 需求的苦海,無法脫身(有多少童鞋戳中淚點)

不管如何,我們都需要對自己有所要求。儘量在開發的過程中注意,少挖坑。對已上線的項目能夠進行優化分析,打造精品。 接下來我們將介紹渲染的底層機制,並針對性地進行優化分析。

渲染機制

視覺感官

我們都可能聽過Android的屏幕刷新頻率是60fps 也就是16ms需要完成一幀的刷新。

首先我們理解一下幀的概念。 每一幀都是靜止的圖象,快速連續地顯示幀便形成了運動的假象,因此高的幀率可以得到更流暢、更逼真的動畫。

當物體在快速運動時, 當人眼所看到的影像消失後,人眼仍能繼續保留其影像1/24秒左右的圖像,這種現象被稱爲視覺暫留現象。是人眼具有的一種性質。人眼觀看物體時,成像於視網膜上,並由視神經輸入人腦,感覺到物體的像。但當物體移去時,視神經對物體的印象不會立即消失,而要延續1/24秒左右的時間,人眼的這種性質被稱爲「眼睛的視覺暫留」。

所以以前我們看膠捲電影的時候刷新的頻率就是24fps。我們看起來就是連續的一個視覺效果。當然這裏越高的幀率,我們可以得到更流暢、逼真的畫面。

VSYNC

Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染, 如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,爲了能夠實現60fps,這意味着程序的大多數操作都必須在16ms內完成。如果超過了16ms那麼可能就出現丟幀的情況。

VSYNC有兩個概念

  1. Refresh Rate:屏幕在一秒時間內刷新屏幕的次數----由硬件的參數決定,比如60HZ.
  2. Frame Rate:GPU在一秒內繪製操作的幀數,比如:60fps。

通常來說,幀率超過刷新頻率只是一種理想的狀況,在超過60fps的情況下,GPU所產生的幀數據會因爲等待VSYNC的刷新信息而被Hold住,這樣能夠保持每次刷新都有實際的新的數據可以顯示。但是我們遇到更多的情況是幀率小於刷新頻率。在這種情況下,某些幀顯示的畫面內容就會與上一幀的畫面相同,造成卡頓的現象。

簡單來說,VSYNC也叫垂直刷新,是一個信號。會觸發渲染。這個過程需要我們屏幕的刷新頻率(一般60fps)和我們GPU所產生的幀數能夠進行同步,那麼UI的渲染就能流暢。如果我們自己定義的佈局或者自定義控件的渲染時間超過了16ms每幀,那麼就可能導致屏幕刷新的時候,我們的GPU還不能產生新的幀,用戶看的還是舊的幀。這就造成了我們視覺上的卡頓,影響用戶體驗。

渲染管線

我們定義好了一個xml的佈局界面後,是怎樣最終呈現在我們的手機屏幕上的呢?

這裏我們藉助Google官方的性能優化的一張示例圖來說明。

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毫秒的呈像障礙,這會導致隱形的碰撞或閃躲。內存在斷時間的抖動也會造成我們的卡頓現象。

所以如果要減少任意幀內啓動GC的次數,需要着重優化程序的內存使用量。

我們在實際的項目中了已通過Monitor進行內存的抖動分析,再通過分析源碼來看是否在某一時刻重複創建大量的對象,導致GC的回收。

Tip

 

  1. 避免在循環裏面重複創建對象
  2. 操作大量的字符,慎用String進行+=,多使用StringBuilder及StringBuffer
  3. 多用池進行進行對象的複用

計算優化

這是一個很淺顯的道理,我們知道渲染的過程需要CPU參與Ploygons與Texture的生成,假如我們將CPU的使用率長時間壓榨得很高,自然就會影響我們的渲染,造成UI卡頓。

那麼怎麼來分析我們的計算優化呢?

首先一個很簡單,可以看看是否在執行某個操作的時候,過分的壓榨了CPU的使用率,我們通過Android Monitor可以看到瞬時的CPU的使用率。 觀察到CPU使用率的異常後,我們可以通過Traceview工具來查找並確定哪些是阻礙應用程序性能問題的代碼。

同樣開DDMS視圖選擇我們要分析的應用,這裏箭頭所指向看上去像是三面箭頭,上面有紅色的圓點,如果按這些按鈕,會出現一些提示,說將開始進行方法分析。這是TraceView的啓動方法,我們點擊它。將出現一個彈出窗口,提示有兩種方法來分析你的應用程序。你可以記錄每個方法的輸入和輸出,他們對資源的要求很高,或者,你也利用示例進行一些分析。其含義是,默認情況下分析程序,將會每1000毫秒偵測一次你的應用程序,以發現和記錄實際上在運行的功能,現在,讓我們來使用這些默認設置。我點擊一下OK,既然分析程序已經在繼續,我們就與你的應用程序進行交互,看能否記錄一些動作。

我們來看跟蹤視圖,跟蹤視圖有兩個主要組成部分。上方窗格的名稱是timeline面板,下方窗格內有很多的信息,稱爲profile面板。這個時間線能夠很好的顯示代碼的執行情況,這裏顯示的每一行,實際上對應於一個線程。顯示的每一個顏色,對應於一個正在運行的特定方法。例如,我們可以看到,主線程的所有活動,我們可以看到方法啓動和停止時間點,更有用的是放大這裏,找到特定的方法,瞭解他們是如何執行的。它們會以這種U型模式顯示出來。這裏的條形表示,方法的啓動時間。右側的條形表示,方法的停止時間。條形的寬度表示方法執行所用的時間。現在,我們選擇一個特定的方法,我們跳轉到跟蹤視圖窗口的底部,這裏,我們看到一些分析數據顯示出來。我們可以看到哪些方法調用了我們選定的方法。

底部面板的一些字段含義如下:

列名

作用

Name

該進程運行過程中所調用的函數名

Incl Cpu Time

函數佔用的CPU時間,包含內部調用其它函數的CPU時間

Excl Cpu Time

函數佔用的CPU時間,但不包含內部調用其它函數所佔用的CPU時間

Incl Real Time

函數運行的真實時間(以毫秒爲單位),內含調用其它函數所佔用的真實時間

Excl Real Time

函數運行的真實時間(以毫秒爲單位),不包含調用其它函數所佔用的真實時間

Calls+Recur Calls/Total

函數被調用次數以及遞歸調用佔總調用次數的百分比

Cpu Time/Call

函數調用CPU時間與調用次數的比(該函數平均執行時間)

Real Time/Call

同CPU Time/Call類似,只不過統計單位換成了真實時間

Tip

 

  1. 優化一些計算的算法,例如遞歸等
  2. 使用線程池技術,避免過度壓榨CPU
  3. 使用批處理及緩存,優化CPU計算

CPU優化

我們知道CPU在渲染的過程,主要需要處理Ploygons和Texture。在CPU方面,最常見的性能問題是不必要的佈局和失效,這些內容必須在視圖層次結構中進行測量、清除並重新創建,引發這種問題通常有兩個原因:一是重建顯示列表的次數太多,二是花費太多時間作廢視圖層次並進行不必要的重繪,這兩個原因在更新顯示列表或者其他緩存GPU資源時導致CPU工作過度。 引用Google官方示例圖。

所以我們需要進行優化的點有:

  1. 減少不必要佈局元素
  2. 減少過多的佈局嵌套

那麼如何來知道,我們的佈局是否因爲CPU過度工作導致我們的渲染卡頓呢? 我們可以通過DDMS裏面的Hierarchy Viewer 來進行我們的佈局分析。

1)通過AS的Tools-Android-Android Device Monitor調起

這個時候APP運行到我們需要檢測的界面,這個點擊藍色的按鈕,就可以顯示當前界面的View Tree

2)我們可以通過圖2箭頭指向來觀察我們的View佈局、繪製、渲染的時間

  • 箭頭1爲我們當前View節點的界面,我們可以觀察當前節點的渲染時間
  • 箭頭2爲觸發檢測渲染性能的按鈕
  • 箭頭3爲渲染性能的顯示,有綠、黃、紅三種顏色

三個圓點分別代表:測量、佈局、繪製三個階段的性能表現。

  1. 綠色:渲染的管道階段,這個視圖的渲染速度快於至少一半的其他的視圖。
  2. 黃色:渲染速度比較慢的50%。
  3. 紅色:渲染速度非常慢。

所以我們可以根據分析查看自己的佈局,層次是否很深以及渲染比較耗時,然後想辦法能否減少層級以及優化每一個View的渲染時。

Tip

 

  1. 避免過來無用的佈局嵌套,特別是ViewGroup層級儘量最小化
  2. 使用<merge>標籤,減少佈局嵌套
  3. 使用懶加載佈局 ViewStub,儘量減少使用View的GONE方式
  4. 注意一些自定義的View的性能,可通過工具的綠黃紅分析

GPU優化

通過上面的流程我們知道,GPU主要乾的事情就是柵格化,所以我們需要儘量儘量避免過度繪製(overdraw)。

我們在開發的過程中,經常會遇到牛逼的設計,需要完善絢麗的UI。高性能和完美的設計,往往會碰到一種性能問題,即過度繪製。過度繪製是一個術語,指的是屏幕上的某個像素點在同一幀的時間內被繪製了多次。假如我們有一堆重疊的UI卡片,最接近用戶的卡片在最上面,其餘卡片都藏在下面,也就是說我們花大力氣繪製的那些下面的卡片基本都是不可見的。

我們藉助Google官方的一個圖來進行說明

Android在屏幕上使用不同顏色,標記過度繪製的區域,如果某個像素點只渲染了一次,我們看到的是它原來的顏色,隨着過度繪製的增多,標記顏色也會逐漸加深,例如1倍過度繪製會被標記爲藍色,2倍、3倍、4倍過度繪製遵循同樣的模式。所以當我們調試應用程序的用戶界面時,目標就是儘可能的減少過度繪製,將紅色區塊轉變成藍色區塊。

1)通過開發者選項打開過度繪製檢測

2)開啓後就可以查看應用的繪製情況

這裏拿了百度網盤來做例子,還是優化得不錯。

首先我們要從視圖中清除那些,不必要的背景和圖片,他們不會在最終渲染圖像中顯示,這些都會影響性能。其次,對視圖中重疊的屏幕區域進行定義,從而降低CPU和GPU的消耗。

Tip

 

  1. 由於我們佈局設置了背景,同時用到的MaterialDesign的主題會默認給一個背景。可以在Activity設置getWindow().setBackgroundDrawable(null);
  2. 儘量保持你的佈局只有一層擁有Background,避免給過多的ViewGroup設置背景
  3. 如果是自定義控件可以通過裁剪來處理(Canvas.clipRect)。

總結

  1. 儘量瞭解渲染的機制,在開始做項目的時候就少挖坑
  2. 儘量動手給自己現在的項目進行優化,這樣可以更深刻的理解
  3. 渲染優化是一個苦逼的體力活,掌握了方法以後,我們需要花時間去一個個調優