怎樣才能寫出優秀的Android App,是每一個程序員追求的目標。那麼怎麼才能寫出一個優秀的App呢?相信很多初學者也會有這種迷茫。一句話來回答這個問題:細節很重要。今天我們就從最基礎的XML佈局來談談怎麼提高Android性能問題吧!
也許你經常會遇到比較複雜的佈局,這種情況下,最簡單的方法就是多層嵌套實現效果,但是最簡單的方法是否是最優的方法呢? 這裏需要打一個大大的問號?????經驗告訴我們,往往簡單的方法,得到的結果不是最優解,那麼我們通過一個例子來研究一下怎麼去優化我們的XML佈局吧,下面通過經典微信中的“發現”tab頁面中的佈局來看看怎麼實現。
上面這張圖片是微信界面截圖,看到這張效果圖的第一眼會讓開發者想到使用線性佈局實現這種左邊圖片,右邊文字,一行白色背景效果很方便。那麼我們就按照一般思路寫出如下佈局代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
<code
class = "hljs"
xml= "" ><linearlayout
android:background= "@color/color_eeeeee"
android:layout_height= "match_parent"
android:layout_width= "match_parent"
android:orientation= "vertical"
tools:context= ".MainActivity"
xmlns:android= "http://schemas.android.com/apk/res/android"
xmlns:tools= "http://schemas.android.com/tools" > <linearlayout
android:layout_height= "0dp"
android:layout_weight= "1"
android:layout_width= "match_parent"
android:orientation= "vertical" > <linearlayout
android:background= "@drawable/item_bg_select"
android:clickable= "true"
android:gravity= "center_vertical"
android:layout_height= "wrap_content"
android:layout_margintop= "20dp"
android:layout_width= "match_parent"
android:orientation= "horizontal"
android:paddingleft= "20dp"
android:paddingright= "20dp" > <imageview
android:layout_height= "wrap_content"
android:layout_width= "wrap_content"
android:src= "@drawable/afe" > <textview
android:layout_height= "wrap_content"
android:layout_marginleft= "15dp"
android:layout_width= "wrap_content"
android:text= "@string/fiends"
android:textcolor= "@android:color/black"
android:textsize= "16sp" > </textview></imageview></linearlayout> <linearlayout
android:background= "@android:color/white"
android:layout_height= "wrap_content"
android:layout_margintop= "20dp"
android:layout_width= "match_parent"
android:orientation= "vertical" > <linearlayout
android:background= "@drawable/item_bg_select"
android:clickable= "true"
android:gravity= "center_vertical"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal"
android:paddingleft= "20dp"
android:paddingright= "20dp" > <imageview
android:layout_height= "wrap_content"
android:layout_width= "wrap_content"
android:src= "@drawable/afg" > <textview
android:layout_height= "wrap_content"
android:layout_marginleft= "15dp"
android:layout_width= "wrap_content"
android:text= "@string/scan"
android:textcolor= "@android:color/black"
android:textsize= "16sp" > </textview></imageview></linearlayout> <view
android:background= "@color/color_e0e0e0"
android:layout_height= "0.5dp"
android:layout_marginleft= "10dp"
android:layout_marginright= "10dp"
android:layout_width= "match_parent" ></view> <linearlayout
android:background= "@drawable/item_bg_select"
android:clickable= "true"
android:gravity= "center_vertical"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal"
android:paddingleft= "20dp"
android:paddingright= "20dp" > <imageview
android:layout_height= "wrap_content"
android:layout_width= "wrap_content"
android:src= "@drawable/afh" > <textview
android:layout_height= "wrap_content"
android:layout_marginleft= "15dp"
android:layout_width= "wrap_content"
android:text= "@string/shake"
android:textcolor= "@android:color/black"
android:textsize= "16sp" > </textview></imageview></linearlayout> </linearlayout> <linearlayout
android:background= "@android:color/white"
android:layout_height= "wrap_content"
android:layout_margintop= "20dp"
android:layout_width= "match_parent"
android:orientation= "vertical" > <linearlayout
android:background= "@drawable/item_bg_select"
android:clickable= "true"
android:gravity= "center_vertical"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal"
android:paddingleft= "20dp"
android:paddingright= "20dp" > <imageview
android:layout_height= "wrap_content"
android:layout_width= "wrap_content"
android:src= "@drawable/afd" > <textview
android:layout_height= "wrap_content"
android:layout_marginleft= "15dp"
android:layout_width= "wrap_content"
android:text= "@string/nearby"
android:textcolor= "@android:color/black"
android:textsize= "16sp" > </textview></imageview></linearlayout> <view
android:background= "@color/color_e0e0e0"
android:layout_height= "0.5dp"
android:layout_marginleft= "10dp"
android:layout_marginright= "10dp"
android:layout_width= "match_parent" ></view> <linearlayout
android:background= "@drawable/item_bg_select"
android:clickable= "true"
android:gravity= "center_vertical"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal"
android:paddingleft= "20dp"
android:paddingright= "20dp" > <imageview
android:layout_height= "wrap_content"
android:layout_width= "wrap_content"
android:src= "@drawable/afb" > <textview
android:layout_height= "wrap_content"
android:layout_marginleft= "15dp"
android:layout_width= "wrap_content"
android:text= "@string/float_bottle"
android:textcolor= "@android:color/black"
android:textsize= "16sp" > </textview></imageview></linearlayout> </linearlayout> <linearlayout
android:background= "@android:color/white"
android:layout_height= "wrap_content"
android:layout_margintop= "20dp"
android:layout_width= "match_parent"
android:orientation= "vertical" > <linearlayout
android:background= "@drawable/item_bg_select"
android:clickable= "true"
android:gravity= "center_vertical"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal"
android:paddingleft= "20dp"
android:paddingright= "20dp" > <imageview
android:layout_height= "wrap_content"
android:layout_width= "wrap_content"
android:src= "@drawable/agg" > <textview
android:layout_height= "wrap_content"
android:layout_marginleft= "15dp"
android:layout_width= "wrap_content"
android:text= "@string/shopping"
android:textcolor= "@android:color/black"
android:textsize= "16sp" > </textview></imageview></linearlayout> <view
android:background= "@color/color_e0e0e0"
android:layout_height= "0.5dp"
android:layout_marginleft= "10dp"
android:layout_marginright= "10dp"
android:layout_width= "match_parent" ></view> <linearlayout
android:background= "@drawable/item_bg_select"
android:clickable= "true"
android:gravity= "center_vertical"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal"
android:paddingleft= "20dp"
android:paddingright= "20dp" > <imageview
android:layout_height= "wrap_content"
android:layout_width= "wrap_content"
android:src= "@drawable/ak6" > <textview
android:layout_height= "wrap_content"
android:layout_marginleft= "15dp"
android:layout_width= "wrap_content"
android:text= "@string/games"
android:textcolor= "@android:color/black"
android:textsize= "16sp" > </textview></imageview></linearlayout> </linearlayout> </linearlayout> <linearlayout
android:background= "@android:color/white"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal" > <textview
android:drawabletop= "@drawable/ala"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/weixin"
android:textcolor= "@color/color_9e9e9e" > <textview
android:drawabletop= "@drawable/al9"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/countans"
android:textcolor= "@color/color_9e9e9e" > <textview
android:drawabletop= "@drawable/alc"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/finds"
android:textcolor= "@color/color_9e9e9e" > <textview
android:drawabletop= "@drawable/ale"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/me"
android:textcolor= "@color/color_9e9e9e" > </textview></textview></textview></textview></linearlayout> </linearlayout> </code> |
以上佈局的效果圖如下:
是不是差不多實現了微信一樣的效果?那麼我們怎麼來判斷以上佈局是不是最優的呢?當然,我們是有工具來查看的。相信很多童鞋用過了,第一個就是 Hierarchy View,第二個就是 顯示GPU過度繪製。
Hierarchy View檢測佈局嵌套層次
如果你是使用AS開發的話,你可以在 AS 工具欄中點擊 Tools–>Android–>Android Device Monitor–>Hierarchy View。(至於Hierarchy View怎麼使用這裏就不仔細介紹了)你可以通過這個工具來查看當前佈局的層次結構,如下圖的佈局的層次結構就是上面微信的佈局:
ContentFrameLayout接點之後就是我們上面XML代碼的佈局了,從上圖可以看到,我們佈局最多有 5 層,其實你從代碼中也可以看到是 5 層,那麼我們是否能減少以上的佈局的嵌套層次呢?答案是肯定的,廢話不多說,我們直接上一份我優化過的佈局代碼吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<code
class = "hljs"
perl= "" ><relativelayout
android:background= "@color/color_eeeeee"
android:layout_height= "match_parent"
android:layout_width= "match_parent"
tools:context= ".MainActivity"
xmlns:android= "http://schemas.android.com/apk/res/android"
xmlns:app= "http://schemas.android.com/apk/res-auto"
xmlns:tools= "http://schemas.android.com/tools" > <textview
android:background= "@drawable/item_bg_select"
android:drawableleft= "@drawable/afe"
android:id= "@+id/tv_one"
android:layout_margintop= "20dp"
android:text= "@string/fiends" > <textview
android:background= "@drawable/item_bg_select"
android:drawableleft= "@drawable/afg"
android:text= "@string/scan" > <textview
android:background= "@drawable/item_bg_select"
android:drawableleft= "@drawable/afh"
android:text= "@string/shake" > </textview></textview></android.support.v7.widget.linearlayoutcompat> <textview
android:background= "@drawable/item_bg_select"
android:drawableleft= "@drawable/afd"
android:text= "@string/nearby" > <textview
android:background= "@drawable/item_bg_select"
android:drawableleft= "@drawable/afb"
android:text= "@string/float_bottle" > </textview></textview></android.support.v7.widget.linearlayoutcompat> <textview
android:background= "@drawable/item_bg_select"
android:drawableleft= "@drawable/agg"
android:text= "@string/shopping" > <textview
android:background= "@drawable/item_bg_select"
android:drawableleft= "@drawable/ak6"
android:text= "@string/games" > </textview></textview></android.support.v7.widget.linearlayoutcompat> <include
android:layout_alignparentbottom= "true"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
layout= "@layout/bottom_layout" > </include></textview></relativelayout> </code> |
哇,代碼量少了很多啊,代碼也簡潔了許多,讓人看着就很舒服,那麼我們到底進行了怎樣的優化呢?從以下幾點總結:
使用 style 主題來定義一個通用的屬性,從而重複利用代碼,減少代碼量。上面代碼使用了兩個style,一個是textStyle 和 LinerLayoutStyle ,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<code
applescript= ""
class = "hljs" ><!--TextView
的通用屬性--><style name= "textStyle"
type= "text/css" ><item
name=android:layout_width>match_parent</item> <item
name=android:layout_height>wrap_content</item> <item
name=android:textSize>16sp</item> <item
name=android:textColor> @android :color/black</item> <item
name=android:paddingRight>20dp</item> <item
name=android:paddingLeft>20dp</item> <item
name=android:gravity>center_vertical</item> <item
name=android:clickable> true </item></style> <!--LinerLayout
的通用屬性--><style name= "LinerLayoutStyle"
type= "text/css" ><item
name=android:layout_width>match_parent</item> <item
name=android:layout_height>wrap_content</item> <item
name=android:layout_marginTop>20dp</item> <item
name=android:background> @android :color/white</item> <item
name=android:orientation>vertical</item></style></code> |
2.減少佈局嵌套的層次,上面佈局使用TextView可以設置四個方向圖片來直接替代LinerLayout下包裹一個ImageView 和TextView。從而這裏減少了一層嵌套佈局,再次利用RelativeLayout相對佈局又減少了一層橋套,提高了加載佈局的效率。看圖:
從圖中看出,不僅減少了兩層嵌套佈局,而且組件數目也減少,從而減少佈局繪製的時間,大大提高了佈局加載效率。
3.使用 LinearLayoutCompat 組件來實現線性佈局元素之間的分割線,從而減少了使用View來實現分割線效果。LinearLayoutCompat的具體內容請參考 http://blog.csdn.net/feiduclear_up/article/details/46619637 。
4.使用 include 標籤加載底部菜單欄佈局,include 標籤的目的是重複利用佈局,來減少代碼了。
1
2
3
4
5
6
7
8
9
10
11
12
|
<code
class = "hljs"
xml= "" ><!--?xml
version= 1.0
encoding=utf- 8 ?--> <linearlayout
android:background= "@android:color/white"
android:layout_height= "wrap_content"
android:layout_width= "match_parent"
android:orientation= "horizontal"
xmlns:android= "http://schemas.android.com/apk/res/android" > <textview
android:drawabletop= "@drawable/ala"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/weixin"
android:textcolor= "@color/color_9e9e9e" > <textview
android:drawabletop= "@drawable/al9"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/countans"
android:textcolor= "@color/color_9e9e9e" > <textview
android:drawabletop= "@drawable/alc"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/finds"
android:textcolor= "@color/color_9e9e9e" > <textview
android:drawabletop= "@drawable/ale"
android:gravity= "center"
android:layout_height= "wrap_content"
android:layout_weight= "1"
android:layout_width= "0dp"
android:text= "@string/me"
android:textcolor= "@color/color_9e9e9e" > </textview></textview></textview></textview></linearlayout></code> |
顯示GPU過度繪製
你可以在手機打開 設置—->開發者選項—->顯示GPU過度繪製,這個開關的作用是按不同顏色值來顯示佈局的過度繪製,繪製的層次從最優到最差:藍,綠,淡紅,紅。給出一張直觀的形象圖片來表示吧
圖片從上到下代表不同層次的OverDraw,我們在佈局時候,儘量減少紅色 Overdraw,看到更多的藍色區域。來看看微信的 Overdraw圖和我們自定義的佈局Overdraw圖吧
第一張圖是 騰訊微信的Overdraw 圖,第二張圖片是我們自定義的 Overdraw 圖片。從上面兩張圖片看出,我們自己的佈局和微信原版的佈局 Overdraw 過度繪製情況差不多,沒啥區別。那麼我們能不能去減少紅色部分的過度繪製呢?試試吧!
我們先去掉最頂端佈局RelativeLayout的背景
1
|
<code
class = "hljs"
perl= "" >android:background= @color /color_eeeeee</code> |
然後修改每個item選擇資源 selector
1
|
<code
class = "hljs"
perl= "" >
android:background= @drawable /item_bg_select1</code> |
之前的 item_bg_select.xml 資源是如下代碼:
1
2
3
4
5
6
7
8
|
<code
class = "hljs"
xml= "" ><!--?xml
version= 1.0
encoding=utf- 8 ?--> <item
android:drawable= "@color/color_e0e0e0"
android:state_pressed= "true" ></item> <item
android:drawable= "@android:color/white" ></item> </selector></code> |
修改之後的 item_bg_select1.xml資源代碼如下:
1
2
3
4
5
6
|
<code
class = "hljs"
xml= "" ><!--?xml
version= 1.0
encoding=utf- 8 ?--> <item
android:drawable= "@color/color_e0e0e0"
android:state_pressed= "true" ></item> </selector></code> |
我們發現,新的 selector資源去除了
1
|
<code
class = "hljs"
xml= "" ><item
android:drawable= "@android:color/white" ></item></code> |
因爲整個背景是白色的,無需重複設置正常情況下item的背景顏色。
修改之後的效果圖如下:
看出,基本沒有紅色區域,從而提高佈局的繪製效率。
總結:現在看來,我們通過減少背景顏色的設置來減少Overdraw的情況。我們自己佈局過度繪製的情況比微信本身的情況有很大的改善,是不是感覺很nice~~。
懶加載佈局 ViewStub
除了以上兩種方法來優化佈局,還有其他辦法來繼續優化佈局,在某些情況下,有些佈局是僅在需要時才加載,比如小米手機的添加聯繫人功能就有在編輯姓名的時候有一個下拉按鈕顯示更多輸入信息,看圖
遇到這種情況,我們首先想到的 就是將不常用的元素使用INVISIBLE或者GONE進行隱藏,這樣是否真的好呢?是否達到了 佈局優化的最終效果呢?利用 INVISIBLE只是隱藏佈局,但是佈局還是佔居當前位置,且系統在加載佈局的時候這一部分還是會繪製出來,同樣花費繪製時間。那麼有沒有好的辦法來解決這一問題呢?不言而喻,我們可以使用懶加載佈局 ViewStub。
ViewStub是Android爲此提供了一種非常輕量級的控件。ViewStub雖說也是View的一種,但是它沒有大小,沒有繪製功能,也不參與佈局,資源消耗非常低,將它放置在佈局當中基本可以認爲是完全不會影響性能的。
下面我們來學習一下 ViewStub的使用方法吧!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<code
class = "hljs"
xml= "" ><!--?xml
version= 1.0
encoding=utf- 8 ?--> <linearlayout
android:layout_height= "match_parent"
android:layout_width= "match_parent"
android:orientation= "vertical"
android:padding= "20dp"
xmlns:android= "http://schemas.android.com/apk/res/android" > <edittext
android:drawableright= "@drawable/a0e"
android:hint= "@string/name"
android:id= "@+id/et_name"
android:layout_height= "wrap_content"
android:layout_width= "150dp" > <viewstub
android:id= "@+id/view_stub"
android:layout= "@layout/item_name"
android:layout_height= "wrap_content"
android:layout_width= "wrap_content" > <edittext
android:hint= "@string/comp"
android:layout_height= "wrap_content"
android:layout_width= "150dp" > <edittext
android:hint= "@string/lead"
android:layout_height= "wrap_content"
android:layout_width= "150dp" > </edittext></edittext></viewstub></edittext></linearlayout></code> |
item_name.xml 佈局如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
<code
class = "hljs"
xml= "" ><!--?xml
version= 1.0
encoding=utf- 8 ?--> <linearlayout
android:layout_height= "match_parent"
android:layout_width= "match_parent"
android:orientation= "vertical"
xmlns:android= "http://schemas.android.com/apk/res/android" > <edittext
android:hint= "@string/name1"
android:id= "@+id/et_name1"
android:layout_height= "wrap_content"
android:layout_width= "150dp" > <edittext
android:hint= "@string/name2"
android:id= "@+id/et_name2"
android:layout_height= "wrap_content"
android:layout_width= "150dp" > <edittext
android:hint= "@string/name3"
android:id= "@+id/et_name3"
android:layout_height= "wrap_content"
android:layout_width= "150dp" > <edittext
android:hint= "@string/name4"
android:id= "@+id/et_name4"
android:layout_height= "wrap_content"
android:layout_width= "150dp" > </edittext></edittext></edittext></edittext></linearlayout></code> |
然後你在代碼中這麼使用即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<code
avrasm= ""
class = "hljs" >findViewById(R.id.et_name).setOnClickListener( new
View.OnClickListener() { @Override public
void
onClick(View v) { ViewStub
viewStub = (ViewStub) findViewById(R.id.view_stub); if
( null
!= viewStub) { //主要是這一句顯示更多佈局 View
view = viewStub.inflate(); EditText
name1 = (EditText) view.findViewById(R.id.et_name1); EditText
name2 = (EditText) view.findViewById(R.id.et_name2); EditText
name3 = (EditText) view.findViewById(R.id.et_name3); EditText
name4 = (EditText) view.findViewById(R.id.et_name4); } } });</code> |
效果圖如下:
從效果圖可以看出,當用戶點擊姓名下拉按鈕時,其他關於姓名的佈局就加載出來了,而且原來的佈局是顯示在ViewStub佈局之下的,位置顯示沒有任何異常。當然你可以通過 setVisibility(View.VISIBLE)或者viewStub.inflate()方來來讓其顯示,通過setVisibility(View.INVISIBLE)來隱藏 ViewStub。
Android Lint 工具
這裏不展開介紹 Lint 工具,感興趣的童鞋可以自己網上搜索一把,這個工具主要是用來檢查工程中代碼的不合理,佈局不合理,資源重複,圖片重複,等,讓開發者進一步優化自己的應用。如果你是AS 用戶,你可以在工具欄 Analyze—>Inspect Code 打開此工具使用。