Android最佳性能實(shí)踐(四)——布局優(yōu)化技巧

在前面幾篇文章當(dāng)中,我們學(xué)習(xí)了如何通過合理管理內(nèi)存,以及高性能編碼技巧的方式來提升應(yīng)用程序的性能。然而實(shí)際上界面布局也會(huì)對(duì)應(yīng)用程序的性能產(chǎn)生比較大的影響,如果布局寫得糟糕的話,那么程序加載UI的速度就會(huì)非常慢,從而造成不好的用戶體驗(yàn)。那么本篇文章我們就來學(xué)習(xí)一下,如何通過優(yōu)化布局來提供應(yīng)用程序的性能。還沒有看過前面前面一篇文章的朋友建議可以先去閱讀Android最佳性能實(shí)踐(三)——高性能編碼優(yōu)化

重用布局文件

Android系統(tǒng)中已經(jīng)提供了非常多好用的控件,這讓我們在編寫布局的時(shí)候可以很輕松。但是有些時(shí)候我們可能需要反復(fù)利用某個(gè)已經(jīng)寫好的布局,如果你總是使用復(fù)制粘貼的方式來進(jìn)行布局重用,這顯然是一種很笨的做法。而Android當(dāng)然也已經(jīng)充分考慮到了布局重用的重要性,于是提供了和這兩個(gè)非常有用的標(biāo)簽,下面我們就來逐個(gè)學(xué)習(xí)一下。

標(biāo)簽可以允許在一個(gè)布局當(dāng)中引入另外一個(gè)布局,那么比如說我們程序的所有界面都有一個(gè)公共的部分,這個(gè)時(shí)候最好的做法就是將這個(gè)公共的部分提取到一個(gè)獨(dú)立的布局文件當(dāng)中,然后在每個(gè)界面的布局文件當(dāng)中來引用這個(gè)公共的布局。

這里舉個(gè)例子吧,我們應(yīng)該都知道,目前幾乎所有的軟件都會(huì)有一個(gè)頭布局,頭布局中可以包含界面的標(biāo)題、返回按鈕、以及其它一些操作功能等。那這樣的一個(gè)頭布局,有些軟件是使用ActionBar來實(shí)現(xiàn)的,但是由于ActionBar的靈活性不太好,因而也有很多軟件會(huì)選擇自己去編寫實(shí)現(xiàn)。那如果自己去實(shí)現(xiàn)的話,由于這個(gè)頭布局是在所有界面都要使用的,顯然我們不可能在每個(gè)界面當(dāng)中都去寫一遍這個(gè)頭布局的代碼,因此這種情況下使用標(biāo)簽就非常合適了。這里為了給大家演示一下,我就編寫一個(gè)非常簡單的頭布局,在res/layout文件夾中新建titlebar.xml作為頭布局,代碼如下所示:

[html]view plaincopy


android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/back"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_centerVertical="true"

android:text="Back"/>

android:id="@+id/title"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:text="Title"

android:textSize="20sp"/>

android:id="@+id/done"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentRight="true"

android:layout_centerVertical="true"

android:text="Done"/>

可以看到,titlebar.xml中的布局非常簡單,外層是一個(gè)RelativeLayout,里面只有兩個(gè)Button和一個(gè)TextView,左邊的Button用于實(shí)現(xiàn)返回功能,右邊的Button用于實(shí)現(xiàn)完成功能,中間的TextView則可以用于顯示當(dāng)前界面的標(biāo)題。我們可以來預(yù)覽一下titlebar的樣子,如下圖所示:

好的,那titlebar作為一個(gè)獨(dú)立的布局現(xiàn)在我們已經(jīng)編寫完了,接下來的工作就非常簡單了,無論任何界面需要加入titlebar這個(gè)功能,只需要在布局文件中引入titlebar.xml就可以了。那么比如說我們的程序當(dāng)中有一個(gè)activity_main.xml文件,現(xiàn)在想要引入titlebar只需要這樣寫:

[html]view plaincopy


android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

......

非常簡單吧,一行include語句就可以搞定了。標(biāo)簽當(dāng)中可以指定一個(gè)layout屬性,我們在這個(gè)layout屬性中填寫需要引入的布局名就可以了。而且使用這種引入的方式,以后如果titlebar的界面有所變更,我們只需要修改titlebar.xml這一個(gè)文件就可以了,而不是所有界面一個(gè)個(gè)地去修改。

