Android UI布局的性能分析和優(yōu)化措施

前言

說到UI布局的性能就不得不提到Overdraw,那么什么是Overdraw波丰?
  
  Overdraw就是過度繪制,是指在一幀的時(shí)間內(nèi)(16.67ms)像素被繪制了多次,理論上一個(gè)像素每次只繪制一次是最優(yōu)的邪狞,但是由于重疊的布局導(dǎo)致一些像素會(huì)被多次繪制,而每次繪制都會(huì)對(duì)應(yīng)到CPU的一組繪圖命令和GPU的一些操作茅撞,當(dāng)這個(gè)操作耗時(shí)超過16.67ms時(shí)帆卓,就會(huì)出現(xiàn)掉幀現(xiàn)象,也就是我們所說的卡頓米丘,所以對(duì)重疊不可見元素的重復(fù)繪制會(huì)產(chǎn)生額外的開銷剑令,需要盡量減少Overdraw的發(fā)生。
Android提供了測(cè)量Overdraw的選項(xiàng)拄查,在開發(fā)者選項(xiàng)-調(diào)試GPU過度繪制(Show GPU Overdraw)吁津,打開選項(xiàng)就可以看到當(dāng)前頁面Overdraw的狀態(tài),就可以觀察屏幕的繪制狀態(tài)堕扶。該工具會(huì)使用三種不同的顏色繪制屏幕碍脏,來指示overdraw發(fā)生在哪里以及程度如何,其中:
沒有顏色: 意味著沒有overdraw稍算。像素只畫了一次典尾。
  藍(lán)色: 意味著overdraw 1倍。像素繪制了兩次糊探。大片的藍(lán)色還是可以接受的(若整個(gè)窗口是藍(lán)色的钾埂,可以擺脫一層)。
  綠色: 意味著overdraw 2倍侧到。像素繪制了三次勃教。中等大小的綠色區(qū)域是可以接受的但你應(yīng)該嘗試優(yōu)化、減少它們匠抗。
  淺紅: 意味著overdraw 3倍故源。像素繪制了四次,小范圍可以接受汞贸。
  暗紅: 意味著overdraw 4倍绳军。像素繪制了五次或者更多。這是錯(cuò)誤的矢腻,要修復(fù)它們门驾。

image.png

那么我們?cè)趺磥硐麥鏾verdraw呢?總的原則就是:盡量避免重疊不可見元素的繪制多柑,基于這個(gè)原則奶是,大致有以下幾種方法:

1.RelativeLayout優(yōu)先于LinearLayout

這里有一個(gè)數(shù)據(jù)可以展示:
  Hierarchy Viewer通過讓你選擇一個(gè)在連接的設(shè)備或模擬器上的進(jìn)程來工作,然后顯示布局樹。每一個(gè)塊上的紅綠燈展示了測(cè)量聂沙,布局秆麸,繪畫的性能,幫你鑒別潛在問題及汉。
可以很清楚地看到這個(gè)item花了多少時(shí)間測(cè)量沮趣,布局,繪畫坷随,你該在哪里花時(shí)間去優(yōu)化房铭。

LinearLayout布局
使用這個(gè)布局表現(xiàn)一個(gè)完整的list item,花費(fèi)的時(shí)間:
測(cè)量: 0.977ms
布局: 0.167ms
繪畫: 2.717ms

這里寫圖片描述

RelativeLayout布局
因?yàn)樯厦娴那短譒inearLayout影響了性能温眉,可以通過RelativeLayout減少層次來提高性能缸匪。
Hierarchy relativelayout
使用RelativeLayout修改后,只有兩層芍殖。
修改后花費(fèi)的時(shí)間:
測(cè)量: 0.598ms
布局: 0.110ms
繪畫: 2.146ms


這里寫圖片描述

