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繪制
Created with Rapha?l 2.2.0開始performTravesals()performMeasure()performLayout()performDraw()結束
- 以下圖片轉載自:Android應用層View繪制流程與源碼分析
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層級,減少測量、繪制時間,提高復用性三個方面來優化布局的時間是非常重要的。
- space標簽是一個空白的標簽,主要用于在兩個View之間添加空白。
- 而且Space不占用繪制資源,因為Space控件onDraw方法進行了一個空的實現。
Merge使用注意事項
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"><includeandroid:id="@+id/topBar"layout="@layout/common_top_bar"android:layout_width="match_parent"android:layout_height="@dimen/top_bar_height" /><ViewStubandroid:id="@+id/viewstub_first"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout="@layout/viewstub_first" /><ViewStubandroid:id="@+id/viewstub_second"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout="@layout/viewstub_second" /><ViewStubandroid: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應用開發實戰詳解》
- 王翠萍
總結
以上是生活随笔為你收集整理的Android 界面介绍与绘制优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 6.47.2 Extended Asm
- 下一篇: BlackBerry Pushmail介