在Android的開發過程當中,drawing performance每每是咱們最關注也是努力去優化的一個點。而形成drawing perfomance的元兇之一就是overdraw。那麼linux
什麼是overdraw?android
overdraw發生在應用每次請求在其它物體上繪製內容的時候。例如:一個白色背景的窗口,在它上面有一個按鈕。當系統繪製按鈕時,要繪製在已存在的白色背景上,這就是overdraw。app
如何識別overdraw?工具
在Anroid的開發者工具中勾選上Show GPU overdraw。佈局
該工具會使用三種不一樣的顏色繪製屏幕,來指示overdraw發生在哪裏以及程度如何,其中:性能
沒有顏色: 意味着沒有overdraw。像素只畫了一次。測試
藍色: 意味着overdraw 1倍。像素繪製了兩次。大片的藍色仍是能夠接受的(若整個窗口是藍色的,能夠擺脫一層)。優化
綠色: 意味着overdraw 2倍。像素繪製了三次。中等大小的綠色區域是能夠接受的但你應該嘗試優化、減小它們。ui
淺紅: 意味着overdraw 3倍。像素繪製了四次,小範圍能夠接受。spa
暗紅: 意味着overdraw 4倍。像素繪製了五次或者更多。這是錯誤的,要修復它們。
下圖展現了優化前的overdraw:
從上圖中能夠看到,底下的標籤選擇區域基本呈現暗紅,上面的標籤展現區域的文本也是淺紅色。甚至是在繪製內容以前,就有一個1倍overdraw的藍色背景。看來,該界面有大量優化空間。
android自帶工具Hierarchy Viewer。能夠查看整個窗口view樹的層級接口,以及各個節點的繪製時間。
上述的開發者選項中的Show GPU Overdraw只有在4.2以上的Android機器上纔有,對於那些沒有4.2以上機器的同窗(個人小米2s默默流淚),只能使用模擬器。因爲Android自帶的模擬器渣成翔且不支持硬件加速,這裏強烈推薦酷炫屌炸天的第三方模擬器: genymotion,誰用誰知道。tip:在linux上跑該虛擬機尤爲流暢,甚至勝過真機
Trace for OpenGL ES。該工具也是Android自帶的調試工具,已在ADT中集成。它可以以可視化的方式看到底層的openGl ES是如何繪製每一幀的,在調試繪製性能問題時無比強大。以下圖:
左側的列表顯示了底層openGL ES的每一個命令調用,這裏我使用Filter篩選出了全部的繪製命令。右側的底部的FrameSummary顯示了該幀繪製出的界面,而右側頂部的Details區域很是重要,它以可視化的方式顯示了在一幀的繪製過程當中,當前的狀態,能夠配合左側的glDraw命令看到當前界面是怎樣一步步繪製出來的,這樣,很容易就能看出哪些像素被重畫了。
merge標籤是用來減小視圖層級結構的一種優化手段。就我目前的總結,較容易出現而且須要使用<merge>標籤來優化的是如下兩種狀況:
咱們activity的contentView是一個FrameLayout節點。因爲contentView的父節點每每是一個ID爲android.R.id.content的FrameLayout節點,所以,咱們自定義的contentView的FrameLayout節點就產生了冗餘,能夠合併到父節點中。
另外一種狀況是咱們自定義一個View,假設是MyRelativeLayout extends Relativelayout。在MyRelativeLayout中咱們又去inflate一個根節點爲RelativeLayout的佈局文件,此時就產生了冗餘,也能夠用<merge>去優化它。
下面看看咱們這個界面的佈局層級(只截取了部分我認爲有問題的地方):
能夠看到紅框內的兩個節點。 其中ProfileLabelPanel一個繼承自RelativeLayout的自定義View,在它裏面又inflate了一個根節點爲RelativeLayout的layout文件。看來,咱們碰到了上述的第二種狀況,優化方案:
將layout文件中的根節點標籤,即RelativeLayout修改爲merge,修改後的層級以下:
第二個RelativeLayout已成功合併到父節點中。
當咱們使用了Android自帶的一些主題時,咱們的activity每每會被設置一個默認的背景,這個背景是被DecorView持有的。當咱們的自定義佈局有一個全屏的背景時,好比咱們這個界面的全屏白色背景,DecorView的background此時對咱們來講是無用的,可是它會產生一次Overdraw,帶來繪製性能損耗。
這種繪製能夠從上面提到的Trace for OpenGL ES工具看到,首先它繪製了一個黑色背景(手Q在application層級應用了黑色背景主題),接着繪製了一個白色背景覆蓋在它上面。
去掉window的背景能夠在onCreate中調用
getWindow().setBackgroundDrawable(null);
可是有一點必定要注意: 上述語句必須在setContentView以後執行,不然是無效的。緣由從源碼中就很容易看出來。 這兩條語句最終都會調用到PhoneWindow類中的同名方法。
setBackgroundDrawable其實設置的就是DecorView的background。
Activity剛啓動的時候,DecorView是爲null的,此時setContentView方法會先去installDecor,而後纔會去加載咱們本身的layout。
這也就是爲何必須先調用setContentView的緣由。
去掉window背景先後的具體性能測試請參考: Speed up your Android UI
在咱們的編輯標籤界面中,有個全屏的白色背景(在根節點中設置的),其中,上半部分的標籤展現區域使用了這個默認的白色背景,而底部的標籤編輯區域(就是上文提到的ProfileLabelPanel這個自定義View,一片紅色那裏)則使用了本身的背景,覆蓋了底層的白色背景,從而產生了一次overdraw。
優化方案:去掉根節點上的backgruond,將其加到須要的子節點中(即界面的上半部分節點中)。此時,紅色區域的背景就不會繪製在原來的白色背景之上,而是屬於第一層繪製的背景。
注意: 因爲將根節點上的background移到了須要的子節點中,所以,若子節點和根節點之間有margin, 就會產生問題(沒有bakground去覆蓋)。所以,子節點的佈局儘可能使用match_parent, 原來的margin改爲padding。好比,我這個例子中:
將根節點上的background移到GridView這個子節點上以後,必須將以前的margin給成padding,不然就會和根節點產生空隙。
這種狀況常常發生在View須要兩層背景,好比ImageView須要設置一個前景和一個背景(其中背景用來作邊框)。以下圖:
這是一個ImageView,設置了兩層drawable,底下一層僅僅是爲了做爲圖片的邊框而已。可是兩層drawable的重疊區域去繪製了兩次,致使overdraw。
優化方案: 將背景drawable製做成9patch,而且將和前景重疊的部分設置爲透明。因爲Android的2D渲染器會優化9patch中的透明區域,從而優化了此次overDraw。
注意: 必須將圖片製做成9patch才行,由於Android 2D渲染器只對9patch有這個優化,不然,一張普通的Png,就算你把中間的部分設置成透明,也不會減小此次overDraw,你們能夠作個demo嘗試一下。
能夠看到,背景的一層overdraw已經去掉,底部區域的大片紅色已經優化成了藍色,只有少量的綠色。
在Android的開發過程當中,overdraw是不可避免的,可是過多的Overdraw就會帶來巨大的性能問題。這個時候,就須要咱們使用一些方法和工具來優化,此文僅作拋磚引玉之用,歡迎拍磚探討。
強烈推薦博文: Android Performance Case Study