達(dá)到UI的效果
可以看到有一些小提升豪嗽,但list里有多個(gè)item,所以優(yōu)化減少的時(shí)候會(huì)是加N倍的谴蔑。
由于使用LinearLayout的layout_weight,大多數(shù)時(shí)間是不一樣的豌骏,這會(huì)降低測(cè)量的速度。這只是一個(gè)如何合理使用Layout的案例隐锭,必要的時(shí)候窃躲,你要小心考慮是否用layout weight

這里寫圖片描述

  
  那么對(duì)于同一界面而言,作為開發(fā)者考慮是使用盡量少的钦睡、表達(dá)能力強(qiáng)的RelativeLayout作為容器蒂窒,還是選擇多個(gè)、表達(dá)能力稍弱的LinearLayout來展示荞怒。從減少overdraw的角度來看洒琢,LinearLayout會(huì)增加控件數(shù)的層級(jí),自然是RelativeLayout更優(yōu)褐桌,但是當(dāng)某一界面在使用LinearLayout并不會(huì)比RelativeLayout帶來更多的控件數(shù)和控件層級(jí)時(shí)衰抑,LinearLayout則是首選。所以在表達(dá)界面的時(shí)候荧嵌,作為一個(gè)有前瞻性的開發(fā)者要根據(jù)實(shí)際情況來選擇合適容器控件呛踊,在保證性能的同時(shí),盡量避免overdraw啦撮。

2.去掉window的默認(rèn)背景

當(dāng)我們使用了Android自帶的一些主題時(shí)谭网,window會(huì)被默認(rèn)添加一個(gè)純色的背景,這個(gè)背景是被DecorView持有的赃春。當(dāng)我們的自定義布局時(shí)又添加了一張背景圖或者設(shè)置背景色愉择,那么DecorView的background此時(shí)對(duì)我們來說是無用的,但是它會(huì)產(chǎn)生一次Overdraw,帶來繪制性能損耗锥涕。
去掉window的背景可以在onCreate()中setContentView()之后調(diào)用

getWindow().setBackgroundDrawable(null); 

或者在theme中添加

android:windowbackground="null"要拂;

3.去掉其他不必要的背景

有時(shí)候?yàn)榱朔奖銜?huì)先給Layout設(shè)置一個(gè)整體的背景,再給子View設(shè)置背景站楚,這里也會(huì)造成重疊脱惰,如果子View寬度mach_parent,可以看到完全覆蓋了Layout的一部分窿春,這里就可以通過分別設(shè)置背景來減少重繪拉一。再比如如果采用的是selector的背景,將normal狀態(tài)的color設(shè)置為“@android:color/transparent",也同樣可以解決問題旧乞。這里只簡(jiǎn)單舉兩個(gè)例子蔚润,我們?cè)陂_發(fā)過程中的一些習(xí)慣性思維定式會(huì)帶來不經(jīng)意的Overdraw,所以開發(fā)過程中我們?yōu)槟硞€(gè)View或者ViewGroup設(shè)置背景的時(shí)候尺栖,先思考下是否真的有必要嫡纠,或者思考下這個(gè)背景能不能分段設(shè)置在子View上,而不是圖方便直接設(shè)置在根View上延赌。

4.ClipRect & QuickReject

為了解決Overdraw的問題除盏,Android系統(tǒng)會(huì)通過避免繪制那些完全不可見的組件來盡量減少消耗。但是不幸的是挫以,對(duì)于那些過于復(fù)雜的自定義的View(通常重寫了onDraw方法)者蠕,Android系統(tǒng)無法檢測(cè)在onDraw里面具體會(huì)執(zhí)行什么操作,系統(tǒng)無法監(jiān)控并自動(dòng)優(yōu)化掐松,也就無法避免Overdraw了踱侣。但是我們可以通過canvas.clipRect()來幫助系統(tǒng)識(shí)別那些可見的區(qū)域。這個(gè)方法可以指定一塊矩形區(qū)域大磺,只有在這個(gè)區(qū)域內(nèi)才會(huì)被繪制抡句,其他的區(qū)域會(huì)被忽視。這個(gè)API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區(qū)域杠愧。同時(shí)clipRect方法還可以幫助節(jié)約CPU與GPU資源待榔,在clipRect區(qū)域之外的繪制指令都不會(huì)被執(zhí)行,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件殴蹄,仍然會(huì)得到繪制究抓。除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個(gè)矩形相交袭灯,從而跳過那些非矩形區(qū)域內(nèi)的繪制操作刺下。

