1、為什么是16ms
2、為什么16ms沒完成繪制就會卡頓

上圖所示是VSync機制下的繪制過程。從上圖可以看出,CPU和GPU的處理時間都少於一個VSync的間隔,即16.6ms。如果每個間隔都有繪制的情況下,當前的FPS即為60幀。
當CPU和GPU處理時間都很慢,或因為其他的原因,如在主線程中干活太多,那么就會出現如下圖這樣的狀況。

3、渲染原理


分析到這里,16毫秒的時間主要被兩件事情所占用,第一件:將UI對象轉換為一系列多邊形和紋理;第二件:CPU傳遞處理數據到GPU。所以很明顯,我們要縮短這兩部分的時間,也就是說需要盡量減少對象轉換的次數,以及上傳數據的次數。
我們再看一圖,這圖簡單說明CPU和GPU的職責工作,以及可能發生的問題和解決方案。
在CPU方面,最常見的性能問題是不必要的布局和失效,這些內容必須在視圖層次結構中進行測量、清除並重新創建,引發這種問題通常有兩個原因:一是重建顯示列表的次數太多,二是花費太多時間作廢視圖層次並進行不必要的重繪,這兩個原因在更新顯示列表或者其他緩存GPU資源時導致CPU工作過度。在GPU方面,最常見的問題是我們所說的過度繪制(overdraw),通常是在像素着色過程中,通過其他工具進行后期着色時浪費了GPU處理時間。下面我們對GPU和CPU產生的兩大問題進行優化。
- CPU產生的問題:不必要的布局
- GPU產生的問題:過度繪制(overdraw)
4、過度繪制(overdraw)檢測
Overdraw(過度繪制)描述的是屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次的UI結構里面, 如果不可見的UI也在做繪制的操作,這就會導致某些像素區域被繪制了多次。這就浪費大量的CPU以及GPU資源。
按照以下步驟打開Show GPU Overrdraw的選項:設置 -> 開發者選項 -> 調試GPU過度繪制 -> 顯示GPU過度繪制

藍色,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,
- 藍色: 意味着overdraw 1倍。像素繪制了兩次。大片的藍色還是可以接受的(若整個窗口是藍色的,可以擺脫一層)。
- 綠色: 意味着overdraw 2倍。像素繪制了三次。中等大小的綠色區域是可以接受的但你應該嘗試優化、減少它們。
- 淡紅: 意味着overdraw 3倍。像素繪制了四次,小范圍可以接受。
- 深紅: 意味着overdraw 4倍。像素繪制了五次或者更多。這是錯誤的,要修復它們。
我們的目標就是盡量減少紅色Overdraw,看到更多的藍色區域。
5、Overdraw 的處理方案
當我們使用了Android自帶的一些主題時,window會被默認添加一個純色的背景,這個背景是被DecorView持有的。當我們的自定義布局時又添加了一張背景圖或者設置背景色,那么DecorView的background此時對我們來說是無用的,但是它會產生一次Overdraw,帶來繪制性能損耗。去掉window的背景可以在onCreate()中setContentView()之后調用getWindow().setBackgroundDrawable(null);或者在theme中添加android:windowbackground="null";
有時候為了方便會先給Layout設置一個整體的背景,再給子View設置背景,這里也會造成重疊,如果子View寬度mach_parent,可以看到完全覆蓋了Layout的一部分,這里就可以通過分別設置背景來減少重繪。再比如如果采用的是selector的背景,將normal狀態的color設置為“@android:color/transparent”,也同樣可以解決問題。這里只簡單舉兩個例子,我們在開發過程中的一些習慣性思維定式會帶來不經意的Overdraw,所以開發過程中我們為某個View或者ViewGroup設置背景的時候,先思考下是否真的有必要,或者思考下這個背景能不能分段設置在子View上,而不是圖方便直接設置在根View上。
我們可以通過 canvas.clipRect()來 幫助系統識別那些可見的區域。這個方法可以指定一塊矩形區域,只有在這個區域內才會被繪制,其他的區域會被忽視。這個API可以很好的幫助那些有多組重疊 組件的自定義View來控制顯示的區域。同時clipRect方法還可以幫助節約CPU與GPU資源,在clipRect區域之外的繪制指令都不會被執行,那些部分內容在矩形區域內的組件,仍然會得到繪制。
ViewStub稱之為“延遲化加載”,在教多數情況下,程序無需顯示ViewStub所指向的布局文件,只有在特定的某些較少條件下,此時ViewStub所指向的布局文件才需要被inflate,且此布局文件直接將當前ViewStub替換掉,具體是通過viewStub.infalte()或viewStub.setVisibility(View.VISIBLE)來完成;
MMerge標簽可以干掉一個view層級。Merge的作用很明顯,但是也有一些使用條件的限制。有兩種情況下我們可以使用Merge標簽來做容器控件。第一種子視圖不需要指定任何針對父視圖的布局屬性,就是說父容器僅僅是個容器,子視圖只需要直接添加到父視圖上用於顯示就行。另外一種是假如需要在LinearLayout里面嵌入一個布局(或者視圖),而恰恰這個布局(或者視圖)的根節點也是LinearLayout,這樣就多了一層沒有用的嵌套,無疑這樣只會拖慢程序速度。而這個時候如果我們使用merge根標簽就可以避免那樣的問題。另外Merge只能作為XML布局的根標簽使用,當Inflate以開頭的布局文件時,必須指定一個父ViewGroup,並且必須設定attachToRoot為true。