Android應用開發(fā)中View繪制的一些優(yōu)化點解析
一個通常的錯誤觀念就是使用基本的布局結(jié)構(gòu)(例如:LinearLayout、FrameLayout等)能夠在大多數(shù)情況下
產(chǎn)生高效率 的布局。 顯然,你的應用程序里添加的每一個控件和每一個布局都需要初始化、布局(layout)、
繪制 (drawing)。舉例來說:嵌入一個LinearLayout會產(chǎn)生一個太深的布局層次。更嚴重的是,嵌入幾個使
用 layout_weight屬性的LinearLayout 將會導致大量的開銷,因為每個子視圖都需要被測量兩次。這是反復解析
布局文件時重要的一點,例如在ListView或者GridView中使用時。
觀察你的布局
Android SDK 工具箱包括一個稱作“ Hierarchy Viewer”的工具,它允許你去在你的應用程序運行時分析
布局。通過使用這個工具,能幫助你發(fā)現(xiàn)你的布局效率上的瓶頸問題。
“ Hierarchy Viewer”工具允許你在已連接的設(shè)備或模擬器中選擇正在運行的進程,然后呈現(xiàn)出布局層次樹
(layout tree)。每個正方塊下的交通燈(見下圖) --- 紅綠藍表現(xiàn)出了在測量(measure)、布局(layout)、以及繪制
(draw)過程中的效率值,這能幫助你定位潛在的問題。
假設(shè)ListView 中的一項Item 存在如下(見圖 1)布局 :
“Hierarchy Viewer”工具可以在 <sdk>/tools路徑下找到。當打開它時,“ Hierarchy Viewer”工具顯示了
所有可用的設(shè)備以及運行在這些設(shè)備上的進程。點擊”Load View Hierarchy”來顯示某個你選擇的組件的UI布局
層次。舉例來說,圖2展現(xiàn)了圖1的布局層次樹。
在圖2中,你可以直觀看到這個三層的布局結(jié)構(gòu)是存在一些問題的。點擊項體現(xiàn)出了在每個測量(measure)、
布局(layout)、以及繪制(draw)過程中的時間消耗(見圖3)。很明顯,該項(LinearLayout)花費了最長的時間去
測量、布局、繪制,你應該花點精力去優(yōu)化它們。
完成該布局文件渲染的時間分別為:
測量過程:0.977ms
布局過程: 0.167ms
繪制過程:2.717ms
修改布局文件
由于上圖中布局效率的低下是因為一個內(nèi)嵌的 LinearLayout控件,通過扁平化布局文件----讓布局變得
更淺更寬,而不是變得更窄更深層次 ,這樣就能提升效率了。 一個RelativeLayout 作為根節(jié)點也能提供如上
的布局效果(即圖1)。 因此, 使用RelativeLayout 改變布局的設(shè)計,你可以看到現(xiàn)在我們的布局層次只有2層了。
新的布局層次樹如下:
現(xiàn)在,完成該布局文件渲染的時間分別為:
測量過程:0.977ms
布局過程:0.167ms
繪制過程:2.717ms
也許它只是一點點微小的改進,但這次它會被多次調(diào)用,因為是ListView會布局所有的Item,累積起來,
改進后效果還是非??捎^地。
大部分的時間差異是由于使用了帶有l(wèi)ayout_weight 屬性的LinearLayout ,它能減緩測量過程的速度。這僅僅
是一個例子,即每個布局都應該合適地被使用以及你應該認真考慮是否有必要采用“l(fā)ayout_weight" 屬性。
使用Lint工具
一個好的實踐就是在你的布局文件中使用Lint工具去尋求可能優(yōu)化布局層次的方法。Lint已經(jīng)取代了Layoutopt
工具并且它提供了更強大的功能。一些Lint規(guī)則如下:
1、使用組合控件 --- 包含了一個 ImageView 以及一個 TextView 控件的 LinearLayout 如果能夠作為一個
組合控件將會被更有效的處理。
2、合并作為根節(jié)點的幀布局(Framelayout) ----如果一個幀布局時布局文件中的根節(jié)點,而且它沒有背景圖片
或者padding等,更有效的方式是使用<merge />標簽替換該< Framelayout />標簽 。
3、無用的葉子節(jié)點----- 通常來說如果一個布局控件沒有子視圖或者背景圖片,那么該布局控件時可以被移除
(由于它處于 invisible狀態(tài))。
4、無用的父節(jié)點 ----- 如果一個父視圖即有子視圖,但沒有兄弟視圖節(jié)點,該視圖不是ScrollView控件或者
根節(jié)點,并且它沒有背景圖片,也是可以被移除的,移除之后,該父視圖的所有子視圖都直接遷移至之前父視圖
的布局層次。同樣能夠使解析布局以及布局層次更有效。
5、過深的布局層次 ----內(nèi)嵌過多的布局總是低效率地??紤]使用一些扁平的布局控件,例如 RelativeLayout、
GridLayout ,來改善布局過程。默認最大的布局深度為10 。
當使用Eclipse環(huán)境開發(fā)時,Lint能夠自動解決一些問題,提供一些建議以及直接跳轉(zhuǎn)到出錯的代碼中去核查。
如果你沒有使用Eclipse,Lint也可以通過命令行的方式運行。
使用<include />標簽復用布局文件
盡管Android通過內(nèi)置了各種各樣的控件提供了微小、可復用的交互性元素,也許你需要復用較大的
組件 ---- 某些特定布局文件 。為了更有效率復用的布局文件,你可以使用<include />以及<merge />
標簽將其他的布局文件加入到當前的布局文件中。
復用布局文件是一種特別強大的方法,它允許你創(chuàng)建可復用性的布局文件。例如,一個包含“Yse”or“No”的
Button面版,或者是帶有文字說明的 Progressbar。復用布局文件同樣意味著你應用程序里的任何元素都能從
繁雜的布局文件提取出來進行單獨管理,接著你需要做的只是加入這些獨立的布局文件(因為他們都是可復用地)。
因此,當你通過自定義View創(chuàng)建獨立的UI組件時,你可以復用布局文件讓事情變得更簡單。
1、創(chuàng)建一個可復用性的布局文件
如果你已經(jīng)知道復用布局的”面貌”,那么創(chuàng)建、定義布局文件( 命名以”.xml”為后綴)。例如,這里是一個來自
G- Kenya codelab 的布局文件,定義了在每個Activity中都要使用的一個自定義標題 (titlebar.xml):由于這些
可復用性布局被添加至其他布局文件中,因此,它的每個根視圖(root View)最好是精確(exactly)的。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width=”match_parent” android:layout_height="wrap_content" android:background="@color/titlebar_bg"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/gafricalogo" /> </FrameLayout>
2、使用<include />標簽
在需要添加這些布局的地方,使用<include />標簽 。 例如,下面是一個來自G-Kenya codelab的布局文件,
它復用了上面列出的“title bar”文件, 該布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width=”match_parent” android:layout_height=”match_parent” android:background="@color/app_bg" android:gravity="center_horizontal"> <include layout="@layout/titlebar"/> <TextView android:layout_width=”match_parent” android:layout_height="wrap_content" android:text="@string/hello" android:padding="10dp" /> ... </LinearLayout>
你也可以在<include />節(jié)點中為被添加的布局文件的root View定義特別標識,重寫所有l(wèi)ayout參數(shù)即可(任何
以“android:layout_”為前綴的屬性)。例如:
<include android:id=”@+id/news_title” android:layout_width=”match_parent” android:layout_height=”match_parent” layout=”@layout/title”/>
3、使用<merge />標簽
當在布局文件中復用另外的布局時, <merge />標簽能夠在布局層次消除多余的視圖元素。例如,如果你的
主布局文件是一個垂直地包含兩個View的LinearLayout,該布局能夠復用在其他布局中,而對任意包含兩個View的
布局文件都需要一個root View(否則, 編譯器會提示錯誤)。然而,在該可復用性布局中添加一個LinearLayout
作為root View,將會導致一個垂直的LinearLayout包含另外的垂直LinearLayout。內(nèi)嵌地LinearLayout只能減緩
UI效率,其他毫無用處可言。
該復用性布局利用.xml呈現(xiàn)如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width=”match_parent” android:layout_height=”match_parent” android:background="@color/app_bg" android:gravity="horizontal"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/add"/> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/delete"/> </LinearLayout>
為了避免冗余的布局元素,你可以使用<merge />作為復用性布局文件地root View 。例如:
使用<merge />標簽的布局文件:
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/add"/> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/delete"/> </merge>
現(xiàn)在,當你添加該布局文件時(使用<include />標簽),系統(tǒng)忽略< merge />節(jié)點并且直接添加兩個Button去
取代<include />節(jié)點。
越少越好
為了加速視圖,從那些調(diào)用頻繁的活動中減少不必要的代碼。在OnDraw()方法中開始繪制,它會給你最大的
效益。特別低,你也應該減少在onDraw()方法中的內(nèi)存分配,因為任何內(nèi)存分配都可能導致內(nèi)存回收,這將會
引起不連貫。 在初始化或者動畫之間分配對象。絕不要在動畫運行時分配內(nèi)存。
另一方面需要減少onDraw()方法中的開銷,只在需要時才調(diào)用onDraw()方法。通常invalidate()方法會調(diào)用
onDraw()方法,因此減少對invalidate()的不必要調(diào)用。如果可能,調(diào)用它的重載版本即帶有參數(shù)的invalidate()
方法而不是無參的invalidate()方法。該帶參數(shù)的方法invalidate()能使draw過程更有效,以及減少對落在該矩形
區(qū)域(參數(shù)指定的區(qū)域)外視圖的不必要重繪 。
注,invalidate()的三個重載版本為:
1 、public void invalidate (Rect dirty)
2、public void invalidate (int l, int t, int r, int b)
3、public void invalidate ()
另外的一個高代價的操作是布局過程(layout)。 任何時刻對View調(diào)用requestLayout()方法,Android UI 框架
都需要遍歷整個View樹,確定每個視圖它們所占用的大小。如果在measure過程中有任何沖突,可能會多次遍歷
View樹。UI設(shè)計人員有時為了實現(xiàn)某些效果,創(chuàng)建了較深層次的ViewGroup。但這些深層次View樹會引發(fā)效率
問題。確保你的View樹層次盡可能淺。
如果你有的UI設(shè)計是復雜地,你應該考慮設(shè)計一個自定義ViewGroup來實現(xiàn)layout過程。不同于內(nèi)置View控件,
自定義View能夠假定它的每個子View的大小以及形狀,同時能夠避免為每個子View進行measure過程。 PieChart
展示了如何繼承ViewGroup類。 PieChart帶有子View,但它從來沒有measure它們。相反,它根據(jù)自己的布局算法
去直接設(shè)置每個子View的大小。
如下代碼所示:
/** * Custom view that shows a pie chart and, optionally, a label. */ public class PieChart extends ViewGroup { ... // // Measurement functions. This example uses a simple heuristic: it assumes that // the pie chart should be at least as wide as its label. // @Override protected int getSuggestedMinimumWidth() { return (int) mTextWidth * 2; } @Override protected int getSuggestedMinimumHeight() { return (int) mTextWidth; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on our minimum int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = Math.max(minw, MeasureSpec.getSize(widthMeasureSpec)); // Whatever the width ends up being, ask for a height that would let the pie // get as big as it can int minh = (w - (int) mTextWidth) + getPaddingBottom() + getPaddingTop(); int h = Math.min(MeasureSpec.getSize(heightMeasureSpec), minh); setMeasuredDimension(w, h); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // Do nothing. Do not call the superclass method--that would start a layout pass // on this view's children. PieChart lays out its children in onSizeChanged(). } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // // Set dimensions for text, pie chart, etc // // Account for padding ... // Lay out the child view that actually draws the pie. mPieView.layout((int) mPieBounds.left, (int) mPieBounds.top, (int) mPieBounds.right, (int) mPieBounds.bottom); mPieView.setPivot(mPieBounds.width() / 2, mPieBounds.height() / 2); mPointerView.layout(0, 0, w, h); onDataChanged(); } }
使用硬件加速
Android 3.0版本后,Android 2D圖形庫能在大多數(shù)Android設(shè)備上使用GPU(圖形處理單元)加速。GPU硬件
加速可以極大的優(yōu)化多數(shù)應用程序,但它并不是每個應用程序的最優(yōu)選擇。Android框架給予你是否在應用程序中
使用硬件加速的控制力。
值得注意的是,我們必須手動在配置文件中設(shè)置應用程序API級別為11或者更高級別,即在 AndroidManifest.xml進行如下配置:
<uses-sdk android:targetSdkVersion="11"/>
一旦你開啟了硬件加速,你可能看不到效率的提升。Mobile GPUs 善于處理特定的任務(wù),例如:伸縮、旋轉(zhuǎn)、
平移圖片。它也有一些不擅長處理的任務(wù),例如:繪制直線或曲線。常言道物盡其用,揚長避短,盡可能讓GPU
處理它擅長的任務(wù),減少讓其處理弱勢任務(wù)的。
在PieChart 示例中,例如,相對來說繪制一個圓形是比較耗費資源的。每次旋轉(zhuǎn)引起的重繪導致UI的遲緩。
解決辦法就是讓View來呈現(xiàn)該圓形,并且設(shè)置該View的layer type屬性為 LAYER_TYPE_HARDWARE,因此GPU
能夠緩存靜態(tài)圖片。示例中該View作為 PieChart類的內(nèi)部類存在,減少了為了實現(xiàn)這個方法的代碼開銷。
private class PieView extends View { public PieView(Context context) { super(context); if (!isInEditMode()) { setLayerType(View.LAYER_TYPE_HARDWARE, null); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (Item it : mData) { mPiePaint.setShader(it.mShader); canvas.drawArc(mBounds, 360 - it.mEndAngle, it.mEndAngle - it.mStartAngle, true, mPiePaint); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mBounds = new RectF(0, 0, w, h); } RectF mBounds; }
改變之后,只有View第一次顯示的時候才會調(diào)用PieChart.PieView.onDraw()方法。在應用程序的其他
時間,繪制的圖像將會作為圖片緩存,重繪時GPU將任意旋轉(zhuǎn)圖像。
然而這只是一個折中手段。緩存圖片作為硬件層導致 video memory開銷,video memory卻是一種受限制的
資源。 出于這個原因,在PieChart.PieView的最終版本上,只有在用戶滑動時才設(shè)置它的layer type屬性為
LAYER_TYPE_HARDWARE。在其他時間,僅僅設(shè)置它的layer type屬性為 LAYER_TYPE_HARDWARE,這
允許GPU停止緩存圖片。
最后,不要忘記分析你的代碼。在一個View上做的優(yōu)化技術(shù)可能會在其他View上產(chǎn)生不好的影響。
相關(guān)文章
Android實現(xiàn)圖片自動切換功能(實例代碼詳解)
這篇文章主要介紹了Android實現(xiàn)圖片自動切換功能,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02Android筆記之:App應用之啟動界面SplashActivity的使用
當前比較成熟一點的應用基本上都會在進入應用之顯示一個啟動界面.這個啟動界面或簡單,或復雜,或簡陋,或華麗,用意不同,風格也不同2013-04-04Android TextView高級顯示技巧實例小結(jié)
這篇文章主要介紹了Android TextView高級顯示技巧,結(jié)合實例形式總結(jié)分析了Android TextView控件進行文字與圖片顯示的相關(guān)操作技巧,需要的朋友可以參考下2016-10-10Android下保存簡單網(wǎng)頁到本地(包括簡單圖片鏈接轉(zhuǎn)換)實現(xiàn)代碼
這篇文章主要介紹了Android下保存簡單網(wǎng)頁到本地(包括簡單圖片鏈接轉(zhuǎn)換)實現(xiàn)代碼,需要的朋友可以參考下2014-02-02android實現(xiàn)自動滾動的Gallary控件效果
這篇文章主要介紹了android實現(xiàn)自動滾動的Gallary控件效果,涉及Android中Gallary控件的相關(guān)使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10詳解Android App中的AsyncTask異步任務(wù)執(zhí)行方式
這篇文章主要介紹了Android App中的AsyncTask異步任務(wù)執(zhí)行方式,文中舉了一個打開網(wǎng)絡(luò)圖片的例子幫助大家直觀理解,需要的朋友可以參考下2016-04-04Android調(diào)用系統(tǒng)自帶瀏覽器打開網(wǎng)頁的實現(xiàn)方法
在Android中可以調(diào)用自帶的瀏覽器,或者指定一個瀏覽器來打開一個鏈接。只需要傳入一個uri,可以是鏈接地址。接下來通過本文給大家分享android 自帶瀏覽器打開網(wǎng)頁的實現(xiàn)方法,需要的朋友參考下吧2017-09-09Android AIDL中Map參數(shù)傳遞的問題詳解
這篇文章主要給大家介紹了關(guān)于Android AIDL中Map參數(shù)傳遞問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友下面來一起看看吧。2017-12-12