5.使用ViewStub

ViewStub是個(gè)什么東西?一句話總結(jié):高效占位符稽荧。
  我們經(jīng)常會(huì)遇到這樣的情況橘茉,運(yùn)行時(shí)動(dòng)態(tài)根據(jù)條件來決定顯示哪個(gè)View或布局工腋。常用的做法是把View都寫在上面,先把它們的可見性都設(shè)為View.GONE畅卓,然后在代碼中動(dòng)態(tài)的更改它的可見性擅腰。這樣的做法的優(yōu)點(diǎn)是邏輯簡(jiǎn)單而且控制起來比較靈活。但是它的缺點(diǎn)就是翁潘,耗費(fèi)資源趁冈。雖然把View的初始可見View.GONE但是在Inflate布局的時(shí)候View仍然會(huì)被Inflate,也就是說仍然會(huì)創(chuàng)建對(duì)象拜马,會(huì)被實(shí)例化渗勘,會(huì)被設(shè)置屬性。也就是說俩莽,會(huì)耗費(fèi)內(nèi)存等資源旺坠。
  推薦的做法是使用android.view.ViewStub,ViewStub是一個(gè)輕量級(jí)的View扮超,它一個(gè)看不見的取刃,不占布局位置,占用資源非常小的控件出刷¤盗疲可以為ViewStub指定一個(gè)布局,在Inflate布局的時(shí)候巷蚪,只有ViewStub會(huì)被初始化病毡,然后當(dāng)ViewStub被設(shè)置為可見的時(shí)候濒翻,或是調(diào)用了ViewStub.inflate()的時(shí)候屁柏,ViewStub所向的布局就會(huì)被Inflate和實(shí)例化,然后ViewStub的布局屬性都會(huì)傳給它所指向的布局有送。這樣淌喻,就可以使用ViewStub來方便的在運(yùn)行時(shí),要還是不要顯示某個(gè)布局雀摘。

<ViewStub
    android:id="@+id/stub_view"
    android:inflatedId="@+id/panel_stub"
    android:layout="@layout/progress_overlay"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />

當(dāng)你想加載布局時(shí)裸删,可以使用下面其中一種方法:

(ViewStub) findViewById(R.id.stub_view)).setVisibility(View.VISIBLE);
View importPanel = ((ViewStub) findViewById(R.id.stub_view)).inflate();

6.使用Merge

Merge標(biāo)簽有什么用呢?簡(jiǎn)單粗暴點(diǎn)回答:干掉一個(gè)view層級(jí)阵赠。
  Merge的作用很明顯涯塔,但是也有一些使用條件的限制。有兩種情況下我們可以使用Merge標(biāo)簽來做容器控件清蚀。第一種子視圖不需要指定任何針對(duì)父視圖的布局屬性匕荸,就是說父容器僅僅是個(gè)容器,子視圖只需要直接添加到父視圖上用于顯示就行枷邪。另外一種是假如需要在LinearLayout里面嵌入一個(gè)布局(或者視圖)榛搔,而恰恰這個(gè)布局(或者視圖)的根節(jié)點(diǎn)也是LinearLayout,這樣就多了一層沒有用的嵌套,無疑這樣只會(huì)拖慢程序速度践惑。而這個(gè)時(shí)候如果我們使用merge根標(biāo)簽就可以避免那樣的問題腹泌。另外Merge只能作為XML布局的根標(biāo)簽使用,當(dāng)Inflate以merge 開頭的布局文件時(shí)尔觉,必須指定一個(gè)父ViewGroup凉袱,并且必須設(shè)定attachToRoot為true。
舉個(gè)簡(jiǎn)單的例子吧:

