Android圖形界面性能優(yōu)化之布局文件優(yōu)化
隨著android手機(jī)的迅速發(fā)展以及技術(shù)的更新,市場(chǎng)對(duì)應(yīng)用的性能提出了更高的要求,android圖形界面是用戶最直接的感受渠道,因此做好UI界面的設(shè)計(jì)日益重要。而其實(shí)相同的布局效果可以有不同的實(shí)現(xiàn)方式。糟糕的布局會(huì)使程序加載UI的速度非常慢,從而降低了用戶體驗(yàn)滿意度,同時(shí)也對(duì)程序的性能產(chǎn)生比較大的影響,因此優(yōu)化布局是提高性能的一種重要途徑,以下從三方面講述布局的三重優(yōu)化。
第一重優(yōu)化:布局中樣式的抽取
布局文件中,系統(tǒng)給我們提供了很多控件的屬性,我們可以方便的使用,但有時(shí)在同一個(gè)布局中甚至不同的布局中會(huì)重復(fù)出現(xiàn)同一個(gè)控件并且其屬性設(shè)置相似,這時(shí)如果我們重復(fù)的單個(gè)定義相同控件的屬性會(huì)使布局代碼顯得龐大冗雜,減低了布局文件的可讀性同時(shí)開(kāi)發(fā)的效率和程序性能也會(huì)受很大的影響,這就需要我們將相同控件的相同屬性抽取出來(lái)做成樣式模板。
舉例1:
同一個(gè)布局或不同布局文件中常常會(huì)用到控件顯示文字比如TextView,Button等,而這時(shí)候?qū)τ诳丶拇笮〖帮@示的文字的大小和顏色等要求又是相同的,這時(shí)我們就可以將這些相同的屬性抽取出來(lái)。如下圖布局是一個(gè)簡(jiǎn)單的LinearLayout布局中的三個(gè)控件中間一個(gè)Button,兩邊各一個(gè)TextView,看代碼不難發(fā)現(xiàn)他們的尺寸,背景,和字體大小顏色都是相同的,這時(shí)我們則可以抽取出來(lái)。
第二重優(yōu)化:布局文件復(fù)用
布局文件的復(fù)用思想和樣式抽取的思想是相同的,都是將相同的東西抽取出來(lái)以達(dá)到可以重復(fù)利用的目的。當(dāng)然Android也已經(jīng)充分考慮到了布局重用的重要性,于是提供了和這兩個(gè)非常有用的標(biāo)簽,讓我們更方便的實(shí)現(xiàn)代碼復(fù)用。
1.include
標(biāo)簽可以允許在一個(gè)布局當(dāng)中引入另外一個(gè)布局,那么比如說(shuō)我們程序的所有界面都有一個(gè)公共的部分,這個(gè)時(shí)候最好的做法就是將這個(gè)公共的部分提取到一個(gè)獨(dú)立的布局文件當(dāng)中,然后在每個(gè)界面的布局文件當(dāng)中來(lái)引用這個(gè)公共的布局。
舉例2:我們應(yīng)該都知道,目前幾乎所有的軟件都會(huì)有一個(gè)頭布局,頭布局中可以包含界面的標(biāo)題、返回按鈕、以及其它一些操作功能等。那這樣的一個(gè)頭布局,有些軟件是使用ActionBar來(lái)實(shí)現(xiàn)的,但是由于ActionBar的靈活性不太好,因而也有很多軟件會(huì)選擇自己去編寫(xiě)實(shí)現(xiàn)。那如果自己去實(shí)現(xiàn)的話,由于這個(gè)頭布局是在所有界面都要使用的,顯然我們不可能在每個(gè)界面當(dāng)中都去寫(xiě)一遍這個(gè)頭布局的代碼,因此這種情況下使用標(biāo)簽就非常合適了。這里為了給大家演示一下,我就編寫(xiě)一個(gè)非常簡(jiǎn)單的頭布局,在res/layout文件夾中新建titlebar.xml作為頭布局,代碼如下所示:
titlebar.xml中的布局非常簡(jiǎn)單,外層RelativeLayout里面只有兩個(gè)Button和一個(gè)TextView,左邊Button用于實(shí)現(xiàn)返回功能,右邊的Button用于實(shí)現(xiàn)完成功能,中間的TextView則可以用于顯示當(dāng)前界面的標(biāo)題。我們可以來(lái)預(yù)覽一下titlebar的樣子。
titlebar的布局寫(xiě)完,接下來(lái)就可以對(duì)布局文件進(jìn)行復(fù)用了,無(wú)論任何界面需要加入titlebar這個(gè)功能,只需要在布局文件中引入titlebar.xml就可以了。
這樣就非常簡(jiǎn)單,一行include語(yǔ)句就搞定了,而無(wú)需重復(fù)寫(xiě)titlebar里的布局。標(biāo)簽當(dāng)中可以指定一個(gè)layout屬性,我們?cè)谶@個(gè)layout屬性中填寫(xiě)需要引入的布局名就可以了。而且使用這種引入的方式,以后如果titlebar的界面有所變更,我們只需要修改titlebar.xml這一個(gè)文件就可以了,而不是所有界面一個(gè)個(gè)地去修改。但需要注意的是雖然titlebar是成功引入了,但是我們activity_main.xml中本來(lái)的界面全部都不見(jiàn)了!出現(xiàn)這個(gè)問(wèn)題的原因是因?yàn)閠itlebar的最外層布局是一個(gè)寬高都是match_parent的RelativeLayout,它會(huì)將整個(gè)布局都填充滿,因而我們?cè)镜牟季忠簿涂床灰?jiàn)了。那既然問(wèn)題的原因清楚了,相信你立刻就想到應(yīng)該怎么修改了,將RelativeLayout的layout_height屬性修改成wrap_content不就可以了嘛。沒(méi)錯(cuò),這樣修改當(dāng)然是沒(méi)問(wèn)題的,不過(guò)這種修改方式會(huì)讓所有引用titlebar的界面都受到影響,而如何你只希望讓activity_main.xml這一個(gè)界面受影響的話,那么可以使用覆寫(xiě)屬性的方式。
在標(biāo)簽當(dāng)中,我們是可以覆寫(xiě)所有l(wèi)ayout屬性的,即include中指定的layout屬性將會(huì)覆蓋掉titlebar中指定的layout屬性。因此,這里我們希望將titlebar的高度設(shè)置成wrap_content,就可以這樣寫(xiě):
這時(shí)重新運(yùn)行一下則是可以正常顯示titlebar之外的其他內(nèi)容的。
除了layout_height之外,我們還可以覆寫(xiě)titlebar中的任何一個(gè)layout屬性,如layout_gravity、layout_margin等,而非layout屬性則無(wú)法在標(biāo)簽當(dāng)中進(jìn)行覆寫(xiě)。另外需要注意的是,如果我們想要在標(biāo)簽當(dāng)中覆寫(xiě)layout屬性,必須要將layout_width和layout_height這兩個(gè)屬性也進(jìn)行覆寫(xiě),否則覆寫(xiě)效果將不會(huì)生效。
2.
標(biāo)簽是作為標(biāo)簽的一種輔助擴(kuò)展來(lái)使用的,他的主要作用是為了防止在引用布局文件時(shí)產(chǎn)生多余的布局嵌套。Android去解析和展示一個(gè)布局是需要消耗時(shí)間的,布局嵌套越多解析起來(lái)越是耗時(shí),性能也就越差,因此我們?cè)诰帉?xiě)布局文件時(shí)應(yīng)該讓嵌套的層數(shù)越少越好。
有優(yōu)點(diǎn),但是它也存在著一個(gè)不好的地方,就是可能會(huì)導(dǎo)致產(chǎn)生多余的布局嵌套。這里還是通過(guò)舉例的方式跟大家說(shuō)明一下,比如說(shuō)我們需要編寫(xiě)一個(gè)確定取消按鈕的公共布局,這樣任何一個(gè)界面需要確定和取消功能時(shí)就不用再單獨(dú)編寫(xiě)了,新建ok_cancel_layout.xml。
可以看到,這個(gè)界面也是非常簡(jiǎn)單,外層是一個(gè)水平方向LinearLayout,LinearLayout中包含了兩個(gè)按鈕,一個(gè)用于實(shí)現(xiàn)確定功能,一個(gè)用于實(shí)現(xiàn)取消功能。現(xiàn)在我們可以來(lái)預(yù)覽一下這個(gè)界面。
然后我們有一個(gè)profile.xml的界面需要編輯一些內(nèi)容,那么這里就可以將ok_cancel_layout這個(gè)布局引入到profile.xml界面當(dāng)中。
這個(gè)看上去并沒(méi)有什么不對(duì),可是在你毫無(wú)察覺(jué)的情況下,目profile.xml這個(gè)界面當(dāng)中其實(shí)已經(jīng)存在著多余的布局嵌套了!感覺(jué)還沒(méi)寫(xiě)幾行代碼呢,怎么這就已經(jīng)有多余的布局嵌套了?我們可以通過(guò)View Hierarchy工具來(lái)查看一下。
可以看到,最外層首先是一個(gè)FrameLayout,然后FrameLayout中包含的是一個(gè)LinearLayout,這個(gè)就是我們?cè)趐rofile.xml中定義的最外層布局。接下來(lái)的部分就有問(wèn)題了,在最外層的LinearLayout當(dāng)中包含了兩個(gè)元素,一個(gè)是EditText,另一個(gè)又是一個(gè)LinearLayout,然后在這個(gè)內(nèi)部的LinearLayout當(dāng)中才包含了確定和取消這兩個(gè)按鈕。這個(gè)內(nèi)部的LinearLayout就是一個(gè)多余的布局嵌套,實(shí)際上并不需要這樣一層,讓兩個(gè)按鈕直接包含在外部的LinearLayout當(dāng)中就可以了。而這個(gè)多余的布局嵌套其實(shí)就是由于布局引入所導(dǎo)致的,因?yàn)槲覀冊(cè)趏k_cancel_layout.xml中也定義了一個(gè)LinearLayout。那么應(yīng)該怎樣優(yōu)化掉這個(gè)問(wèn)題呢?當(dāng)然就是使用標(biāo)簽來(lái)完成了,修改ok_cancel_layout.xml中的代碼,。
可以看到,這里我們將ok_cancel_layout最外層的LinearLayout布局刪除掉,換用了標(biāo)簽,這就表示當(dāng)有任何一個(gè)地方去include這個(gè)布局時(shí),會(huì)將標(biāo)簽內(nèi)包含的內(nèi)容直接填充到include的位置,不會(huì)再添加任何額外的布局結(jié)構(gòu)。好的,的用法就是這么簡(jiǎn)單,現(xiàn)在重新運(yùn)行一下程序,你會(huì)看到界面沒(méi)有任何改變,然后我們?cè)偻ㄟ^(guò)View Hierarchy工具來(lái)查看一下當(dāng)前的View結(jié)構(gòu)。
第三重優(yōu)化:僅在需要時(shí)才加載布局
有的時(shí)候我們會(huì)遇到這樣的場(chǎng)景,就是某個(gè)布局當(dāng)中的元素非常多,但并不是所有元素都一起顯示出來(lái)的,而是普通情況下只顯示部分常用的元素,而那些不常用的元素只有在用戶進(jìn)行特定操作的情況下才會(huì)顯示出來(lái)。
這里舉個(gè)大家都非常熟悉的例子,我們?cè)谔砑勇?lián)系人的時(shí)候其實(shí)可以編輯的字段真的非常多,姓名、電話、email、傳真、住址、昵稱(chēng)等等等等,但其實(shí)基本上大家最常用的就是填一個(gè)姓名,填一個(gè)電話而已。那么將這么多繁雜的字段都一起顯示在界面上其實(shí)并不是一種很好的做法,因?yàn)榇蠖鄶?shù)人都是用不到這些字段的。比較聰明的做法就是把最常用的姓名和電話顯示在界面上,然后給用戶提供一個(gè)添加更多字段的選項(xiàng),當(dāng)用戶真的有需要去添加其它信息的時(shí)候,我們才將另外的元素顯示到界面上。
說(shuō)到實(shí)現(xiàn)這樣一個(gè)功能,我相信大多數(shù)人的第一反應(yīng)就是將不常用的元素使用INVISIBLE或者GONE進(jìn)行隱藏,然后當(dāng)用戶需要使用這些元素的時(shí)候再把它們置成VISIBLE顯示出來(lái)。使用這種方式肯定可以實(shí)現(xiàn)功能的,但是性能方面就表現(xiàn)得一般了,因?yàn)榧词故菍⒃剡M(jìn)行隱藏,它們其實(shí)還是在布局當(dāng)中的,每個(gè)元素還擁有著自己的寬、高、背景等等屬性,解析布局的時(shí)候也會(huì)將這些隱藏的元素一一解析出來(lái)。
那么我們?nèi)绾尾拍茏屵@些不常用的元素僅在需要時(shí)才去加載呢?Android為此提供了一種非常輕量級(jí)的控件,ViewStub。ViewStub雖說(shuō)也是View的一種,但是它沒(méi)有大小,沒(méi)有繪制功能,也不參與布局,資源消耗非常低,將它放置在布局當(dāng)中基本可以認(rèn)為是完全不會(huì)影響性能的。
下面我們就來(lái)學(xué)習(xí)一下如何使用ViewStub來(lái)完成僅在需要時(shí)才去加載布局的功能,目前profile.xml中只有一個(gè)EditText用于編輯信息,那么比如說(shuō)我們還有另外三個(gè)不太常用的EditText,就可以將它們定義在另外一個(gè)布局文件當(dāng)中。新建profile_extra.xml文件。
目前profile_extra.xml是一個(gè)獨(dú)立的布局,和profile.xml這個(gè)布局文件是完全沒(méi)有關(guān)系的。接下來(lái)我們修改profile.xml文件中的代碼。
可以看到LinearLayout布局中我們新增了一個(gè)More Button,這個(gè)按鈕就是用于去加載那些不常用的元素的,然后在Button的下面定義了一個(gè)ViewStub。在ViewStub控件中,我們先是通過(guò)id屬性給它指定了一個(gè)唯一標(biāo)識(shí),又通過(guò)layout屬性將profile_extra布局傳入進(jìn)來(lái),接著給ViewStub指定了一個(gè)寬高。注意,雖然ViewStub是不占用任何空間的,但是每個(gè)布局都必須要指定layout_width和layout_height屬性,否則運(yùn)行就會(huì)報(bào)錯(cuò)。
接著修改ProfileActivity中的代碼,在Activity中添加More Button的點(diǎn)擊事件,并在點(diǎn)擊事件中進(jìn)行如下邏輯處理。
當(dāng)點(diǎn)擊More Button之后我們首先會(huì)調(diào)用findViewById()方法將ViewStub的實(shí)例獲取到,拿到ViewStub的實(shí)例之后就很簡(jiǎn)單了,調(diào)用inflate()方法或者setVisibility(View.VISIBLE)都可以將隱藏的布局給加載出來(lái),而加載的這個(gè)布局就是剛才在XML當(dāng)中配置的profile_extra布局。
調(diào)用inflate()方法之后會(huì)將加載出來(lái)的布局進(jìn)行返回,之后我們就可以對(duì)這個(gè)布局進(jìn)行任意的操作了,再次隱藏顯示,或者獲取子元素的實(shí)例等。注意這里我對(duì)ViewStub的實(shí)例進(jìn)行了一個(gè)非空判斷,這是因?yàn)閂iewStub在XML中定義的id只在一開(kāi)始有效,一旦ViewStub中指定的布局加載之后,這個(gè)id也就失敗了,那么此時(shí)findViewById()得到的值也會(huì)是空。
可以看到,界面上只有一個(gè)More按鈕,ViewStub是完全不占用任何空間的。然后點(diǎn)擊一下More按鈕。
運(yùn)行結(jié)果正常,profile_extra.xml中定義的布局已經(jīng)加載出來(lái)了,而且顯示的位置正是ViewStub控件定義的位置,說(shuō)明我們已經(jīng)成功是實(shí)現(xiàn)了ViewStub的功能。但是需要注意的是ViewStub所加載的布局不能使用標(biāo)簽,也就是說(shuō)包含ViewStub控件的布局在進(jìn)行復(fù)用時(shí)會(huì)導(dǎo)致加載出來(lái)的布局存在著多余的嵌套結(jié)構(gòu),但是對(duì)于含有較多隱藏子布局的布局文件來(lái)說(shuō),使用ViewStub還是一種相當(dāng)不錯(cuò)的選擇的,即使增加了一層無(wú)用的布局結(jié)構(gòu),仍然還是利大于弊,具體如何去選擇就要具體情況具體分析了。對(duì)于性能的優(yōu)化需要考慮很多的方面,這里只是簡(jiǎn)單的介紹了性能優(yōu)化中的布局優(yōu)化,作為一個(gè)開(kāi)發(fā)者還需要有的放矢的選擇和權(quán)衡各種方法對(duì)應(yīng)用性能的影響,從而開(kāi)發(fā)出性能優(yōu)越,客戶滿意度高的應(yīng)用。