Android最佳效能實踐(四)——布局最佳化技巧

來源:互聯網
上載者:User

標籤:android   viewstub   效能   include   merge   

轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/43376527

在前面幾篇文章當中,我們學習了如何通過合理管理記憶體,以及高效能編碼技巧的方式來提升應用程式的效能。然而實際上介面布局也會對應用程式的效能產生比較大的影響,如果布局寫得糟糕的話,那麼程式載入UI的速度就會非常慢,從而造成不好的使用者體驗。那麼本篇文章我們就來學習一下,如何通過最佳化布局來提供應用程式的效能。還沒有看過前面前面一篇文章的朋友建議可以先去閱讀 Android最佳效能實踐(三)——高效能編碼最佳化 。

重用布局檔案

Android系統中已經提供了非常多好用的控制項,這讓我們在編寫布局的時候可以很輕鬆。但是有些時候我們可能需要反覆利用某個已經寫好的布局,如果你總是使用複製粘貼的方式來進行布局重用,這顯然是一種很笨的做法。而Android當然也已經充分考慮到了布局重用的重要性,於是提供了<include>和<merge>這兩個非常有用的標籤,下面我們就來逐個學習一下。

<include>

<include>標籤可以允許在一個布局當中引入另外一個布局,那麼比如說我們程式的所有介面都有一個公用的部分,這個時候最好的做法就是將這個公用的部分提取到一個獨立的布局檔案當中,然後在每個介面的布局檔案當中來引用這個公用的布局。

這裡舉個例子吧,我們應該都知道,目前幾乎所有的軟體都會有一個頭布局,頭布局中可以包含介面的標題、返回按鈕、以及其它一些操作功能等。那這樣的一個頭布局,有些軟體是使用ActionBar來實現的,但是由於ActionBar的靈活性不太好,因而也有很多軟體會選擇自己去編寫實現。那如果自己去實現的話,由於這個頭布局是在所有介面都要使用的,顯然我們不可能在每個介面當中都去寫一遍這個頭布局的代碼,因此這種情況下使用<include>標籤就非常合適了。這裡為了給大家示範一下,我就編寫一個非常簡單的頭布局,在res/layout檔案夾中建立titlebar.xml作為頭布局,代碼如下所示:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <Button        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" />    <TextView        android:id="@+id/title"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:text="Title"        android:textSize="20sp" />    <Button        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" /></RelativeLayout>
可以看到,titlebar.xml中的布局非常簡單,外層是一個RelativeLayout,裡面只有兩個Button和一個TextView,左邊的Button用於實現返回功能,右邊的Button用於實現完成功能,中間的TextView則可以用於顯示當前介面的標題。我們可以來預覽一下titlebar的樣子,如所示:


好的,那titlebar作為一個獨立的布局現在我們已經編寫完了,接下來的工作就非常簡單了,無論任何介面需要加入titlebar這個功能,只需要在布局檔案中引入titlebar.xml就可以了。那麼比如說我們的程式當中有一個activity_main.xml檔案,現在想要引入titlebar只需要這樣寫:

<?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" >    <include layout="@layout/titlebar" />    ......</LinearLayout>
非常簡單吧,一行include語句就可以搞定了。<include>標籤當中可以指定一個layout屬性,我們在這個layout屬性中填寫需要引入的布局名就可以了。而且使用這種引入的方式,以後如果titlebar的介面有所變更,我們只需要修改titlebar.xml這一個檔案就可以了,而不是所有介面一個個地去修改。

等等!現在如果你運行一下程式會發現出大問題了,雖然titlebar是成功引入了,但是我們activity_main.xml中本來的介面全部都不見了!出現這個問題是原因是因為titlebar的最外層布局是一個寬高都是match_parent的RelativeLayout,它會將整個布局都填充滿,因而我們原本的布局也就看不見了。那既然問題的原因清楚了,相信你立刻就想到應該怎麼修改了,將RelativeLayout的layout_height屬性修改成wrap_content不就可以了嘛。沒錯,這樣修改當然是沒問題的,不過這種修改方式會讓所有引用titlebar的介面都受到影響,而如何你只希望讓activity_main.xml這一個介面受影響的話,那麼可以使用覆寫<include>屬性的方式。

在<include>標籤當中,我們是可以覆寫所有layout屬性的,即include中指定的layout屬性將會覆蓋掉titlebar中指定的layout屬性。因此,這裡我們希望將titlebar的高度設定成wrap_content,就可以這樣寫:

