安卓UI 渲染機制

優(yōu)化性能一般從渲染,運算與內(nèi)存,電量三個方面進行谨娜,今天開始說聊一聊Android的渲染機制,我們要知道Android系統(tǒng)每隔16ms就重新繪制一次Activity磺陡,也就是說趴梢,我們的應用必須在16ms內(nèi)完成屏幕刷新的全部邏輯操作,即每一幀只能停留16ms币他,渲染機制說完之后坞靶,然后在說如何去優(yōu)化UI。

1蝴悉、為什么是16ms

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

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

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

image.png

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

當CPU和GPU處理時間都很慢脱茉,或因為其他的原因,如在主線程中干活太多垄开,那么就會出現(xiàn)如下圖這樣的狀況琴许。


image.png

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

3屁魏、渲染原理

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

[圖片上傳失敗...(image-688e9f-1525510460536)]

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

image.png

分析到這里焊切,16毫秒的時間主要被兩件事情所占用扮授,第一件:將UI對象轉(zhuǎn)換為一系列多邊形和紋理;第二件:CPU傳遞處理數(shù)據(jù)到GPU蛛蒙。所以很明顯糙箍,我們要縮短這兩部分的時間,也就是說需要盡量減少對象轉(zhuǎn)換的次數(shù)牵祟,以及上傳數(shù)據(jù)的次數(shù)深夯,對否?

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

[圖片上傳失敗...(image-75f3e7-1525510821340)]

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

在CPU方面收奔,最常見的性能問題是不必要的布局和失效掌呜,這些內(nèi)容必須在視圖層次結構中進行測量、清除并重新創(chuàng)建坪哄,引發(fā)這種問題通常有兩個原因:一是重建顯示列表的次數(shù)太多质蕉,二是花費太多時間作廢視圖層次并進行不必要的重繪,這兩個原因在更新顯示列表或者其他緩存GPU資源時導致CPU工作過度翩肌。在GPU方面模暗,最常見的問題是我們所說的過度繪制(overdraw),通常是在像素著色過程中念祭,通過其他工具進行后期著色時浪費了GPU處理時間兑宇。下面我們對GPU和CPU產(chǎn)生的兩大問題進行優(yōu)化。

  • CPU產(chǎn)生的問題:不必要的布局和失效
  • GPU產(chǎn)生的問題:過度繪制(overdraw)
4粱坤、過度繪制(overdraw)*檢測

Overdraw(過度繪制)描述的是屏幕上的某個像素在同一幀的時間內(nèi)被繪制了多次隶糕。在多層次的UI結構里面瓷产, 如果不可見的UI也在做繪制的操作,這就會導致某些像素區(qū)域被繪制了多次枚驻。這就浪費大量的CPU以及GPU資源濒旦。

按照以下步驟打開Show GPU Overrdraw的選項:設置 -> 開發(fā)者選項 -> 調(diào)試GPU過度繪制 -> 顯示GPU過度繪制

[圖片上傳失敗...(image-3ea5ed-1525510460536)]

藍色,淡綠测秸,淡紅疤估,深紅代表了4種不同程度的Overdraw情況,

  • 藍色: 意味著overdraw 1倍霎冯。像素繪制了兩次铃拇。大片的藍色還是可以接受的(若整個窗口是藍色的,可以擺脫一層)沈撞。
  • 綠色: 意味著overdraw 2倍慷荔。像素繪制了三次。中等大小的綠色區(qū)域是可以接受的但你應該嘗試優(yōu)化缠俺、減少它們显晶。
  • 淡紅: 意味著overdraw 3倍。像素繪制了四次壹士,小范圍可以接受磷雇。
  • 深紅: 意味著overdraw 4倍。像素繪制了五次或者更多躏救。這是錯誤的唯笙,要修復它們。

我們的目標就是盡量減少紅色Overdraw盒使,看到更多的藍色區(qū)域崩掘。