<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" > 
<TextView 
android:layout_width="wrap_content"  
android:layout_height="wrap_content" 
android:text="merge標(biāo)簽使用" />
</RelativeLayout>

把上面這個(gè)XML加載到頁面中侦铜,布局層級(jí)是RelativeLayout-TextView绑蔫。但是采用下面的方式,把RelativeLayout提換成merge泵额,RelativeLayout這一層級(jí)就被干掉了配深。

<merge
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" >
<TextView  
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
 android:text="merge標(biāo)簽使用" />
</merge>

7.善用draw9patch

給ImageView加一個(gè)邊框,你肯定遇到過這種需求嫁盲,通常在ImageView后面設(shè)置一張背景圖篓叶,露出邊框便完美解決問題,此時(shí)這個(gè)ImageView羞秤,設(shè)置了兩層drawable缸托,底下一層僅僅是為了作為圖片的邊框而已。但是兩層drawable的重疊區(qū)域去繪制了兩次瘾蛋,導(dǎo)致overdraw俐镐。
優(yōu)化方案: 將背景drawable制作成draw9patch,并且將和前景重疊的部分設(shè)置為透明哺哼。由于Android的2D渲染器會(huì)優(yōu)化draw9patch中的透明區(qū)域佩抹,從而優(yōu)化了這次overdraw。 但是背景圖片必須制作成draw9patch才行取董,因?yàn)锳ndroid 2D渲染器只對(duì)draw9patch有這個(gè)優(yōu)化棍苹,否則,一張普通的Png茵汰,就算你把中間的部分設(shè)置成透明枢里,也不會(huì)減少這次overdraw。

8.慎用Alpha

假如對(duì)一個(gè)View做Alpha轉(zhuǎn)化蹂午,需要先將View繪制出來栏豺,然后做Alpha轉(zhuǎn)化,最后將轉(zhuǎn)換后的效果繪制在界面上豆胸。通俗點(diǎn)說奥洼,做Alpha轉(zhuǎn)化就需要對(duì)當(dāng)前View繪制兩遍,可想而知配乱,繪制效率會(huì)大打折扣溉卓,耗時(shí)會(huì)翻倍皮迟,所以Alpha還是慎用。
  如果一定做Alpha轉(zhuǎn)化的話桑寨,可以采用緩存的方式伏尼。

view.setLayerType(LAYER_TYPE_HARDWARE);
doSmoeThing();
view.setLayerType(LAYER_TYPE_NONE);

通過setLayerType方式可以將當(dāng)前界面緩存在GPU中,這樣不需要每次繪制原始界面尉尾,但是GPU內(nèi)存是相當(dāng)寶貴的爆阶,所以用完要馬上釋放掉。

9.避免“OverDesign”

overdraw會(huì)給APP帶來不好的體驗(yàn)沙咏,overdraw產(chǎn)生的原因無外乎:復(fù)雜的Layout層級(jí)辨图,重疊的View,重疊的背景這幾種肢藐。開發(fā)人員無節(jié)制的View堆砌故河,究其根本無非是產(chǎn)品無節(jié)制的需求設(shè)計(jì)。有道是“由儉入奢易吆豹,由奢入儉難"鱼的,很多APP披著過度設(shè)計(jì)的華麗外衣,卻忘了簡(jiǎn)單易用才是王道的本質(zhì)痘煤,紛繁復(fù)雜的設(shè)計(jì)并不會(huì)給用戶帶來好的體驗(yàn)凑阶,反而會(huì)讓用戶有壓迫感,產(chǎn)品本身也有可能因此變得卡頓衷快。當(dāng)然宙橱,一切拋開業(yè)務(wù)談優(yōu)化都是空中樓閣,這就需要產(chǎn)品設(shè)計(jì)也要有一個(gè)權(quán)衡蘸拔,在復(fù)雜的業(yè)務(wù)邏輯與簡(jiǎn)單易用的界面展現(xiàn)中做一個(gè)平衡师郑,而不是一味的OverDesign。

10.使用include標(biāo)簽

