Android性能優(yōu)化之如何避免Overdraw

什么是Overdraw凸克?

Overdraw就是過(guò)度繪制萎战,是指在一幀的時(shí)間內(nèi)(16.67ms)像素被繪制了多次舆逃,理論上一個(gè)像素每次只繪制一次是最優(yōu)的,但是由于重疊的布局導(dǎo)致一些像素會(huì)被多次繪制路狮,而每次繪制都會(huì)對(duì)應(yīng)到CPU的一組繪圖命令和GPU的一些操作览祖,當(dāng)這個(gè)操作耗時(shí)超過(guò)16.67ms時(shí),就會(huì)出現(xiàn)掉幀現(xiàn)象展蒂,也就是我們所說(shuō)的卡頓,所以對(duì)重疊不可見元素的重復(fù)繪制會(huì)產(chǎn)生額外的開銷柳骄,需要盡量減少Overdraw的發(fā)生箕般。
Android提供了測(cè)量Overdraw的選項(xiàng),在開發(fā)者選項(xiàng)-調(diào)試GPU過(guò)度繪制(Show GPU Overdraw)曲初,打開選項(xiàng)就可以看到當(dāng)前頁(yè)面Overdraw的狀態(tài)杯聚,就可以觀察屏幕的繪制狀態(tài)。該工具會(huì)使用三種不同的顏色繪制屏幕颁褂,來(lái)指示overdraw發(fā)生在哪里以及程度如何傀广,其中:
沒(méi)有顏色: 意味著沒(méi)有overdraw。像素只畫了一次伪冰。
藍(lán)色: 意味著overdraw 1倍糜值。像素繪制了兩次坯墨。大片的藍(lán)色還是可以接受的(若整個(gè)窗口是藍(lán)色的病往,可以擺脫一層)。
綠色: 意味著overdraw 2倍耍攘。像素繪制了三次畔勤。中等大小的綠色區(qū)域是可以接受的但你應(yīng)該嘗試優(yōu)化、減少它們庆揪。
淺紅: 意味著overdraw 3倍。像素繪制了四次吝羞,小范圍可以接受内颗。
暗紅: 意味著overdraw 4倍。像素繪制了五次或者更多恨溜。這是錯(cuò)誤的找前,要修復(fù)它們。

552dd397d5e53.jpg

那么我們?cè)趺磥?lái)消滅overdraw呢系吭?總的原則就是:盡量避免重疊不可見元素的繪制颗品,基于這個(gè)原則沃缘,我們大概可以想出以下幾招:

第一招:合理選擇控件容器

既然overdraw是因?yàn)橹貜?fù)繪制了同一片區(qū)域的像素點(diǎn),那我們首先想到的是解決布局問(wèn)題锄蹂。Android提供的Layout控件主要包括LinearLayout水慨、TableLayout敬扛、FrameLayout朝抖、RelativeLayout治宣。俗話說(shuō)條條大路通羅馬,同一個(gè)界面我們可以使用不同的容器控件來(lái)表達(dá)侮邀,但是各個(gè)容器控件描述界面的復(fù)雜度是不一樣的。一般來(lái)說(shuō)LinearLayout最易铝宵,RelativeLayout較復(fù)雜华畏。但是尺有所短,寸有所長(zhǎng)拼岳,LinearLayout只能用來(lái)描述一個(gè)方向上連續(xù)排列的控件况芒,而RelativeLayout幾乎可以用于描述任意復(fù)雜度的界面。但是我又要說(shuō)但是了耐版,表達(dá)能力越強(qiáng)的容器控件压汪,性能往往略低一些,因?yàn)橄到y(tǒng)需要將更多的時(shí)間花在計(jì)算子控件的位置上腺阳。綜上所述:LinearLayout易用穿香,效率高,表達(dá)能力有限皮获。RelativeLayout復(fù)雜,表達(dá)能力強(qiáng)购公,效率稍遜。
那么對(duì)于同一界面而言知残,作為開發(fā)者考慮是使用盡量少的绘闷、表達(dá)能力強(qiáng)的RelativeLayout作為容器,還是選擇多個(gè)扒最、表達(dá)能力稍弱的LinearLayout來(lái)展示华嘹。從減少overdraw的角度來(lái)看,LinearLayout會(huì)增加控件數(shù)的層級(jí)强挫,自然是RelativeLayout更優(yōu)薛躬,但是當(dāng)某一界面在使用LinearLayout并不會(huì)比RelativeLayout帶來(lái)更多的控件數(shù)和控件層級(jí)時(shí),LinearLayout則是首選八匠。所以在表達(dá)界面的時(shí)候趴酣,作為一個(gè)有前瞻性的開發(fā)者要根據(jù)實(shí)際情況來(lái)選擇合適容器控件,在保證性能的同時(shí)抡四,盡量避免overdraw仗谆。

