1. 程式人生 > >Android解決卡頓,從避免Overdraw開始

Android解決卡頓,從避免Overdraw開始

什麼是Overdraw?

Overdraw就是過度繪製,是指在一幀的時間內(16.67ms)畫素被繪製了多次,理論上一個畫素每次只繪製一次是最優的,但是由於重疊的佈局導致一些畫素會被多次繪製,而每次繪製都會對應到CPU的一組繪圖命令和GPU的一些操作,當這個操作耗時超過16.67ms時,就會出現掉幀現象,也就是我們所說的卡頓,所以對重疊不可見元素的重複繪製會產生額外的開銷,需要儘量減少Overdraw的發生。
Android提供了測量Overdraw的選項,在開發者選項-除錯GPU過度繪製(Show GPU Overdraw),開啟選項就可以看到當前頁面Overdraw的狀態,就可以觀察螢幕的繪製狀態。該工具會使用三種不同的顏色繪製螢幕,來指示overdraw發生在哪裡以及程度如何,其中:
沒有顏色: 意味著沒有overdraw。畫素只畫了一次。
藍色: 意味著overdraw 1倍。畫素繪製了兩次。大片的藍色還是可以接受的(若整個視窗是藍色的,可以擺脫一層)。
綠色: 意味著overdraw 2倍。畫素繪製了三次。中等大小的綠色區域是可以接受的但你應該嘗試優化、減少它們。
淺紅: 意味著overdraw 3倍。畫素繪製了四次,小範圍可以接受。
暗紅: 意味著overdraw 4倍。畫素繪製了五次或者更多。這是錯誤的,要修復它們。

那麼我們怎麼來消滅overdraw呢?總的原則就是:儘量避免重疊不可見元素的繪製,基於這個原則,我們大概可以想出以下幾招:

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

既然overdraw是因為重複繪製了同一片區域的畫素點,那我們首先想到的是解決佈局問題。Android提供的Layout控制元件主要包括LinearLayout、TableLayout、FrameLayout、RelativeLayout。俗話說條條大路通羅馬,同一個介面我們可以使用不同的容器控制元件來表達,但是各個容器控制元件描述介面的複雜度是不一樣的。一般來說LinearLayout最易,RelativeLayout較複雜。但是尺有所短,寸有所長,LinearLayout只能用來描述一個方向上連續排列的控制元件,而RelativeLayout幾乎可以用於描述任意複雜度的介面。但是我又要說但是了,表達能力越強的容器控制元件,效能往往略低一些,因為系統需要將更多的時間花在計運算元控制元件的位置上。綜上所述:LinearLayout易用,效率高,表達能力有限。RelativeLayout複雜,表達能力強,效率稍遜。


那麼對於同一介面而言,作為開發者考慮是使用盡量少的、表達能力強的RelativeLayout作為容器,還是選擇多個、表達能力稍弱的LinearLayout來展示。從減少overdraw的角度來看,LinearLayout會增加控制元件數的層級,自然是RelativeLayout更優,但是當某一介面在使用LinearLayout並不會比RelativeLayout帶來更多的控制元件數和控制元件層級時,LinearLayout則是首選。所以在表達介面的時候,作為一個有前瞻性的開發者要根據實際情況來選擇合適容器控制元件,在保證效能的同時,儘量避免overdraw。

第二招:去掉window的預設背景

當我們使用了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上。

第四招:ClipRect & QuickReject

為了解決Overdraw的問題,Android系統會通過避免繪製那些完全不可見的元件來儘量減少消耗。但是不幸的是,對於那些過於複雜的自定義的View(通常重寫了onDraw方法),Android系統無法檢測在onDraw裡面具體會執行什麼操作,系統無法監控並自動優化,也就無法避免Overdraw了。但是我們可以通過canvas.clipRect()來幫助系統識別那些可見的區域。這個方法可以指定一塊矩形區域,只有在這個區域內才會被繪製,其他的區域會被忽視。這個API可以很好的幫助那些有多組重疊元件的自定義View來控制顯示的區域。同時clipRect方法還可以幫助節約CPU與GPU資源,在clipRect區域之外的繪製指令都不會被執行,那些部分內容在矩形區域內的元件,仍然會得到繪製。除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形區域內的繪製操作。