<?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" >    <include        android:layout_width="match_parent"        android:layout_height="wrap_content"        layout="@layout/titlebar" />    ......</LinearLayout>
現在重新運行一下程式應該就可以一切正常了,如所示:


除了layout_height之外,我們還可以覆寫titlebar中的任何一個layout屬性,如layout_gravity、layout_margin等,而非layout屬性則無法在<include>標籤當中進行覆寫。另外需要注意的是,如果我們想要在<include>標籤當中覆寫layout屬性,必須要將layout_width和layout_height這兩個屬性也進行覆寫,否則覆寫效果將不會生效。

<merge>

<merge>標籤是作為<include>標籤的一種輔助擴充來使用的,它的主要作用是為了防止在引用布局檔案時產生多餘的布局嵌套。大家都知道,Android去解析和展示一個布局是需要消耗時間的,布局嵌套的越多,那麼解析起來就越耗時,效能也就越差,因此我們在編寫布局檔案時應該讓嵌套的層數越少越好。

在上面我們講解<include>標籤的用法時主要介紹了它優點,但是它也存在著一個不好的地方,就是可能會導致產生多餘的布局嵌套。這裡還是通過舉例的方式跟大家說明一下,比如說我們需要編寫一個確定取消按鈕的公用布局,這樣任何一個介面需要確定和取消功能時就不用再單獨編寫了,建立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>
可以看到,這個介面也是非常簡單,外層是一個垂直方向的LinearLayout,LinearLayout中包含了兩個按鈕,一個用於實現確定功能,一個用於實現取消功能。現在我們可以來預覽一下這個介面,如所示:


好的,然後我們有一個profile.xml的介面需要編輯一些內容,那麼這裡就可以將ok_cancel_layout這個布局引入到profile.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="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>
在profile.xml當中有一個EditText控制項用於編輯內容,然後下面使用了<include>標籤來將ok_cancel_layout布局進行引入,現在重新運行一下程式,介面效果如所示:


看上去效果非常不錯對嗎?可是在你毫無察覺的情況下,目前profile.xml這個介面當中其實已經存在著多餘的布局嵌套了!感覺還沒寫幾行代碼呢,怎麼這就已經有多餘的布局嵌套了?不信的話我們可以通過View Hierarchy工具來查看一下,如所示:


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

相信大家已經可以看出來了吧,這個內部的LinearLayout就是一個多餘的布局嵌套,實際上並不需要這樣一層,讓兩個按鈕直接包含在外部的LinearLayout當中就可以了。而這個多餘的布局嵌套其實就是由於布局引入所導致的,因為我們在ok_cancel_layout.xml中也定義了一個LinearLayout。那麼應該怎樣最佳化掉這個問題呢?當然就是使用<merge>標籤來完成了,修改ok_cancel_layout.xml中的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android">    <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" /></merge>
可以看到,這裡我們將ok_cancel_layout最外層的LinearLayout布局刪除掉,換用了<merge>標籤,這就表示當有任何一個地方去include這個布局時,會將<merge>標籤內包含的內容直接填充到include的位置,不會再添加任何額外的布局結構。好的,<merge>的用法就是這麼簡單,現在重新運行一下程式,你會看到介面沒有任何改變,然後我們再通過View Hierarchy工具來查看一下當前的View結構,如所示:


OK,可以看到,現在EditText和兩個按鈕都直接包含在了LinearLayout下面,我們的profile.xml當中也就不存在多餘的布局嵌套了。

僅在需要時才載入布局

有的時候我們會遇到這樣的情境,就是某個布局當中的元素非常多,但並不是所有元素都一起顯示出來的,而是普通情況下只顯示部分常用的元素,而那些不常用的元素只有在使用者進行特定操作的情況下才會顯示出來。

這裡舉個大家都非常熟悉的例子,我們在新增連絡人...的時候其實可以編輯的欄位真的非常多,姓名、電話、email、傳真、住址、暱稱等等等等,但其實基本上大家最常用的就是填一個姓名,填一個電話而已。那麼將這麼多繁雜的欄位都一起顯示在介面上其實並不是一種很好的做法,因為大多數人都是用不到這些欄位的。比較聰明的做法就是把最常用的姓名和電話顯示在介面上,然後給使用者提供一個添加更多欄位的選項,當使用者真的有需要去添加其它資訊的時候,我們才將另外的元素顯示到介面上。

