我們通過對View的源碼分析验游,其實發(fā)現(xiàn)View的測量和繪制都是遞歸實現(xiàn)的苫幢,布局是一個多叉樹的結(jié)構(gòu)职员,多叉樹遍歷所需的時間跟樹的結(jié)構(gòu)是相關(guān)的,布局復(fù)雜那么遍歷View完成測量和繪制的時間就越久赊琳,過于復(fù)雜的布局也會影響到應(yīng)用的性能街夭。
1,Hierarchy Viewer的使用
Hierarchy Viewer是Android SDK提供的躏筏,用來檢測布局的嵌套和繪制時間的工具板丽,通過Hierarchy Viewer可以直觀的檢測布局的設(shè)計和屬性,從而找到布局中可優(yōu)化點趁尼。
下面是一個簡單的布局檐什,用它來說明Hierarchy Viewer的使用方式:
布局如下:
activity_main:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:hint="Edit something here" />
<include layout="@layout/ok_cancel_layout"/>
</LinearLayout>
ok_cancel_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:id="@+id/ok"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:text="OK" />
<Button
android:id="@+id/cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="Cancel" />
</LinearLayout>
1弱卡,開啟Hierarchy Viewer
AndroidStudio中開啟Hierarchy Viewer步驟是:選擇Tools->Android->Android Device Monitor乃正,在Android Device Monitor中有可能Hierarchy Viewer被隱藏了,按照下圖的步驟就可以打開
打開后:
Windows:顯示當前設(shè)備信息,以及當前設(shè)備的所有頁面列表。
View Properties:當前選中View的屬性名党。
TreeView:把Activity中所有控件(View)的層次結(jié)構(gòu)從左到右顯示出來叹阔,其中最右邊部分是最底層的控件(View)。
Tree Overview:全局概覽传睹,以縮略圖的方式顯示整個應(yīng)用中各控件的層次關(guān)系耳幢,并且框出TreeView窗口中顯示部分在全局中的位置,如果一個界面中的控件和層級比較多欧啤,可以通過鼠標移動這個顯示區(qū)域移動睛藻。
Layout View:整體Layout布局圖,以手機屏幕上真實位置呈現(xiàn)出來邢隧,在TreeView中選中某一個控件時店印,會在Layout View用紅色的框標注。
2倒慧,使用Hierarchy Viewer查看布局層級與耗時
(1)查看布局層級:在Window窗口點擊選擇需要查看的Activity按摘,然后點擊Load View Hierarchy按鈕即可打開,顯示在TreeView的窗口中纫谅,從左到右可以移動鼠標進行查看炫贤。
(2)查看耗時:在菜單欄中單擊Obtain layout times for tree rooted at selected node按鈕
這時付秕,就可以在TreeView中的View多了一些信息兰珍,截圖如下:
圖中灰色方框中信息從上到下分別的意思是盹牧,1 view表示這個節(jié)點下只有1個View了,就是自己本身的View励幼,如果某個節(jié)點顯示3 views汰寓,那么就表示該節(jié)點下面包括自己,一共有3個元素(自身+2個子view)苹粟。下面的時間表示Measure有滑、Layout以及Draw三個階段的耗時。
最后一個框有不同色的三個指示燈嵌削,分別對應(yīng)當前控件在測量毛好、布局以及畫視圖三個階段,顏色表示這個控件占用的時間百分比苛秕,如果是綠色的肌访,表示該控件在該階段比其他50%的控件的速度要快,黃色表示比其他50%的控件的速度要慢艇劫,紅色表示該控件在該階段的處理速度是最慢的吼驶,就需要注意了。
2,Lint
Android Lint是代碼檢查工具蟹演,通過代碼靜態(tài)檢查风钻,可以發(fā)現(xiàn)潛在的代碼問題,并給出優(yōu)化建議酒请。Lint的功能非常強大骡技,可以掃描出我們項目中的代碼的正確性、安全性羞反、性能問題布朦、可用、可達苟弛,甚至是國際化問題喝滞,并且針對不同的問題給出不同的錯誤級別:
Fatal > Error > Warning > Information > Ignore,我們可以根據(jù)這些錯誤級別來改造代碼膏秫,使得代碼更加健壯右遭。
同時Lint還可以用來檢查布局,使用Lint掃描前缤削,先配置需要檢查的項目窘哈,只需要檢查Layout層級深度。File——Settings——Inspections——Android Lint
如圖所示滚婉,在Android Lint的Performance中找到Layout has too many views 和 Layout hierarchy is too deep,勾上帅刀,其余的都取消让腹,只要保證Lint就只會拋出與這2個屬性相關(guān)的信息了。其中:
Layout has too many views表示控件太多扣溺,默認超過80個控件會提示該問題骇窍。
Layout hierarchy is too deep表示布局太深,默認層級超過10層會提示該問題锥余,可以自定義環(huán)境變量ANDROID_LINT_MAX_DEPTH來修改腹纳。布局深度增加會導(dǎo)致內(nèi)存消耗也隨之增加,因此布局盡可能淺而寬驱犹。
配置好后嘲恍,點擊確定。然后返回AndroidStudio中雄驹,在菜單欄中選擇Analyze——Inspect Code佃牛,然后選擇整個工程或者某個Moudle
為了方便測試医舆,我將上面用到的xml布局中稍作修改吁脱,一共嵌套了11層桑涎,看看就知道有多恐怖了。
然后我們用Lint工具分析一下代碼攻冷,立馬就出了提示:
Lint里提示到activity_index的布局嵌套超過了10層遍希,提示到如此明顯的地步等曼,那我們就能根據(jù)提示的消息來修改或者優(yōu)化這部分的布局了。
3凿蒜,布局的優(yōu)化方式
關(guān)于布局的優(yōu)化方式網(wǎng)上有很多的介紹禁谦,這里推薦看看郭大神的博客,寫的簡潔明了
Android最佳性能實踐(四)——布局優(yōu)化技巧
總結(jié):
提高布局效率的方法總體來說就是減少層級废封,提高繪制速度和布局復(fù)用州泊。影響布局效率主要有以下幾點:
**
(1)布局的層級越少,加載速度越快漂洋。因為View的結(jié)構(gòu)是典型的多叉樹的結(jié)構(gòu)遥皂,減少布局層級,明顯減少了多叉樹的深度刽漂,這樣在遍歷遞歸測量和繪制的時間就相應(yīng)較少很多演训。
(2)減少同一層級控件的數(shù)量,加載速度會變快贝咙。
(3)一個控件的屬性越少样悟,解析越快。刪除控件中的無用屬性也是加快解析速度的一種必要手段庭猩。
(4)盡量多使用RelativeLayout或LinearLayout窟她,不要使用絕對布局AbsoluteLayout。同時蔼水,在大部分情況下推薦使用RelativeLayout而不是LinearLayout震糖,因為前者在相同情況下,嵌套的層次會少徙缴。
(5)將可復(fù)用的組件抽取出來并通過<include/>標簽使用试伙。
(6)使用<ViewStub/>標簽加載一些不常用的布局嘁信。<ViewStub/>修飾的布局不會立刻就加載于样,而是調(diào)用了inflate方法或者View.VISIBILITY屬性后才加載到內(nèi)存。這種方式特別適合用在無網(wǎng)絡(luò)環(huán)境給頁面做提示的功能上潘靖。
(7)使用<merge/>標簽減少布局的嵌套層次穿剖。
(8)盡可能少用wrap_content,wrap_content會增加布局measure時的計算成本卦溢,已知寬高為固定值時糊余,不用wrap_content秀又。
**
4,避免過度繪制
可以參考Android性能優(yōu)化之如何避免Overdraw
Overdraw就是過度繪制贬芥,是指在一幀的時間內(nèi)(16.67ms)像素被繪制了多次吐辙,理論上一個像素每次只繪制一次是最優(yōu)的,但是由于重疊的布局導(dǎo)致一些像素會被多次繪制蘸劈,而每次繪制都會對應(yīng)到CPU的一組繪圖命令和GPU的一些操作昏苏,當這個操作耗時超過16.67ms時,就會出現(xiàn)掉幀現(xiàn)象威沫,也就是我們所說的卡頓贤惯,所以對重疊不可見元素的重復(fù)繪制會產(chǎn)生額外的開銷,需要盡量減少Overdraw的發(fā)生棒掠。
Android提供了測量Overdraw的選項孵构,在開發(fā)者選項-調(diào)試GPU過度繪制(Show GPU Overdraw),打開選項就可以看到當前頁面Overdraw的狀態(tài)烟很,就可以觀察屏幕的繪制狀態(tài)颈墅。該工具會使用三種不同的顏色繪制屏幕,來指示overdraw發(fā)生在哪里以及程度如何溯职,其中:
沒有顏色: 意味著沒有overdraw精盅。像素只畫了一次。
藍色: 意味著overdraw 1倍谜酒。像素繪制了兩次叹俏。大片的藍色還是可以接受的(若整個窗口是藍色的,可以擺脫一層)僻族。
綠色: 意味著overdraw 2倍粘驰。像素繪制了三次。中等大小的綠色區(qū)域是可以接受的但你應(yīng)該嘗試優(yōu)化述么、減少它們蝌数。
淺紅: 意味著overdraw 3倍。像素繪制了四次度秘,小范圍可以接受顶伞。
暗紅: 意味著overdraw 4倍。像素繪制了五次或者更多剑梳。這是錯誤的唆貌,要修復(fù)它們。
如何避免過度繪制?
除了上述8點布局優(yōu)化建議外裂逐,還有
(1)布局上的優(yōu)化
在XML布局上,如果出現(xiàn)了過度繪制的情況酪刀,可以使用Hierarchy View來查看具體的層級情況粹舵,可以通過XML布局優(yōu)化來減少層級。需要注意的是骂倘,在使用XML文件布局時眼滤,會設(shè)置很多背景,如果不是必需的历涝,盡量移除柠偶。布局優(yōu)化總結(jié)為以下幾點:
移除XML中非必需的背景,或根據(jù)條件設(shè)置睬关。
移除Window默認的背景诱担。
按需顯示占位背景圖片。
使用Android自帶的一些主題時电爹,activity往往會被設(shè)置一個默認的背景蔫仙,這個背景由DecorView持有。當自定義布局有一個全屏的背景時丐箩,比如設(shè)置了這個界面的全屏黑色背景摇邦,DecorView的背景此時對我們來說是無用的,但是它會產(chǎn)生一次Overdraw屎勘。因此沒有必要的話施籍,也可以移除,代碼如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.getWindow().setBackgroundDrawable(null);
setContentView(R.layout.activity_index);
}
針對ListView中的Avatar ImageView的設(shè)置概漱,在getView的代碼中丑慎,判斷是否獲取對應(yīng)的Bitmap,獲取Avatar的圖像之后瓤摧,把ImageView的Background設(shè)置為Transparent竿裂,只有當圖像沒有獲取到時,才設(shè)置對應(yīng)的Background占位圖片照弥,這樣可以避免因為給Avatar設(shè)置背景圖而導(dǎo)致的過度渲染腻异。
(2)自定義View優(yōu)化
雖然自定義View減少了Layout的層級,但在實際繪制時也是會過度繪制的这揣。原因是有些過于復(fù)雜的自定義View(通常重寫了onDraw方法)悔常,Android系統(tǒng)無法檢測在onDraw中具體會執(zhí)行什么操作,無法監(jiān)控并自動優(yōu)化给赞,也就無法避免Overdraw了机打。但是在自定義View中可以通過canvas.clipRect()來幫助系統(tǒng)識別那些可見的區(qū)域。這個方法可以指定一塊矩形區(qū)域塞俱,只有在這個區(qū)域內(nèi)才會被繪制姐帚,其他的區(qū)域會被忽視吏垮。canvas.clipRect()可以很好地幫助那些有多組重疊組件的自定義View來控制顯示的區(qū)域障涯。clipRect方法還可以幫助節(jié)約CPU與GPU資源罐旗,在clipRect區(qū)域之外的繪制指令都不會被執(zhí)行,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件唯蝶,仍然會得到繪制九秀,并且可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形區(qū)域內(nèi)的繪制操作粘我。
參考資料:
Android最佳性能實踐(四)——布局優(yōu)化技巧
Android性能優(yōu)化之如何避免Overdraw