第五招:ViewStub

ViewStub是個什麼東西?一句話總結:高效佔位符。
我們經常會遇到這樣的情況,執行時動態根據條件來決定顯示哪個View或佈局。常用的做法是把View都寫在上面,先把它們的可見性都設為View.GONE,然後在程式碼中動態的更改它的可見性。這樣的做法的優點是邏輯簡單而且控制起來比較靈活。但是它的缺點就是,耗費資源。雖然把View的初始可見View.GONE但是在Inflate佈局的時候View仍然會被Inflate,也就是說仍然會建立物件,會被例項化,會被設定屬性。也就是說,會耗費記憶體等資源。
推薦的做法是使用android.view.ViewStub,ViewStub是一個輕量級的View,它一個看不見的,不佔佈局位置,佔用資源非常小的控制元件。可以為ViewStub指定一個佈局,在Inflate佈局的時候,只有ViewStub會被初始化,然後當ViewStub被設定為可見的時候,或是呼叫了ViewStub.inflate()的時候,ViewStub所向的佈局就會被Inflate和例項化,然後ViewStub的佈局屬性都會傳給它所指向的佈局。這樣,就可以使用ViewStub來方便的在執行時,要還是不要顯示某個佈局。

<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" />

當你想載入佈局時,可以使用下面其中一種方法:

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

第六招:Merge

Merge標籤有什麼用呢?簡單粗暴點回答:幹掉一個view層級。
Merge的作用很明顯,但是也有一些使用條件的限制。有兩種情況下我們可以使用Merge標籤來做容器控制元件。第一種子檢視不需要指定任何針對父檢視的佈局屬性,就是說父容器僅僅是個容器,子檢視只需要直接新增到父檢視上用於顯示就行。另外一種是假如需要在LinearLayout裡面嵌入一個佈局(或者檢視),而恰恰這個佈局(或者檢視)的根節點也是LinearLayout,這樣就多了一層沒有用的巢狀,無疑這樣只會拖慢程式速度。而這個時候如果我們使用merge根標籤就可以避免那樣的問題。另外Merge只能作為XML佈局的根標籤使用,當Inflate以開頭的佈局檔案時,必須指定一個父ViewGroup,並且必須設定attachToRoot為true。
舉個簡單的例子吧:

<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標籤使用" />
</RelativeLayout>

把上面這個XML載入到頁面中,佈局層級是RelativeLayout-TextView。但是採用下面的方式,把RelativeLayout提換成merge,RelativeLayout這一層級就被幹掉了。

<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標籤使用" />
</merge>

第七招:善用draw9patch

給ImageView加一個邊框,你肯定遇到過這種需求,通常在ImageView後面設定一張背景圖,露出邊框便完美解決問題,此時這個ImageView,設定了兩層drawable,底下一層僅僅是為了作為圖片的邊框而已。但是兩層drawable的重疊區域去繪製了兩次,導致overdraw。
優化方案: 將背景drawable製作成draw9patch,並且將和前景重疊的部分設定為透明。由於Android的2D渲染器會優化draw9patch中的透明區域,從而優化了這次overdraw。 但是背景圖片必須製作成draw9patch才行,因為Android 2D渲染器只對draw9patch有這個優化,否則,一張普通的Png,就算你把中間的部分設定成透明,也不會減少這次overdraw。

第八招:慎用Alpha

假如對一個View做Alpha轉化,需要先將View繪製出來,然後做Alpha轉化,最後將轉換後的效果繪製在介面上。通俗點說,做Alpha轉化就需要對當前View繪製兩遍,可想而知,繪製效率會大打折扣,耗時會翻倍,所以Alpha還是慎用。
如果一定做Alpha轉化的話,可以採用快取的方式。

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

通過setLayerType方式可以將當前介面快取在GPU中,這樣不需要每次繪製原始介面,但是GPU記憶體是相當寶貴的,所以用完要馬上釋放掉。