第二招:去掉window的默認(rèn)背景

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

getWindow().setBackgroundDrawable(null); 

或者在theme中添加

android:windowbackground="null"京郑;

第三招:去掉其他不必要的背景

有時(shí)候?yàn)榱朔奖銜?huì)先給Layout設(shè)置一個(gè)整體的背景葫掉,再給子View設(shè)置背景,這里也會(huì)造成重疊户魏,如果子View寬度mach_parent挪挤,可以看到完全覆蓋了Layout的一部分匕得,這里就可以通過(guò)分別設(shè)置背景來(lái)減少重繪。再比如如果采用的是selector的背景论寨,將normal狀態(tài)的color設(shè)置為“@android:color/transparent",也同樣可以解決問(wèn)題。這里只簡(jiǎn)單舉兩個(gè)例子贞铣,我們?cè)陂_發(fā)過(guò)程中的一些習(xí)慣性思維定式會(huì)帶來(lái)不經(jīng)意的Overdraw沮明,所以開發(fā)過(guò)程中我們?yōu)槟硞€(gè)View或者ViewGroup設(shè)置背景的時(shí)候,先思考下是否真的有必要酱畅,或者思考下這個(gè)背景能不能分段設(shè)置在子View上江场,而不是圖方便直接設(shè)置在根View上。

第四招:ClipRect & QuickReject

為了解決Overdraw的問(wèn)題址否,Android系統(tǒng)會(huì)通過(guò)避免繪制那些完全不可見的組件來(lái)盡量減少消耗。但是不幸的是樊诺,對(duì)于那些過(guò)于復(fù)雜的自定義的View(通常重寫了onDraw方法)词爬,Android系統(tǒng)無(wú)法檢測(cè)在onDraw里面具體會(huì)執(zhí)行什么操作,系統(tǒng)無(wú)法監(jiān)控并自動(dòng)優(yōu)化顿膨,也就無(wú)法避免Overdraw了恋沃。但是我們可以通過(guò)canvas.clipRect()來(lái)幫助系統(tǒng)識(shí)別那些可見的區(qū)域。這個(gè)方法可以指定一塊矩形區(qū)域芽唇,只有在這個(gè)區(qū)域內(nèi)才會(huì)被繪制匆笤,其他的區(qū)域會(huì)被忽視。這個(gè)API可以很好的幫助那些有多組重疊組件的自定義View來(lái)控制顯示的區(qū)域炮捧。同時(shí)clipRect方法還可以幫助節(jié)約CPU與GPU資源,在clipRect區(qū)域之外的繪制指令都不會(huì)被執(zhí)行咆课,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件末誓,仍然會(huì)得到繪制。除了clipRect方法之外书蚪,我們還可以使用canvas.quickreject()來(lái)判斷是否沒(méi)和某個(gè)矩形相交喇澡,從而跳過(guò)那些非矩形區(qū)域內(nèi)的繪制操作。

第五招:ViewStub