可能有的人說include并不能減少overdraw啊都伪,但是他能給打UI布局很大的優(yōu)化呕乎,增加復(fù)用性,可以靈活的修改布局陨晶,所以這兒就把它也加了進(jìn)來,實(shí)際開發(fā)中可以多使用include標(biāo)簽帝璧。在以前Android開發(fā)中先誉,由于ActionBar設(shè)計(jì)上的不統(tǒng)一以及兼容性問題,所以很多應(yīng)用都自定義了一套自己的標(biāo)題欄titlebar的烁。標(biāo)題欄我們知道在應(yīng)用的每個(gè)界面幾乎都會(huì)用到褐耳,在這里可以作為一個(gè)很好的示例來解釋include標(biāo)簽的使用。
下面是一個(gè)自定義的titlebar文件:

<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/titlebar_bg">
 
    <ImageViewandroid:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:src="@drawable/gafricalogo"/>
</FrameLayout>

在應(yīng)用中使用titlebar布局文件渴庆,我們通過include標(biāo)簽,布局文件如下:

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:gravity="center_horizontal">
 
    <includelayout="@layout/titlebar"/>
 
    <TextViewandroid:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp"/>
 
</LinearLayout>

在include標(biāo)簽中可以覆蓋導(dǎo)入的布局文件root布局的布局屬性(如layout_*屬性)铃芦。

布局示例如下:

<includeandroid:id="@+id/news_title"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        layout="@layout/title"/>

如果想使用include標(biāo)簽覆蓋嵌入布局root布局屬性雅镊,必須同時(shí)覆蓋layout_height和layout_width屬性,否則會(huì)直接報(bào)編譯時(shí)語法錯(cuò)誤刃滓。
  如果include標(biāo)簽已經(jīng)定義了id仁烹,而嵌入布局文件的root布局文件也定義了id,includ標(biāo)簽的id會(huì)覆蓋掉嵌入布局文件root的id咧虎,如果include標(biāo)簽沒有定義id則會(huì)使用嵌入文件root的id卓缰。

總結(jié)

以上除了4,5,9三點(diǎn)基本都是常用的優(yōu)化規(guī)則,在實(shí)際開發(fā)中可以在開發(fā)者模式中打開GPU過度繪制設(shè)置為現(xiàn)實(shí)過渡繪制區(qū)域砰诵,然后對(duì)比圖一就行優(yōu)化征唬,一般顏色現(xiàn)實(shí)暗紅都可以接受
  
  在此特別感謝以下鏈接:
  https://www.pocketdigi.com/20140609/1347.html
  http://www.reibang.com/p/145fc61011cd

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市茁彭,隨后出現(xiàn)的幾起案子总寒,更是在濱河造成了極大的恐慌,老刑警劉巖理肺,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偿乖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哲嘲,警方通過查閱死者的電腦和手機(jī)贪薪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眠副,“玉大人画切,你說我怎么就攤上這事〈雅拢” “怎么了霍弹?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)娃弓。 經(jīng)常有香客問我典格,道長(zhǎng),這世上最難降的妖魔是什么台丛? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任耍缴,我火速辦了婚禮,結(jié)果婚禮上挽霉,老公的妹妹穿的比我還像新娘防嗡。我一直安慰自己,他們只是感情好侠坎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布蚁趁。 她就那樣靜靜地躺著,像睡著了一般实胸。 火紅的嫁衣襯著肌膚如雪他嫡。 梳的紋絲不亂的頭發(fā)上番官,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音钢属,去河邊找鬼徘熔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛署咽,可吹牛的內(nèi)容都是我干的近顷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼宁否,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼窒升!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起慕匠,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤饱须,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后台谊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓉媳,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年锅铅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酪呻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盐须,死狀恐怖玩荠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贼邓,我是刑警寧澤阶冈,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站塑径,受9級(jí)特大地震影響女坑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜统舀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一匆骗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绑咱,春花似錦绰筛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衡蚂。三九已至窿克,卻和暖如春骏庸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背年叮。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工具被, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人只损。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓一姿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親跃惫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叮叹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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