第九招:避免“OverDesign”

overdraw會給APP帶來不好的體驗,overdraw產生的原因無外乎:複雜的Layout層級,重疊的View,重疊的背景這幾種。開發人員無節制的View堆砌,究其根本無非是產品無節制的需求設計。有道是“由儉入奢易,由奢入儉難”,很多APP披著過度設計的華麗外衣,卻忘了簡單易用才是王道的本質,紛繁複雜的設計並不會給使用者帶來好的體驗,反而會讓使用者有壓迫感,產品本身也有可能因此變得卡頓。當然,一切拋開業務談優化都是空中樓閣,這就需要產品設計也要有一個權衡,在複雜的業務邏輯與簡單易用的介面展現中做一個平衡,而不是一味的OverDesign。

本文轉載,原文資訊如下:
作者:尹star
連結:https://www.jianshu.com/p/145fc61011cd
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

相關推薦

Android解決避免Overdraw開始

什麼是Overdraw? Overdraw就是過度繪製,是指在一幀的時間內(16.67ms)畫素被繪製了多次,理論上一個畫素每次只繪製一次是最優的,但是由於重疊的佈局導致一些畫素會被多次繪製,而每次繪製都會對應到CPU的一組繪圖命令和GPU的一些操作,當這個操

Android螢幕電量損耗和Service常駐小結

一、螢幕出現卡頓的原因: 表面原因: 1、在UI主執行緒中執行比如像網路下載等耗時的操作,致使CPU沒有能力在16ms內完成對下一幀顯示資料的處理 2、需要顯示的介面太過複雜,比如佈局的層次較深,介面控制元件過多等,給CPU與GPU的渲染造成壓力 3、手

Eclipse內存猛增解決方案

char 快速 運行 內存 args maxperm snat linking handle 本文轉載自http://rsy.iteye.com/blog/2095668/ PS:所有校驗都去除後,對如下版本來說,內存一直猛增,解決辦法參照上放博客:修改項目的.proje

webstorm配置內存參數解決

storm exe orm ons 更改 目錄 找到 clas blog 找到WebStorm.exe.vmoptions這個文件,路徑如下webstorm安裝主目錄>bin>WebStorm.exe.vmoptions更改為第二行:-Xms526m第三行:-X

Android App解決慢之內存抖動及內存泄漏(發現和定位)

