Android性能優化第(四)篇---Android渲染機制

版權聲明:本文為LooperJing原創文章,轉載請注明出處!

藝術的渲染

優化性能一般從渲染,運算與內存,電量三個方面進行,今天開始說聊一聊Android的渲染機制,我們要知道Android系統每隔16ms就重新繪制一次Activity,也就是說,我們的應用必須在16ms內完成屏幕刷新的全部邏輯操作,即每一幀只能停留16ms,渲染機制說完之后,然后在說如何去優化UI。

1、為什么是16ms

16ms意味著1000/60hz,相當于60fps。這是因為人眼與大腦之間的協作無法感知超過60fps的畫面更新。12fps大概類似手動快速翻動書籍的幀率, 這明顯是可以感知到不夠順滑的。24fps使得人眼感知的是連續線性的運動,這其實是歸功于運動模糊的效果。 24fps是電影膠圈通常使用的幀率,因為這個幀率已經足夠支撐大部分電影畫面需要表達的內容,同時能夠最大的減少費用支出。 但是低于30fps是 無法順暢表現絢麗的畫面內容的,此時就需要用到60fps來達到想要的效果,超過60fps就沒有必要了。如果我們的應用沒有在16ms內完成屏幕刷新的全部邏輯操作,就會發生卡頓。**

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

Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,VSync是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上很早就廣泛使用的技術,可以簡單的把它認為是一種定時中斷。而在Android 4.1(JB)中已經開始引入VSync機制。



上圖所示是VSync機制下的繪制過程。從上圖可以看出,CPU和GPU的處理時間都少于一個VSync的間隔,即16.6ms。如果每個間隔都有繪制的情況下,當前的FPS即為60幀。

當CPU和GPU處理時間都很慢,或因為其他的原因,如在主線程中干活太多,那么就會出現如下圖這樣的狀況。


從上圖可以看到,CPU和GPU的處理時間因為各種原因都大于一個VSync的間隔(16.6ms),所以在第二個VSync還在處理1區域的繪制時,不可能實現理論上的FPS60,同時也出現了丟幀(SF: Skipped Frame)情況。試想用戶盯著同一張圖看了32ms而不是16ms,當然很容易察覺出卡頓感,哪怕僅僅出現一次掉幀,用戶都會發現動畫不是很順暢,大家在察覺到APP卡頓的時候,可以看看logcat控制臺,會有drop frames類似的警告,那么是什么原因導致16ms沒能完成繪制的操作呢?

3、渲染原理

上面說了CPU和GPU的處理時間因為各種原因都大于一個VSync的間隔(16.6ms),導致了卡頓。渲染操作通常依賴于兩個核心組件:CPU與GPU。CPU負責包括Measure,Layout,Record,Execute的計算操作,GPU 負責Rasterization(柵格化)操作。何為柵格化,我也是第一次聽到這詞,看下圖。

柵格化

所謂的柵格化就是繪制那些Button,Shape,Path,String,Bitmap等組件最基礎的操作。它把那些組件拆分到不同的像素上進行顯示,說的俗氣一點,就是解決那些復雜的XML布局文件和標記語言,使之轉化成用戶能看懂的圖像,但是這不是直接轉換的,XML布局文件需要在CPU中首先轉換為多邊形或者紋理,然后再傳遞給GPU進行格柵化,對于柵格化,跟OpenGL有關,格柵化是一個特別費時的操作。

分析到這里,16毫秒的時間主要被兩件事情所占用,第一件:將UI對象轉換為一系列多邊形和紋理;第二件:CPU傳遞處理數據到GPU。所以很明顯,我們要縮短這兩部分的時間,也就是說需要盡量減少對象轉換的次數,以及上傳數據的次數,對否?

我們再看一圖,這圖簡單說明CPU和GPU的職責工作,以及可能發生的問題和解決方案。


列名 解釋
PIPELINE 管道
PROBLEM 發生的問題
TOOLS 用什么工具來解決
SOLUTION 解決方案時什么