等等!現(xiàn)在如果你運(yùn)行一下程序會(huì)發(fā)現(xiàn)出大問題了,雖然titlebar是成功引入了,但是我們activity_main.xml中本來的界面全部都不見了!出現(xiàn)這個(gè)問題是原因是因?yàn)閠itlebar的最外層布局是一個(gè)寬高都是match_parent的RelativeLayout,它會(huì)將整個(gè)布局都填充滿,因而我們原本的布局也就看不見了。那既然問題的原因清楚了,相信你立刻就想到應(yīng)該怎么修改了,將RelativeLayout的layout_height屬性修改成wrap_content不就可以了嘛。沒錯(cuò),這樣修改當(dāng)然是沒問題的,不過這種修改方式會(huì)讓所有引用titlebar的界面都受到影響,而如何你只希望讓activity_main.xml這一個(gè)界面受影響的話,那么可以使用覆寫屬性的方式。

在標(biāo)簽當(dāng)中,我們是可以覆寫所有l(wèi)ayout屬性的,即include中指定的layout屬性將會(huì)覆蓋掉titlebar中指定的layout屬性。因此,這里我們希望將titlebar的高度設(shè)置成wrap_content,就可以這樣寫:

[html]view plaincopy


android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:layout_width="match_parent"

android:layout_height="wrap_content"

layout="@layout/titlebar"/>

......

現(xiàn)在重新運(yùn)行一下程序應(yīng)該就可以一切正常了,如下圖所示:

除了layout_height之外,我們還可以覆寫titlebar中的任何一個(gè)layout屬性,如layout_gravity、layout_margin等,而非layout屬性則無法在標(biāo)簽當(dāng)中進(jìn)行覆寫。另外需要注意的是,如果我們想要在標(biāo)簽當(dāng)中覆寫layout屬性,必須要將layout_width和layout_height這兩個(gè)屬性也進(jìn)行覆寫,否則覆寫效果將不會(huì)生效。

標(biāo)簽是作為標(biāo)簽的一種輔助擴(kuò)展來使用的,它的主要作用是為了防止在引用布局文件時(shí)產(chǎn)生多余的布局嵌套。大家都知道,Android去解析和展示一個(gè)布局是需要消耗時(shí)間的,布局嵌套的越多,那么解析起來就越耗時(shí),性能也就越差,因此我們在編寫布局文件時(shí)應(yīng)該讓嵌套的層數(shù)越少越好。

在上面我們講解標(biāo)簽的用法時(shí)主要介紹了它優(yōu)點(diǎn),但是它也存在著一個(gè)不好的地方,就是可能會(huì)導(dǎo)致產(chǎn)生多余的布局嵌套。這里還是通過舉例的方式跟大家說明一下,比如說我們需要編寫一個(gè)確定取消按鈕的公共布局,這樣任何一個(gè)界面需要確定和取消功能時(shí)就不用再單獨(dú)編寫了,新建ok_cancel_layout.xml,代碼如下所示:

[html]view plaincopy


android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical">

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

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

可以看到,這個(gè)界面也是非常簡單,外層是一個(gè)垂直方向的LinearLayout,LinearLayout中包含了兩個(gè)按鈕,一個(gè)用于實(shí)現(xiàn)確定功能,一個(gè)用于實(shí)現(xiàn)取消功能。現(xiàn)在我們可以來預(yù)覽一下這個(gè)界面,如下圖所示:

好的,然后我們有一個(gè)profile.xml的界面需要編輯一些內(nèi)容,那么這里就可以將ok_cancel_layout這個(gè)布局引入到profile.xml界面當(dāng)中,如下所示:

[html]view plaincopy


android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

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

在profile.xml當(dāng)中有一個(gè)EditText控件用于編輯內(nèi)容,然后下面使用了標(biāo)簽來將ok_cancel_layout布局進(jìn)行引入,現(xiàn)在重新運(yùn)行一下程序,界面效果如下圖所示:

看上去效果非常不錯(cuò)對(duì)嗎?可是在你毫無察覺的情況下,目前profile.xml這個(gè)界面當(dāng)中其實(shí)已經(jīng)存在著多余的布局嵌套了!感覺還沒寫幾行代碼呢,怎么這就已經(jīng)有多余的布局嵌套了?不信的話我們可以通過View Hierarchy工具來查看一下,如下圖所示:

可以看到,最外層首先是一個(gè)FrameLayout,這個(gè)無可厚非,不知道為什么最外層是FrameLayout的朋友可以去參考Android LayoutInflater原理分析,帶你一步步深入了解View(一)這篇文章。然后FrameLayout中包含的是一個(gè)LinearLayout,這個(gè)就是我們在profile.xml中定義的最外層布局。接下來的部分就有問題了,在最外層的LinearLayout當(dāng)中包含了兩個(gè)元素,一個(gè)是EditText,另一個(gè)又是一個(gè)LinearLayout,然后在這個(gè)內(nèi)部的LinearLayout當(dāng)中才包含了確定和取消這兩個(gè)按鈕。