頻率 其他 直觀 工具使用 nts and article 退出 大小 內存抖動是指在短時間內有大量的對象被創建或者被回收的現象,內存抖動出現原因主要是頻繁(很重要)在循環裏創建對象(導致大量對象在短時間內被創建,由於新對象是要占用內存空間的而且是頻繁,如果一次或者兩次在

Android Stuido解決方法

修改Android studio的安裝目錄下bin/studio.vmoptions和studio64.vmoptions 兩個檔案的以下屬性就可以在記憶體配置比較大的電腦上有效減少Android Stuido卡頓現象,多開也不怎麼卡了 -Xms2048m -X

解決虛擬機器內伺服器不流暢問題

1   右鍵虛擬機器設定->硬體->處理器->虛擬化引擎:選擇虛擬化Intel..../AMD...那個,下面也勾選第二個類似的。然後跑起來立馬飛快       vmware虛擬機器如何設定不當的話會造成執行速度慢,

Android Studio 問題解決方案

先說說我的電腦配置: CPU-i7低壓 記憶體-8G 硬碟-機械500G 相信大部分人初次使用AS都會遇到一個頭疼的問題:卡頓-執行慢 網上有很多解決方案,目前本人親測行之有效果的方案如下:

android系統性能優化(63)---Android APP 問題分析及解決方案

使用者對卡頓的感知, 主要來源於介面的重新整理. 而介面的效能主要是依賴於裝置的UI渲染效能. 如果我們的UI設計過於複雜, 或是實現不夠友好,計算繪製演算法不夠優化, 裝置又不給力, 介面就會像卡住了一樣, 給使用者卡頓的感覺.如果你的應用介面出現卡頓不流暢的情況,不用懷疑,這很大原因是你沒有在16ms完成

LG G3升級Android 6.0 Marshmallow方法親測!介面渲染精美拒絕提升續航!

14年入手的國行LG G3 d857機子,一直很喜歡LG的knock code和簡潔的流線型外觀,周身無按鍵的和背部按鍵的設計確實提升了操作體驗。去年LG  OTA推送了Android 5.0 Lollipop果斷更新之,但用後發現問題多多:手機發熱、耗電嚴重、電話程式卡頓偶

Android APP 問題分析及解決方案

使用者對卡頓的感知, 主要來源於介面的重新整理. 而介面的效能主要是依賴於裝置的UI渲染效能. 如果我們的UI設計過於複雜, 或是實現不夠友好,計算繪製演算法不夠優化, 裝置又不給力, 介面就會像卡住了一樣, 給使用者卡頓的感覺. 如果你的應用介面出現卡頓不流

Android App解決慢之記憶體抖動及記憶體洩漏(發現和定位)

記憶體抖動是指在短時間內有大量的物件被建立或者被回收的現象,記憶體抖動出現原因主要是頻繁(很重要)在迴圈裡建立物件(導致大量物件在短時間內被建立,由於新物件是要佔用記憶體空間的而且是頻繁,如果一次或者兩次在迴圈裡建立物件對記憶體影響不大,不會造成嚴重記憶體抖動這樣可以接受也

聊聊 鍵盤長按將解決方案

itl lang false doctype ctype 如何 true etl osi 當我們做遊戲的時候,很多時候遇用到鍵盤事件長按的的時候第一下卡頓的情況,怎麽解決呢? 以下是我的解決方案,建議復制到編輯器上直接調試 代碼裏面有我思考的過程,大家可以參考   思考

android 動畫分析工具

android 動畫卡頓分析工具     Android應用效能優化之分析工具   上一次記錄瞭解決過度繪製的過程,這一次,想先弄清個概念性的東西,就是如何判斷順不順暢?   這東西其實最初我自己也覺得有點廢話,用起來會卡就明顯是不順暢咯。   但這東西就跟我很想吐槽很

移動swiper中動畫通過requestAnimationFrame寫向下的動畫

當手機網頁使用swiper的時候,在css中寫入動畫的時候有些時候瀏覽頁面會進行卡頓,這個時候你就需要手寫動畫了,以下為參考的一些的案例,大家可以用來參考 setT(); var dataSet = 0, dataStop = 0.35, dataSetT = 7; fun

VS Code 使用者設定 解決問題

{     "git.ignoreLegacyWarning": true,     "editor.fontSize": 12,     "editor.minimap.enabled":      true

android檢測問題,recycleview

recycleview雙列表聯動卡頓問題一直沒解決,今天找了個檢測卡頓原因的神器,附上地址 https://github.com/markzhai/AndroidPerformanceMonitor 整合步驟先簡單, 1.加依賴 compile 'com.github.mar

原 NestedScrollview 巢狀 RecyclerView 滑動ScrollView 巢狀 RecyclerView 衝

   用 ScrollView 巢狀 RecyclerView 的時候會出現 衝突的情況,這個時候,很多人都知道要使用 NestedScrollView來替換 ScrollView... 但是,當NestedScrollview 巢狀 RecyclerVi

BOSE QC35 藍芽斷斷續續問題

在apple store買了一個BOSE QC35,剛收到貨用iPhone7聽歌是沒有問題的,播放器是Apple Music,晚上回家測試了電視機(型號:Sharp)藍芽連線上,在B站看視訊,很長一段時間會有一點卡頓,問題也不大,以為是電視或者應用的問題。 過

view繪製渲染機制和runloop什麼關係?所謂的列表到底是什麼原因引發的?drawrect方法內為何第一行程式碼總要獲取圖形的上下文?

當在操作 UI 時,比如改變了 Frame、更新了 UIView/CALayer 的層次時,或者手動呼叫了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法後,這個 UIView/CALayer 就被標記為待處理