Android 介面介紹與繪製優化
Andorid使用者介面框架
Android
的使用者介面框架(Android UI Framework
)採用MVC
(Model-View-Controller
)模型,為使用者介面提供了處理使用者輸入的控制器(Controller
)和顯示介面內容的檢視(View
)。其中模型層(Model
)是應用程式的核心,資料和程式碼都被儲存在模型中。
MVC
模型中的檢視將應用程式的資訊反饋給使用者,可能的反饋方法包括視覺、聽覺或者觸覺等,最常用的就是通過螢幕顯示反饋資訊。
Android介面的基本架構
在Android
中,介面是由一顆View
樹來定義的。View
樹的葉子結點是檢視元件,而其他非葉子結點則是容器元件。View
View樹的這種樹型結構也為元件的事件管理,如觸控式螢幕、鍵盤點選事件,提供了很多的便利。可以說元件的事件傳遞機制就是沿著樹,從高層向底層傳遞的。
從上圖中可以看出,一個ViewGroup
可以擁有多個View
,也可以包含多個ViewGroup
。Android
這種非常靈活的View
層次結構可以形成非常複雜的佈局,開發者可以利用這個特性開發出特別精緻的介面。
當呼叫Activity
的setContentView()
方法的時候,可以將一棵樹的根節點或者其子樹的根節點,設定是葉子節點作為引數傳入此方法,這樣系統就獲得了該結點的引數,並將該節點作為根節點來測距和繪製,最終將以該結點為根節點的整棵樹都繪製在介面上。繪製過程中,父節點負責請求其子節點繪製它們自己,子節點可能會請求他們在父節點中的大小和位置,父節點對每個子節點的大小和位置由最終的決定權。
View和ViewGroup
Android
的使用者介面都是由檢視元件(View
)和容器元件(ViewGroup
)組成的。為了便於理解,可以將容器看成是螢幕上的一個矩形的區域,是檢視則是位於該矩形區域內的一個元件。
View
類是Android API中定義的類,位於android.view
包中。一個View
類或者其子類的物件中儲存與該物件相關的佈局和內容屬性等,並且可以實現處理包括測距、佈局、繪圖、焦點變換、滾動條以及螢幕區域內的該物件的按鍵和手勢在內的多種功能。View還為Android中的Widget提供服務。Widget
是Android
的系統包,包含了一組View
類的可例項化子類。這些子類可以用於繪製互動式螢幕元素。使用Widget
ViewGroup
類也是Android API中的類,位於android.view
包中。值得一提的是,ViewGroup
雖然是View
的子類,但是與View類有著完全不同的任何和功能。ViewGroup
,其實是一組View
類物件的集合。實際上,ViewGroup
的功能也確實是這樣的,它負責裝載和管理一組View
,包括View
類的物件和ViewGroup
本身。
ViewGroup的一個重要的子類是佈局(Layout)。Layout可以為一組View建立一個結構。
Android介面的組成元素是View
和ViewGroup
的子類和簡介子類。檢視元件的作用是顯示,而容器元件的作用是管理。
座標系
View自身的座標
方法 | 說明 |
---|---|
getTop() | 獲取View自身頂邊到其父控制元件佈局頂邊的距離。 |
getLeft() | 獲取View自身左邊到其父控制元件佈局左邊的距離。 |
getTop() | 獲取View自身右邊到其父控制元件佈局左邊的距離。 |
getTop() | 獲取View自身底邊到其父控制元件佈局頂邊的距離。 |
MotionEvent提供的方法
上圖中的圓點,就是我們的觸控點(假定)。觸控事件最終都會由onTouchEvent(MotionEvent event)
方法來處理。
方法 | 說明 |
---|---|
getX() | 獲取點選事件距離控制元件左邊的距離,即檢視座標。 |
getY() | 獲取點選事件距離控制元件頂邊的距離,即檢視座標。 |
getRawX() | 獲取點選事件距離整個螢幕左邊的距離,即絕對座標。 |
getRawY() | 獲取點選事件距離整個螢幕頂邊的距離,即絕對座標。 |
繪製過程
檢視展示的系統架構,由檢視根結點(RootView)開始,檢視根結點負責連線視窗管理器(WindowManage)與裝飾檢視(DecorView),視窗管理器用於響應使用者事件,而裝飾檢視用於展示特定影象。
performTravesals()
中包含了以下三個方法:performMeasure()
測量performLayout
佈局performDraw
繪製
Android系統顯示原理
Android
的顯示過程可以簡單概括為:Android
應用程式把經過測量、佈局繪製後的surface
快取資料,通過SurfaceFlinger
把資料渲染到顯示螢幕上,通過Android
的重新整理機制來重新整理資料。也就是說應用層負責繪製,系統層負責渲染,通過程序間通訊把應用層需要繪製的資料傳遞到系統層服務,系統層服務通過重新整理機制把資料更新到螢幕。
繪製原理
繪製任務是由應用發起的,最終通過系統層繪製到硬體螢幕上。也就是說,應用程序繪製好後,通過跨程序通訊機制把需要顯示的內容傳遞到系統層,由系統層的SurfaceFlinger
服務繪製到螢幕上。
應用層
在Android
中每個View
繪製中有三個核心步驟,通過Measure
和Layout
來確定當前需要繪製的View
所在的大小和位置,通過繪製(Draw)到Surface
,在Android系統中整體的繪圖原始碼是在ViewRootImp
類performTraversals()
方法,通過這個方法可以看出Measure
和Layout
都是通過遞迴來獲取View
的大小和位置的,並且以深度作為優先順序。可以看出,層級越深,元素越多,耗時也越長(優化點)。
方法 | 說明 |
---|---|
Measure | 用深度優先原則遞迴得到所有檢視(View)的寬、高;獲取當前View的正確寬度childWidthMeasureSpec 和高度childHeightMeasureSpec 之後,可以呼叫它的成員函式Measure來設定它的大小。如果當前正在測量的子檢視child是一個檢視容器,那麼它又會重複執行操作,直到它的所有子孫檢視的大小都測量完成為止。 |
Layout | 用深度優先原則遞迴得到所有檢視(View)的位置;當一個子View在應用程式視窗左上角的位置確定之後,再結合它在前面測量過程中確定的寬度和高度,就可以完全確定它在應用程式視窗中的佈局。 |
Draw | 目前Android支援了兩種繪製方式:軟體繪製(CPU)和硬體加速(GPU),其中硬體加速在Android3.0開始已經全面支援,很明顯,硬體加速在UI的顯示和繪製的效率遠遠高於CPU繪製。 GPU的缺點: 耗電:GPU的功耗與CPU高。 相容問題:某些介面和函式不支援硬體加速。 記憶體大:使用OpenGL的介面至少需要8MB的記憶體。 |
佈局優化
Android的介面繪製是通過遞迴來繪製介面,因此通過減少Layout層級,減少測量、繪製時間,提高複用性三個方面來優化佈局的時間是非常重要的。
- 儘量使用
RelativeLayout
和LinearLayout
。 - 在佈局層相同的情況下,使用
LinearLayout
。 - 用
LinearLayout
有時會使巢狀層級變多,應該使用RelativeLayout
,使介面儘量扁平化。 - 使用merge標籤消除多餘的層級。
- 如果需要在兩個View之間新增空白,可以使用Space標籤。
- space標籤是一個空白的標籤,主要用於在兩個View之間新增空白。
- 而且Space不佔用繪製資源,因為
Space
控制元件onDraw
方法進行了一個空的實現。
Merge使用注意事項
Merge
只能用在佈局XML檔案的根元素。- 使用
Merge
來載入一個佈局,必須指定一個ViewGroup
作為其父元素,並且要設定載入的attachToRoot
引數為true
(參照inflat(int, ViewGroup, boolean)
) - 不能在
ViewStub
中使用Merge
標籤。原因就是ViewStub
的inflate
方法中根本沒有attachToRoot
的設定。
ViewStub
使用ViewStub
標籤提高顯示速度。
在開發中,我們經常會遇到一個問題,在某一個佈局中的子佈局非常多,但是在App中,又不是所有的佈局都需要顯示(部分顯示),開啟這個介面的時候,會根據不同的場景和屬性顯示不同的Layout。例如:一個頁面對不同的使用者,比如未登入使用者、普通使用者、VIP使用者會顯示不同的頁面。又或者說,有些使用者喜歡對APP介面中的某些元素進行隱藏,比如微信的朋友圈入口,這個時候可能就用到了INVISIBLE
或者GONE
屬性。但是INVISIBLE
或者GONE
屬性的效率非常的低,原因是即使將元素隱藏了,它們仍然在佈局中,仍會測試和解析這些佈局。
這個時候,就可以使用ViewStub來解決。
ViewStub是一個輕量級的View,它是一個看不見的,並且不佔佈局位置,佔用資源非常小的檢視物件。可以為ViewStub指定一個佈局,載入佈局的使用,只有ViewStub會被初始化,然後當ViewStub被設定為可見的時候,或者呼叫了ViewStub.inflate()
的時候,ViewStub所指向的佈局才會被載入和例項化,然後ViewStub的佈局屬性都會傳給它指向的佈局。這樣,就可以使用ViewStub來設定是否顯示某個佈局。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/topBar"
layout="@layout/common_top_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/top_bar_height" />
<ViewStub
android:id="@+id/viewstub_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/viewstub_first" />
<ViewStub
android:id="@+id/viewstub_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/viewstub_second" />
<ViewStub
android:id="@+id/viewstub_default"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/layout_default" />
</LinearLayout>
在呼叫的時候,根據不同的需求切換不同的Layout,這樣可以提高頁面初始化的速度,使用程式碼如下:
View view = view.inflate(R.layout.activity_viewstub, container, false);
switch (choice) {
case First:
ViewStub stub1 = view.findViewById(R.id.viewstub_first);
stub1.inflate();
break;
case Second:
ViewStub stub2 = view.findViewById(R.id.viewstub_second);
stub2.inflate();
break;
default:
ViewStub stub = view.findViewById(R.id.viewstub_default);
stub.inflate();
break;
}
ViewStub顯示有兩種方式,上面的程式碼使用的是inflate方法,也可以直接使用ViewStub.setVisibility(View.Visible)
方法。
注意事項
- 使用
ViewStub
只能載入一次,之後ViewStub物件會被置為空。換句話說,某個被ViewStub指定的佈局被載入後,就不能再通過ViewStub來控制它了。所以它不適用於需要按需顯示隱藏的情況。 ViewStub
只能用來載入一個佈局檔案,而不是某個具體的View,當然也可以把View寫在某個佈局檔案中。如果想操作一個具體的View,還是使用visibility屬性。- ViewStub中不能巢狀Merge標籤。
使用場景
- 在程式執行期間,某個佈局在載入後,就不會有變化,除非銷燬該頁面重新載入。
- 想要控制顯示與隱藏的是一個佈局檔案,而非某個View。
- 因為ViewStub只能Inflate一次,之後就會被置空,無法繼續使用ViewStub來控制佈局。所以需要在執行時不止一次顯示和隱藏某個佈局的時候,使用ViewStub是無法實現的。這時只能使用View的可見性來控制。
延遲載入
延遲載入的功能非常重要,特別是介面中顯示的內容比較多並且所佔空間比較大的時候。在Android應用程式中,可以使用ViewStub
實現延遲載入的功能。
當呼叫ViewStub
的setVisbility()
函式設定為可見或者呼叫inflate
初始化該View的時候,ViewStub
引用的資源開始初始化,然後引用的資源填充在ViewStub
所在的位置上。在沒有呼叫setVisibility(int)
函式或者inflate()
函式之前,ViewStub
一直存在元件樹層級結構中。但是由於ViewStub
非常輕量級,所以對效能的影響非常小。
影響佈局效率的主要原因
提高佈局效率的方法總體來說就是減少層級,提高繪製速度和佈局複用。
影響佈局效率的主要原因如下:
- 佈局的層級越少,載入速度越快。
- 減少同一級控制元件的數量,載入速度會變快。
- 一個控制元件的屬性越少,解析越快。
Android繪製優化
- 儘量多使用RelativeLayout或者LinearLayout,不要使用絕對佈局AbsoluteLayout。
- 將可複用的元件抽取出來並通過
<include/>
標籤使用。 - 使用
<ViewStub/>
標籤載入一些不常用的佈局。 - 使用
<merge/>
標籤減少佈局的巢狀層次。 - 儘可能少用
wrap_content
,wrap_content
會增加布局Measure時的計算成本,已知寬高為固定值的時候,不用wrap_content
。 - 刪除控制元件中的無用屬性。
附錄
- 《Android應用效能優化最佳實踐》
- 羅彧成
- 《煮酒論Android》
- 原始人工作室
- 《Android進階之光》
- 劉望舒
- 《Android應用開發實戰詳解》
- 王翠萍