相信大家已經(jīng)可以看出來了吧,這個(gè)內(nèi)部的LinearLayout就是一個(gè)多余的布局嵌套,實(shí)際上并不需要這樣一層,讓兩個(gè)按鈕直接包含在外部的LinearLayout當(dāng)中就可以了。而這個(gè)多余的布局嵌套其實(shí)就是由于布局引入所導(dǎo)致的,因?yàn)槲覀冊趏k_cancel_layout.xml中也定義了一個(gè)LinearLayout。那么應(yīng)該怎樣優(yōu)化掉這個(gè)問題呢?當(dāng)然就是使用標(biāo)簽來完成了,修改ok_cancel_layout.xml中的代碼,如下所示:

[html]view plaincopy


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

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

可以看到,這里我們將ok_cancel_layout最外層的LinearLayout布局刪除掉,換用了標(biāo)簽,這就表示當(dāng)有任何一個(gè)地方去include這個(gè)布局時(shí),會(huì)將標(biāo)簽內(nèi)包含的內(nèi)容直接填充到include的位置,不會(huì)再添加任何額外的布局結(jié)構(gòu)。好的,的用法就是這么簡單,現(xiàn)在重新運(yùn)行一下程序,你會(huì)看到界面沒有任何改變,然后我們再通過View Hierarchy工具來查看一下當(dāng)前的View結(jié)構(gòu),如下圖所示:

OK,可以看到,現(xiàn)在EditText和兩個(gè)按鈕都直接包含在了LinearLayout下面,我們的profile.xml當(dāng)中也就不存在多余的布局嵌套了。

僅在需要時(shí)才加載布局

有的時(shí)候我們會(huì)遇到這樣的場景,就是某個(gè)布局當(dāng)中的元素非常多,但并不是所有元素都一起顯示出來的,而是普通情況下只顯示部分常用的元素,而那些不常用的元素只有在用戶進(jìn)行特定操作的情況下才會(huì)顯示出來。

這里舉個(gè)大家都非常熟悉的例子,我們在添加聯(lián)系人的時(shí)候其實(shí)可以編輯的字段真的非常多,姓名、電話、email、傳真、住址、昵稱等等等等,但其實(shí)基本上大家最常用的就是填一個(gè)姓名,填一個(gè)電話而已。那么將這么多繁雜的字段都一起顯示在界面上其實(shí)并不是一種很好的做法,因?yàn)榇蠖鄶?shù)人都是用不到這些字段的。比較聰明的做法就是把最常用的姓名和電話顯示在界面上,然后給用戶提供一個(gè)添加更多字段的選項(xiàng),當(dāng)用戶真的有需要去添加其它信息的時(shí)候,我們才將另外的元素顯示到界面上。

說到實(shí)現(xiàn)這樣一個(gè)功能,我相信大多數(shù)人的第一反應(yīng)就是將不常用的元素使用INVISIBLE或者GONE進(jìn)行隱藏,然后當(dāng)用戶需要使用這些元素的時(shí)候再把它們置成VISIBLE顯示出來。使用這種方式肯定可以實(shí)現(xiàn)功能的,但是性能方面就表現(xiàn)得一般了,因?yàn)榧词故菍⒃剡M(jìn)行隱藏,它們其實(shí)還是在布局當(dāng)中的,每個(gè)元素還擁有著自己的寬、高、背景等等屬性,解析布局的時(shí)候也會(huì)將這些隱藏的元素一一解析出來。

那么我們?nèi)绾尾拍茏屵@些不常用的元素僅在需要時(shí)才去加載呢?Android為此提供了一種非常輕量級(jí)的控件,ViewStub。ViewStub雖說也是View的一種,但是它沒有大小,沒有繪制功能,也不參與布局,資源消耗非常低,將它放置在布局當(dāng)中基本可以認(rèn)為是完全不會(huì)影響性能的。

下面我們就來學(xué)習(xí)一下如何使用ViewStub來完成僅在需要時(shí)才去加載布局的功能,目前profile.xml中只有一個(gè)EditText用于編輯信息,那么比如說我們還有另外三個(gè)不太常用的EditText,就可以將它們定義在另外一個(gè)布局文件當(dāng)中。新建profile_extra.xml文件,代碼如下所示:

[html]view plaincopy


android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/edit_extra1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_marginLeft="20dp"

android:layout_marginRight="20dp"

android:hint="Extra?field?1"/>

android:id="@+id/edit_extra2"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_marginLeft="20dp"

android:layout_marginRight="20dp"

android:layout_marginTop="10dp"

android:hint="Extra?field?2"/>

android:id="@+id/edit_extra3"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_marginLeft="20dp"

android:layout_marginRight="20dp"

android:layout_marginTop="10dp"

android:hint="Extra?field?3"/>

可以看到,在profile_extra.xml這個(gè)布局文件當(dāng)中定義了三個(gè)EditText,也就是用于編輯那些不常用信息的控件,現(xiàn)在我們可以來預(yù)覽一下這個(gè)布局,如下圖所示:

目前profile_extra.xml是一個(gè)獨(dú)立的布局,和profile.xml這個(gè)布局文件是完全沒有關(guān)系的。接下來我們修改profile.xml文件中的代碼,如下所示:

[html]view plaincopy


android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

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="@string/edit_something_here"/>

android:id="@+id/more"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="right"

android:layout_marginRight="20dp"

android:layout_marginBottom="10dp"

android:text="More"/>

android:id="@+id/view_stub"

android:layout="@layout/profile_extra"

android:layout_width="match_parent"

android:layout_height="wrap_content"

/>

可以看到,這里我們新增了一個(gè)More Button,這個(gè)按鈕就是用于去加載那些不常用的元素的,然后在Button的下面定義了一個(gè)ViewStub。在ViewStub控件中,我們先是通過id屬性給它指定了一個(gè)唯一標(biāo)識(shí),又通過layout屬性將profile_extra布局傳入進(jìn)來,接著給ViewStub指定了一個(gè)寬高。注意,雖然ViewStub是不占用任何空間的,但是每個(gè)布局都必須要指定layout_width和layout_height屬性,否則運(yùn)行就會(huì)報(bào)錯(cuò)。

接著修改ProfileActivity中的代碼,在Activity中添加More Button的點(diǎn)擊事件,并在點(diǎn)擊事件中進(jìn)行如下邏輯處理:

[java]view plaincopy

privateEditText?editExtra1;

privateEditText?editExtra2;

privateEditText?editExtra3;

publicvoidonMoreClick()?{

ViewStub?viewStub?=?(ViewStub)?findViewById(R.id.view_stub);

if(viewStub?!=null)?{

View?inflatedView?=?viewStub.inflate();

editExtra1?=?(EditText)?inflatedView.findViewById(R.id.edit_extra1);

editExtra2?=?(EditText)?inflatedView.findViewById(R.id.edit_extra2);

editExtra3?=?(EditText)?inflatedView.findViewById(R.id.edit_extra3);

}

}

當(dāng)點(diǎn)擊More Button之后我們首先會(huì)調(diào)用findViewById()方法將ViewStub的實(shí)例獲取到,拿到ViewStub的實(shí)例之后就很簡單了,調(diào)用inflate()方法或者setVisibility(View.VISIBLE)都可以將隱藏的布局給加載出來,而加載的這個(gè)布局就是剛才在XML當(dāng)中配置的profile_extra布局。

調(diào)用inflate()方法之后會(huì)將加載出來的布局進(jìn)行返回,之后我們就可以對(duì)這個(gè)布局進(jìn)行任意的操作了,再次隱藏顯示,或者獲取子元素的實(shí)例等。注意這里我對(duì)ViewStub的實(shí)例進(jìn)行了一個(gè)非空判斷,這是因?yàn)閂iewStub在XML中定義的id只在一開始有效,一旦ViewStub中指定的布局加載之后,這個(gè)id也就失敗了,那么此時(shí)findViewById()得到的值也會(huì)是空。

現(xiàn)在我們重新運(yùn)行一下程序,界面如下圖所示:

可以看到,界面上只有一個(gè)More按鈕,ViewStub是完全不占用任何空間的。然后點(diǎn)擊一下More按鈕,新的界面如下所示:

沒有問題,profile_extra.xml中定義的布局已經(jīng)加載出來了,而且顯示的位置也是在More按鈕和OK按鈕之間,正是ViewStub控件定義的位置,說明我們確實(shí)已經(jīng)將ViewStub成功使用起來了。

另外需要提醒大家一點(diǎn),ViewStub所加載的布局是不可以使用標(biāo)簽的,因此這有可能導(dǎo)致加載出來的布局存在著多余的嵌套結(jié)構(gòu),具體如何去取舍就要根據(jù)各自的實(shí)際情況來決定了,對(duì)于那些隱藏的布局文件結(jié)構(gòu)相當(dāng)復(fù)雜的情況,使用ViewStub還是一種相當(dāng)不錯(cuò)的選擇的,即使增加了一層無用的布局結(jié)構(gòu),仍然還是利大于弊。

經(jīng)過四篇文章的學(xué)習(xí),我們已經(jīng)掌握了不少可以提高Android應(yīng)用程序性能的技巧,這些技巧多數(shù)都是來自于Android Doc,我也是從中選取了一些感覺比較實(shí)用的部分,然后又加入了自己的理解呈現(xiàn)給大家。如果大家想要繼續(xù)學(xué)習(xí)更多關(guān)于性能優(yōu)化的技巧,可以到這個(gè)網(wǎng)址上閱讀更多內(nèi)容http://developer.android.com/training/best-performance.html


http://blog.csdn.net/guolin_blog/article/details/43376527

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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