在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 的處理方案
  • Overdraw 的處理方案一:去掉window的默認背景
    當我們使用了Android自帶的一些主題時,window會被默認添加一個純色的背景,這個背景是被DecorView持有的。當我們的自定義布局時又添加了一張背景圖或者設置背景色,那么DecorView的background此時對我們來說是無用的,但是它會產生一次Overdraw,帶來繪制性能損耗。去掉window的背景可以在onCreate()中setContentView()之后調用getWindow().setBackgroundDrawable(null);或者在theme中添加android:windowbackground="null";

  • Overdraw 的處理方案二:去掉其他不必要的背景
    有時候為了方便會先給Layout設置一個整體的背景,再給子View設置背景,這里也會造成重疊,如果子View寬度mach_parent,可以看到完全覆蓋了Layout的一部分,這里就可以通過分別設置背景來減少重繪。再比如如果采用的是selector的背景,將normal狀態的color設置為“@android:color/transparent”,也同樣可以解決問題。這里只簡單舉兩個例子,我們在開發過程中的一些習慣性思維定式會帶來不經意的Overdraw,所以開發過程中我們為某個View或者ViewGroup設置背景的時候,先思考下是否真的有必要,或者思考下這個背景能不能分段設置在子View上,而不是圖方便直接設置在根View上。

  • Overdraw 的處理方案三:clipRect的使用
    我們可以通過canvas.clipRect()來 幫助系統識別那些可見的區域。這個方法可以指定一塊矩形區域,只有在這個區域內才會被繪制,其他的區域會被忽視。這個API可以很好的幫助那些有多組重疊 組件的自定義View來控制顯示的區域。同時clipRect方法還可以幫助節約CPU與GPU資源,在clipRect區域之外的繪制指令都不會被執行,那些部分內容在矩形區域內的組件,仍然會得到繪制。

  • Overdraw 的處理方案四:ViewStub
    ViewStub稱之為“延遲化加載”,在教多數情況下,程序無需顯示ViewStub所指向的布局文件,只有在特定的某些較少條件下,此時ViewStub所指向的布局文件才需要被inflate,且此布局文件直接將當前ViewStub替換掉,具體是通過viewStub.infalte()或viewStub.setVisibility(View.VISIBLE)來完成;

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ViewStub
        android:id="@+id/network_error_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/empty_view" />
</RelativeLayout>

 private void showNetError() {
        // not repeated infalte
        if (networkErrorView != null) {
            networkErrorView.setVisibility(View.VISIBLE);
            return;
        }

        ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
        networkErrorView = stub.inflate();
        Button networkSetting = (Button)networkErrorView.findViewById(R.id.network_setting);
        Button refresh = (Button)findViewById(R.id.network_refresh);
    }

    private void showNormal() {
        if (networkErrorView != null) {
            networkErrorView.setVisibility(View.GONE);
        }
    }

  • Overdraw 的處理方案五:Merge標簽
    MMerge標簽可以干掉一個view層級。Merge的作用很明顯,但是也有一些使用條件的限制。有兩種情況下我們可以使用Merge標簽來做容器控件。第一種子視圖不需要指定任何針對父視圖的布局屬性,就是說父容器僅僅是個容器,子視圖只需要直接添加到父視圖上用于顯示就行。另外一種是假如需要在LinearLayout里面嵌入一個布局(或者視圖),而恰恰這個布局(或者視圖)的根節點也是LinearLayout,這樣就多了一層沒有用的嵌套,無疑這樣只會拖慢程序速度。而這個時候如果我們使用merge根標簽就可以避免那樣的問題。另外Merge只能作為XML布局的根標簽使用,當Inflate以開頭的布局文件時,必須指定一個父ViewGroup,并且必須設定attachToRoot為true。
6、減少不必要的層次:巧用Hierarchy Viewer

Hierarchy Viewer接觸過Android的人估計都用過,如果在真機上可以
使用ViewServer這個第三方庫:https://github.com/romainguy/ViewServer,配置步驟比較簡單,主要分為如下三步:
第一步,在根build.gradle文件中加入

allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

第二步,在Module的build.gradle文件中加入

dependencies {
    ...................................
    compile 'com.github.romainguy:ViewServer:017c01cd512cac3ec054d9eee05fc48c5a9d2de'
}

第三步,加上訪問網絡權限,在Activity添加下列代碼

public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        // Set content view, etc.  
        ViewServer.get(this).addWindow(this);  
    }  
  
    public void onDestroy() {  
        super.onDestroy();  
        ViewServer.get(this).removeWindow(this);  
    }  
  
    public void onResume() {  
        super.onResume();  
        ViewServer.get(this).setFocusedWindow(this);  
    }  

它只能在root過的機器才能使用,可以幫我們減少View的層,在Hierarchy Viewer窗口中,所有的子View上面都有了3個圈圈, (取色范圍為紅、黃、綠色),這三個圈圈分別代表measure 、layout、draw的速度,并且你也可以看到實際的運行的速度,如果你發現某個View上的圈是紅色,那么說明這個View相對其他的View,該操作運行最慢,注意只是相對別的View,并不是說就一定很慢。

布局常見問題與優化建議

  • 沒有用的父布局時指沒有背景繪制或者沒有大小限制的父布局,這樣的布局不會對UI效果產生任何影響。我們可以把沒有用的父布局,通過<merge/>標簽合并來減少UI的層次;
  • 使用線性布局LinearLayout排版導致UI層次變深,如果有這類問題,我們就使用相對布局RelativeLayout代替LinearLayout,減少UI的層次;
  • 不常用的UI被設置成GONE,比如異常的錯誤頁面,如果有這類問題,我們需要用<ViewStub/>標簽,代替GONE提高UI性能。

參考鏈接:
http://www.cnblogs.com/krislight1105/p/5352517.html
http://www.csdn.net/article/2015-01-20/2823621-android-performance-patterns

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容