5、Overdraw 的處理方案
  • Overdraw 的處理方案一:去掉window的默認背景
    當我們使用了Android自帶的一些主題時少办,window會被默認添加一個純色的背景苞慢,這個背景是被DecorView持有的。當我們的自定義布局時又添加了一張背景圖或者設置背景色英妓,那么DecorView的background此時對我們來說是無用的挽放,但是它會產(chǎn)生一次Overdraw,帶來繪制性能損耗蔓纠。去掉window的背景可以在onCreate()中setContentView()之后調(diào)用getWindow().setBackgroundDrawable(null);或者在theme中添加android:windowbackground="null"骂维;

  • Overdraw 的處理方案二:去掉其他不必要的背景
    有時候為了方便會先給Layout設置一個整體的背景,再給子View設置背景贺纲,這里也會造成重疊,如果子View寬度mach_parent褪测,可以看到完全覆蓋了Layout的一部分猴誊,這里就可以通過分別設置背景來減少重繪潦刃。再比如如果采用的是selector的背景,將normal狀態(tài)的color設置為“@android:color/transparent”,也同樣可以解決問題懈叹。這里只簡單舉兩個例子乖杠,我們在開發(fā)過程中的一些習慣性思維定式會帶來不經(jīng)意的Overdraw,所以開發(fā)過程中我們?yōu)槟硞€View或者ViewGroup設置背景的時候澄成,先思考下是否真的有必要胧洒,或者思考下這個背景能不能分段設置在子View上,而不是圖方便直接設置在根View上墨状。

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

  • Overdraw 的處理方案四:ViewStub
    ViewStub稱之為“延遲化加載”诗越,在教多數(shù)情況下,程序無需顯示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里面嵌入一個布局(或者視圖)呀狼,而恰恰這個布局(或者視圖)的根節(jié)點也是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'
}

第三步,加上訪問網(wǎng)絡權限作媚,在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的速度勤哗,并且你也可以看到實際的運行的速度,如果你發(fā)現(xiàn)某個View上的圈是紅色掩驱,那么說明這個View相對其他的View芒划,該操作運行最慢,注意只是相對別的View欧穴,并不是說就一定很慢民逼。

布局常見問題與優(yōu)化建議

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

作者:LooperJing
鏈接:http://www.reibang.com/p/9ac245657127
來源:簡書
著作權歸作者所有燕侠。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權者祖,非商業(yè)轉(zhuǎn)載請注明出處。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贬循,一起剝皮案震驚了整個濱河市咸包,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杖虾,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媒熊,死亡現(xiàn)場離奇詭異奇适,居然都是意外死亡,警方通過查閱死者的電腦和手機芦鳍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門嚷往,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人柠衅,你說我怎么就攤上這事皮仁。” “怎么了菲宴?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵贷祈,是天一觀的道長。 經(jīng)常有香客問我喝峦,道長势誊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任谣蠢,我火速辦了婚禮粟耻,結果婚禮上,老公的妹妹穿的比我還像新娘眉踱。我一直安慰自己挤忙,他們只是感情好,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布谈喳。 她就那樣靜靜地躺著册烈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叁执。 梳的紋絲不亂的頭發(fā)上茄厘,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天,我揣著相機與錄音谈宛,去河邊找鬼次哈。 笑死,一個胖子當著我的面吹牛吆录,可吹牛的內(nèi)容都是我干的窑滞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哀卫!你這毒婦竟也來了巨坊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤此改,失蹤者是張志新(化名)和其女友劉穎趾撵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體共啃,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡占调,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了移剪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片究珊。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纵苛,靈堂內(nèi)的尸體忽然破棺而出剿涮,到底是詐尸還是另有隱情,我是刑警寧澤攻人,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布取试,位于F島的核電站,受9級特大地震影響贝椿,放射性物質(zhì)發(fā)生泄漏想括。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一烙博、第九天 我趴在偏房一處隱蔽的房頂上張望瑟蜈。 院中可真熱鬧,春花似錦渣窜、人聲如沸铺根。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽位迂。三九已至,卻和暖如春详瑞,著一層夾襖步出監(jiān)牢的瞬間掂林,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工坝橡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泻帮,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓计寇,卻偏偏與公主長得像锣杂,于是被迫代替她去往敵國和親脂倦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容