說到實現這樣一個功能,我相信大多數人的第一反應就是將不常用的元素使用INVISIBLE或者GONE進行隱藏,然後當使用者需要使用這些元素的時候再把它們置成VISIBLE顯示出來。使用這種方式肯定可以實現功能的,但是效能方面就表現得一般了,因為即使是將元素進行隱藏,它們其實還是在布局當中的,每個元素還擁有著自己的寬、高、背景等等屬性,解析布局的時候也會將這些隱藏的元素一一解析出來。

那麼我們如何才能讓這些不常用的元素僅在需要時才去載入呢?Android為此提供了一種非常輕量級的控制項,ViewStub。ViewStub雖說也是View的一種,但是它沒有大小,沒有繪製功能,也不參與布局,資源消耗非常低,將它放置在布局當中基本可以認為是完全不會影響效能的。

下面我們就來學習一下如何使用ViewStub來完成僅在需要時才去載入布局的功能,目前profile.xml中只有一個EditText用於編輯資訊,那麼比如說我們還有另外三個不太常用的EditText,就可以將它們定義在另外一個布局檔案當中。建立profile_extra.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="match_parent"    android:orientation="vertical" >    <EditText        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" />    <EditText        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" />    <EditText        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" /></LinearLayout>
可以看到,在profile_extra.xml這個布局檔案當中定義了三個EditText,也就是用於編輯那些不常用資訊的控制項,現在我們可以來預覽一下這個布局,如所示:


目前profile_extra.xml是一個獨立的布局,和profile.xml這個布局檔案是完全沒有關係的。接下來我們修改profile.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="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="@string/edit_something_here" />    <Button        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" />        <ViewStub         android:id="@+id/view_stub"        android:layout="@layout/profile_extra"        android:layout_width="match_parent"        android:layout_height="wrap_content"        />    <include layout="@layout/ok_cancel_layout" /></LinearLayout>
可以看到,這裡我們新增了一個More Button,這個按鈕就是用於去載入那些不常用的元素的,然後在Button的下面定義了一個ViewStub。在ViewStub控制項中,我們先是通過id屬性給它指定了一個唯一標識,又通過layout屬性將profile_extra布局傳入進來,接著給ViewStub指定了一個寬高。注意,雖然ViewStub是不佔用任何空間的,但是每個布局都必須要指定layout_width和layout_height屬性,否則運行就會報錯。

接著修改ProfileActivity中的代碼,在Activity中添加More Button的點擊事件,並在點擊事件中進行如下邏輯處理:

private EditText editExtra1;private EditText editExtra2;private EditText editExtra3;public void onMoreClick() {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);}}
當點擊More Button之後我們首先會調用findViewById()方法將ViewStub的執行個體擷取到,拿到ViewStub的執行個體之後就很簡單了,調用inflate()方法或者setVisibility(View.VISIBLE)都可以將隱藏的布局給載入出來,而載入的這個布局就是剛才在XML當中配置的profile_extra布局。

調用inflate()方法之後會將載入出來的布局進行返回,之後我們就可以對這個布局進行任意的操作了,再次隱藏顯示,或者擷取子項目的執行個體等。注意這裡我對ViewStub的執行個體進行了一個非空判斷,這是因為ViewStub在XML中定義的id只在一開始有效,一旦ViewStub中指定的布局載入之後,這個id也就失敗了,那麼此時findViewById()得到的值也會是空。

現在我們重新運行一下程式,介面如所示:


可以看到,介面上只有一個More按鈕,ViewStub是完全不佔用任何空間的。然後點擊一下More按鈕,新的介面如下所示:


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

另外需要提醒大家一點,ViewStub所載入的布局是不可以使用<merge>標籤的,因此這有可能導致載入出來的布局存在著多餘的嵌套結構,具體如何去取捨就要根據各自的實際情況來決定了,對於那些隱藏的布局檔案結構相當複雜的情況,使用ViewStub還是一種相當不錯的選擇的,即使增加了一層無用的布局結構,仍然還是利大於弊。

經過四篇文章的學習,我們已經掌握了不少可以提高Android應用程式效能的技巧,這些技巧多數都是來自於Android Doc,我也是從中選取了一些感覺比較實用的部分,然後又加入了自己的理解呈現給大家。如果大家想要繼續學習更多關於效能最佳化的技巧,可以到這個網址上閱讀更多內容 http://developer.android.com/training/best-performance.html 。

好的,那麼最佳效能實踐系列的文章就到此結束,感謝大家有耐心看到最後。

第一時間獲得部落格更新提醒,以及更多技術資訊分享,歡迎關注我的公眾號,掃一掃下方二維碼或搜尋號guolin_blog,即可關注。

Android最佳效能實踐(四)——布局最佳化技巧

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.