標籤:
1.Android UI的渲染機制
當我們感覺到的流暢畫面,需要的畫面幀數要達到40幀到60幀每秒。而一幀的時間大約是16.67ms,換句話說,在1000ms的時間內,16.67ms大約就是現實60幀畫面的單位時間。在Android系統中,系統是通過VSYNC訊號觸發對UI的渲染的,如果系統每次渲染的事件都保持在16.67ms以內,那麼我們看到的UI介面將是非常的流暢的,這也就需要我們將所有程式的邏輯都保證在16ms之內,如果不能在16ms內完成繪製,那麼就將造成丟幀的現象。即當前該重繪的幀被未處理完成的邏輯阻塞,例如一次繪製任務耗時20ms,那麼在16ms系統發出的VSYNC訊號就無法繪製,該幀就會被丟棄,等待下次訊號擦次開始繪製,這就是畫面卡頓的原因。
Android系統提供了檢測UI渲染時間的工具,在“開發人員選項中”有“GPU呈現模式分析”,選擇“在螢幕上顯示為橫條圖”,如下所示(本測試機為魅族,其他手機可能略有不同):
每一個橫條圖都包含有三部分,藍色部分表示測量繪製Display List的時間,紅色代表的是OpenGL渲染Display List所需要的時間,黃色代表的是CPU等待GPU處理的時間,中間的綠色橫線代表的是VSYNC時間16ms,需要盡量將所有橫條圖都控制在這條綠線之下。
2.overDraw
overDraw表示的就是過度繪製,是指在一幀的時間內(16.67ms)像素被繪製了多次,理論上一個像素每次只繪製一次是最優的,但是由於重疊的布局導致一些像素會被多次繪製,而每次繪製都會對應到CPU的一組繪圖命令和GPU的一些操作,造成CPU和GPU資源的浪費。在系統預設的繪製Activity的背景,如果再給布局繪製了重疊的背景,那麼預設Activity的背景就是無效的過度繪製。
在我們的“開發人員選項”中有這樣一個偵查工具“調用GPU過度繪製”,啟用該功能之後可以通過介面上的顏色來判斷overDraw的次數。
這個工具可以協助我們檢測目前範圍的繪製次數,從而最佳化介面繪圖層次,盡量增大藍色的地區,減少紅色的地區。
3.最佳化布局層次
在Android中,系統對View進行測量,布局和繪製時,都是通過對View數的遍曆來進行操作的。如果一個View樹的高度太高,就會嚴重影響到測量,布局和繪製的速度,因此,最佳化布局的第一個方法就會是降低View樹的高度,Google也在API文檔中建議View樹的高度不宜超過10層。在現在的XML檔案的根布局中,我們預設RelativeLayout來替換使用LineraLayout作為預設的根布局,其原因就是通過扁平的RelativeLayout來降低LineraLayout嵌套所產生的布局樹的高度,從而提高UI的渲染速度。
1.使用 include 標籤重用Layout
在一個應用程式的介面上,為了保持風格的統一,很多介面都會存在共通的UI,比如所說Topbar,Bottombar,Actionbar等等,如果在每一個介面上都進行賦值這一段共通的布局代碼,不僅不利於後期代碼的維護,還會增加程式的冗餘。這時候就可以使用include標籤來定義一這一個共通的UI。
<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="0dp" android:layout_height="0dp" android:textSize="28sp" android:text="這是共通的UI介面" android:gravity="center"></TextView>
在該共通的布局中,我將layout_width和layout_height設定為0dp,這樣就迫使調用者在使用時必須對控制項的狂傲進行賦值,否者是無法看見該控制項的。
下一步就是如何使用該共通的UI布局了。只需要在使用該共通的UI布局檔案中使用include標籤的layout屬性對這個共通的UI的ID的引用即可。
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/commen_ui" android:layout_width="match_parent" android:layout_height="wrap_content"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:textSize="20sp" android:text="你好你好你好"/></LinearLayout>
這時候如果我們要對布局中的屬性進行賦值,就要重新覆蓋某一項屬性,進行賦值即可。效果如下:
2. 使用ViewStub實現view的消極式載入
使用ViewStub標籤來實現對一個view的引用並且實現消極式載入。ViewStub是一個非常輕量級的組件,它不僅不可視,而且大小為0,下面來示範如何使用ViewStub來進行實現消極式載入的目的。
首先建立一個布局,這個布局在初始化載入時是不需要顯示的,只有在某些情況下才需要進行顯示的,例如查看使用者資訊的時候,只有點擊了某一個按鈕時,使用者詳細資料才顯示出來。
當運行程式後,我們發現ViewStub中的布局確實沒有顯示出來,那麼要如何才能重新載入顯示的布局呢?
首先要通過findViewById()方法找到組件ViewStub。
mStub = (ViewStub) findViewById(R.id.vs_viewstub);
接下來就有兩種方式顯示這個view:
- VISIBLE
通過調用ViewStub的setVisiblity()方法來顯示這個view.代碼如下:
mStub.setVisibility(View.VISIBLE);
- INFLATE
通過調用ViewStub的inflate()方法來顯示這個view.代碼如下:
View inflate = mStub.inflate();
這兩種方式都是可以將ViewStub重新進行展開,顯示引用的布局,而唯一的區別在於就是inflate()可以返回引用的布局,從而可以通過View.findViewById()方法來找到對應的控制項。
View inflate = mStub.inflate(); TextView textview = (TextView) inflate.findViewById(R.id.textview); textview.setText("我是點擊後載入的");
注意:不管只用那種方式,一旦ViewStub被設定可見或者是inflate之後,ViewStub就不存在了,不能被反覆的inflate,取而代之的就是被inflate的Layout,並將這個Layout的ID重新設定為ViewStub中通過android:inflateId屬性所指定的ID,這也就是為什麼兩次點擊之後會報錯的原因:如下所示:
Caused by: java.lang.IllegalStateException: ViewStub must have a non-null ViewGroup viewParent
簡要說明View.GONE和ViewStub標籤的區別是什嗎?共同點都是初始時都不會顯示,但是ViewStub標籤只會在顯示時才會去渲染整個布局,而View.GONE,在初始化布局樹的時候就已經添加在布局樹檔案中了,相比之下ViewStub的效率會更高。
如所示:
4.hierarchyviewer.bat
hierarchyviewer.bat是無法在真機上進行使用的,只能在模擬器上使用或者是原生的模擬器上使用,也就是沒有加密的裝置上。當然真機上也是可以用的,可以到github上下載一個開源項目View Server,下面在模擬器上使用hierarchyviewer.bat。
hierarchyviewer.bat位於sdk\tools目錄下,直接雙擊即可啟動,如下:
注意:我們在使用hierarchyviewer的時候,一定要是模擬器是開啟的,這樣才能在hierarchyviewer中看到我們要顯示的布局檔案。
下面我們寫一個非常冗餘的布局進行顯示檔案,代碼如下所示:
<?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"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="你好啊啊 啊啊啊啊 啊" android:textColor="#efff0019" android:textSize="20sp"/> </LinearLayout> </LinearLayout></LinearLayout>
這個布局檔案是三層LinearLayout嵌套之后里面裝了一個button,很顯然這些LinearLayout都是冗餘的,利用hierarchyviewer可以開啟這個布局檔案,如所示:
通常情況下,我們只關注ID為content的Framlayout的分支,這也是setContentView()設定的內容,可以很明顯的看出在layout布局檔案中(紅色布局),這三層LinearLayout沒有任何的分支,說明是冗餘嵌套,可以直接去掉的。
當點擊其中一個view的時候,可以顯示view的繪製情況的,不過第一次點擊的時候各種顯示的事件都是n/a,需要點擊菜單中的Profile Node按鈕重新進行計算,才能回去到繪製資訊。在系統的右下方會給出不同顏色的小圓點,用來表示繪製的效率,綠黃紅分別代表的是好中差的繪製效率。
Android的效能最佳化(上)