ViewStub是個(gè)什么東西殊校?一句話總結(jié):高效占位符晴玖。
我們經(jīng)常會(huì)遇到這樣的情況,運(yùn)行時(shí)動(dòng)態(tài)根據(jù)條件來(lái)決定顯示哪個(gè)View或布局为流。常用的做法是把View都寫在上面呕屎,先把它們的可見性都設(shè)為View.GONE敬察,然后在代碼中動(dòng)態(tài)的更改它的可見性秀睛。這樣的做法的優(yōu)點(diǎn)是邏輯簡(jiǎn)單而且控制起來(lái)比較靈活。但是它的缺點(diǎn)就是莲祸,耗費(fèi)資源蹂安。雖然把View的初始可見View.GONE但是在Inflate布局的時(shí)候View仍然會(huì)被Inflate椭迎,也就是說(shuō)仍然會(huì)創(chuàng)建對(duì)象,會(huì)被實(shí)例化田盈,會(huì)被設(shè)置屬性侠碧。也就是說(shuō),會(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來(lái)方便的在運(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();

第六招:Merge

Merge標(biāo)簽有什么用呢期虾?簡(jiǎn)單粗暴點(diǎn)回答:干掉一個(gè)view層級(jí)。
Merge的作用很明顯驯嘱,但是也有一些使用條件的限制镶苞。有兩種情況下我們可以使用Merge標(biāo)簽來(lái)做容器控件。第一種子視圖不需要指定任何針對(duì)父視圖的布局屬性鞠评,就是說(shuō)父容器僅僅是個(gè)容器茂蚓,子視圖只需要直接添加到父視圖上用于顯示就行。另外一種是假如需要在LinearLayout里面嵌入一個(gè)布局(或者視圖)谢澈,而恰恰這個(gè)布局(或者視圖)的根節(jié)點(diǎn)也是LinearLayout煌贴,這樣就多了一層沒(méi)有用的嵌套,無(wú)疑這樣只會(huì)拖慢程序速度锥忿。而這個(gè)時(shí)候如果我們使用merge根標(biāo)簽就可以避免那樣的問(wèn)題牛郑。另外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加載到頁(yè)面中,布局層級(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>

第七招:善用draw9patch

給ImageView加一個(gè)邊框惶楼,你肯定遇到過(guò)這種需求,通常在ImageView后面設(shè)置一張背景圖诊杆,露出邊框便完美解決問(wèn)題歼捐,此時(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刽锤。

第八招:慎用Alpha

假如對(duì)一個(gè)View做Alpha轉(zhuǎn)化,需要先將View繪制出來(lái)朦佩,然后做Alpha轉(zhuǎn)化并思,最后將轉(zhuǎn)換后的效果繪制在界面上。通俗點(diǎn)說(shuō)语稠,做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);

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

第九招:避免“OverDesign”

overdraw會(huì)給APP帶來(lái)不好的體驗(yàn),overdraw產(chǎn)生的原因無(wú)外乎:復(fù)雜的Layout層級(jí)乃正,重疊的View住册,重疊的背景這幾種。開發(fā)人員無(wú)節(jié)制的View堆砌瓮具,究其根本無(wú)非是產(chǎn)品無(wú)節(jié)制的需求設(shè)計(jì)荧飞。有道是“由儉入奢易,由奢入儉難"名党,很多APP披著過(guò)度設(shè)計(jì)的華麗外衣垢箕,卻忘了簡(jiǎn)單易用才是王道的本質(zhì),紛繁復(fù)雜的設(shè)計(jì)并不會(huì)給用戶帶來(lái)好的體驗(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末碧绞,一起剝皮案震驚了整個(gè)濱河市府框,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讥邻,老刑警劉巖迫靖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異兴使,居然都是意外死亡系宜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門发魄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)盹牧,“玉大人,你說(shuō)我怎么就攤上這事励幼√ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵苹粟,是天一觀的道長(zhǎng)有滑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)六水,這世上最難降的妖魔是什么俺孙? 我笑而不...
    開封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任辣卒,我火速辦了婚禮,結(jié)果婚禮上睛榄,老公的妹妹穿的比我還像新娘荣茫。我一直安慰自己,他們只是感情好场靴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開白布啡莉。 她就那樣靜靜地躺著,像睡著了一般旨剥。 火紅的嫁衣襯著肌膚如雪咧欣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天轨帜,我揣著相機(jī)與錄音魄咕,去河邊找鬼。 笑死蚌父,一個(gè)胖子當(dāng)著我的面吹牛哮兰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播苟弛,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼喝滞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了膏秫?” 一聲冷哼從身側(cè)響起右遭,我...
    開封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缤削,沒(méi)想到半個(gè)月后窘哈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亭敢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年宵距,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吨拗。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡满哪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出劝篷,到底是詐尸還是另有隱情哨鸭,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布娇妓,位于F島的核電站像鸡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜只估,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一志群、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛔钙,春花似錦锌云、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至兼贡,卻和暖如春攻冷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遍希。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工等曼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凿蒜。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓涉兽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親篙程。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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