經過《Android性能優化(一)之啓動加速35%》咱們得到了閃電般的App啓動速度,那麼在應用啓動完畢以後,UI佈局也會對App的性能產生比較大的影響,若是佈局寫得糟糕,顯而易見App的表現不可能流暢。android
那麼本文我一樣基於實際案例,針對應用的佈局進行優化進而提高App性能。性能優化
根據Google官方出品的Android性能優化典範,60幀每秒是目前最合適的圖像顯示速度,事實上絕大多數的Android設備也是按照每秒60幀來刷新的。爲了讓屏幕的刷新幀率達到60fps,咱們須要確保在時間16ms(1000/60Hz)內完成單次刷新的操做(包括measure、layout以及draw),這也是Android系統每隔16ms就會發出一次VSYNC信號觸發對UI進行渲染的緣由。微信
若是整個過程在16ms內順利完成則能夠展現出流暢的畫面;然而因爲任何緣由致使接收到VSYNC信號的時候沒法完成本次刷新操做,就會產生掉幀的現象,刷新幀率天然也就跟着降低(假定刷新幀率由正常的60fps降到30fps,用戶就會明顯感知到卡頓)。markdown
理論上一個像素每次只繪製一次是最優的,可是因爲重疊的佈局致使一些像素會被屢次繪製,Overdraw由此產生。網絡
咱們能夠經過調試工具來檢測Overdraw:設置——開發者選項——調試GPU過分繪製——顯示過分繪製區域。工具
原色 – 沒有過分繪製 – 這部分的像素點只在屏幕上繪製了一次。
藍色 – 1次過分繪製– 這部分的像素點只在屏幕上繪製了兩次。
綠色 – 2次過分繪製 – 這部分的像素點只在屏幕上繪製了三次。
粉色 – 3次過分繪製 – 這部分的像素點只在屏幕上繪製了四次。
紅色 – 4次過分繪製 – 這部分的像素點只在屏幕上繪製了五次。oop
在實際項目中,通常認爲藍色便是能夠接受的顏色。佈局
咱們來看一個簡單卻隱藏了不少問題的界面,App的設置界面。在沒有優化以前打開Overdraw調試,能夠看到界面大多數是嚴重的紅色:見下圖。post
貼出這個佈局的代碼性能
<?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:background="#F1F0F0" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@color/white" android:orientation="vertical"> <RelativeLayout android:id="@+id/update_phone" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="修改手機號" android:textColor="#FF555555" android:textSize="14sp" /> <ImageView android:id="@+id/update_phone_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow_right" /> <ImageView android:id="@+id/update_phone_dot" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:layout_toLeftOf="@id/update_phone_iv" android:src="@drawable/message_logo_red" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginLeft="10dp" android:background="#FFDDDDDD" /> <RelativeLayout android:id="@+id/setting_lv_forgetPassword" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="找回密碼" android:textColor="#FF555555" android:textSize="14sp" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow_right" /> </RelativeLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@color/white" android:orientation="vertical"> <RelativeLayout android:id="@+id/privacy_setting" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="隱私設置" android:textColor="#FF555555" android:textSize="14sp" /> <ImageView android:id="@+id/privacy_setting_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow_right" /> <ImageView android:id="@+id/privacy_setting_dot" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:layout_toLeftOf="@id/privacy_setting_iv" android:src="@drawable/message_logo_red" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginLeft="10dp" android:background="#FFDDDDDD" /> <RelativeLayout android:id="@+id/setting_lv_messageSetting" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="@string/accountSetting_messageSetting" android:textColor="#FF555555" android:textSize="14sp" /> <CheckBox android:id="@+id/setting_checkbox_c_messageSetting" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:checked="true" /> </RelativeLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@color/white" android:orientation="vertical"> <RelativeLayout android:id="@+id/setting_lv_feedback_m" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="@string/accountSetting_feedback" android:textColor="#FF555555" android:textSize="14sp" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow_right" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginLeft="10dp" android:background="#FFDDDDDD" /> <RelativeLayout android:id="@+id/setting_lv_score" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="@string/accountSetting_score" android:textColor="#FF555555" android:textSize="14sp" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow_right" /> </RelativeLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_marginLeft="10dp" android:background="#FFDDDDDD" /> <RelativeLayout android:id="@+id/setting_lv_aboutus" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="@string/about_us" android:textColor="#FF555555" android:textSize="14sp" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow_right" /> </RelativeLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@color/white" android:orientation="vertical"> <RelativeLayout android:id="@+id/setting_lv_changeStatus" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/white" android:paddingLeft="10dp" android:paddingRight="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@color/white" android:text="我要招人" android:textColor="#FF555555" android:textSize="14sp" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow_right" /> </RelativeLayout> </LinearLayout> <Button android:id="@+id/setting_btn_exitLogin" android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginTop="30dp" android:background="@color/white" android:gravity="center" android:text="@string/me_exitbtn" android:textColor="#FFFF5A5A" android:textSize="16sp" /> </LinearLayout>複製代碼
分析佈局可知:多層佈局重複設置了背景色致使Overdraw。
那麼咱們結合產品的需求(任何不結合具體場景優化都是耍流氓):
備註:一個容易忽略的點是咱們的Activity使用的Theme可能會默認的加上背景色,不須要的狀況下能夠去掉。
去掉背景色以後再看一下Overdraw;
對比一下優化後的佈局的顏色,能夠看出Overdraw降到了能夠接受的程度。
備註:有些過分繪製都是不可避免的,須要結合具體的佈局場景具體分析。
由此獲得結論:那麼隨着控件數量越多、佈局嵌套層次越深,展開佈局花費的時間幾乎是線性增加,性能也就越差。
幸運的是,咱們有Hierarchy Viewer這個方即可視化的工具,能夠獲得:樹形結構總覽、佈局view、每個View(包含子View)繪製所花費的時間及View總個數。
備註: Hierarchy Viewer不能鏈接真機的問題能夠經過ViewServer這個庫解決;
使用Hierarchy Viewer來看查看一下設置界面,能夠從下圖中獲得設置界面的一些數據及存在的問題:
優化方案:
如今咱們再使用Hierarchy Viewer來檢測一下:
優化後:
1. 控件數量從85個減小到26個,減小69%;
2. 繪製時間從17.8ms減小到14.756ms,下降17%;
總結:
1. 一樣的UI效果可使用不一樣的佈局來完成,咱們須要考慮使用少的嵌套層次以及控件個數來完成,例如設置界面的普通一行,能夠像以前同樣使用RelativeLayout嵌套TextView以及ImageView來實現,可是明顯只使用TextView來作:嵌套層次、控件個數都更少。
2. 優化過程當中使用低端手機更易發現瓶頸;
根據Android性能優化典範,打開設備的GPU配置渲染工具——》在屏幕上顯示爲條形圖,能夠協助咱們定位UI渲染問題。
從Android M版本開始,GPU Profiling工具把渲染操做拆解成以下8個詳細的步驟進行顯示。
- Swap Buffers:表示處理任務的時間,也能夠說是CPU等待GPU完成任務的時間,線條越高,表示GPU作的事情越多;
- Command Issue:表示執行任務的時間,這部分主要是Android進行2D渲染顯示列表的時間,爲了將內容繪製到屏幕上,Android須要使用Open GL ES的API接口來繪製顯示列表,紅色線條越高表示須要繪製的視圖更多;
- Sync & Upload:表示的是準備當前界面上有待繪製的圖片所耗費的時間,爲了減小該段區域的執行時間,咱們能夠減小屏幕上的圖片數量或者是縮小圖片的大小;
- Draw:表示測量和繪製視圖列表所須要的時間,藍色線條越高表示每一幀須要更新不少視圖,或者View的onDraw方法中作了耗時操做;
- Measure/Layout:表示佈局的onMeasure與onLayout所花費的時間,一旦時間過長,就須要仔細檢查本身的佈局是否是存在嚴重的性能問題;
- Animation:表示計算執行動畫所須要花費的時間,包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦這裏的執行時間過長,就須要檢查是否是使用了非官方的動畫工具或者是檢查動畫執行的過程當中是否是觸發了讀寫操做等等;
- Input Handling:表示系統處理輸入事件所耗費的時間,粗略等於對事件處理方法所執行的時間。一旦執行時間過長,意味着在處理用戶的輸入事件的地方執行了複雜的操做;
- Misc Time/Vsync Delay:表示在主線程執行了太多的任務,致使UI渲染跟不上vSync的信號而出現掉幀的狀況;出現該線條的時候,能夠在Log中看到這樣的日誌:
備註:GPU配置渲染工具雖然能夠定位出問題發生在某個步驟,可是並不能定位到具體的某一行;當咱們定位到某個步驟以後可使用工具TraceView進行更加詳細的定位。TraceView的使用能夠參照《Android性能優化(一)之啓動加速35%》。
merge能夠用來合併佈局,減小布局的層級。merge多用於替換頂層FrameLayout或者include佈局時,用於消除由於引用佈局致使的多餘嵌套。
例如:須要顯示一個Button,佈局以下;
<?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"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Merge標籤演示" /> </LinearLayout>複製代碼
咱們經過UiAutoMatorViewer(無需root,相比Hierarchy Viewer只能查看佈局層次,不能獲得繪製時間)看一下佈局的層次
咱們使用Merge標籤對代碼進行修改;
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Merge標籤演示" /> </merge>複製代碼
再看下佈局的層次:
能夠看到使用Merge標籤進行優化以後佈局嵌套就少了一層,Button做爲父視圖第三層FrameLayout的直接子視圖。
注意:merge標籤經常使用於減小布局嵌套層次,可是隻能用於根佈局。
推遲建立對象、延遲初始化,不只能夠提升性能,也能夠節省內存(初始化對象不被建立)。Android定義了ViewStub類,ViewStub是輕量級且不可見的視圖,它沒有大小,沒有繪製功能,也不參與measure和layout,資源消耗很是低。
一、
<ViewStub android:id="@+id/mask" android:layout="@layout/b_me_mask" android:layout_width="match_parent" android:layout_height="match_parent" />複製代碼
ViewStub viewStub = (ViewStub)view.findViewById(R.id.mask);
viewStub.inflate();複製代碼
App裏常見的視圖如蒙層、小紅點,以及網絡錯誤、沒有數據等公共視圖,使用頻率並不高,若是每一次都參與繪製實際上是浪費資源的,均可以藉助ViewStub標籤進行延遲初始化,僅當使用時纔去初始化。
include標籤和佈局性能關係不大,主要用於佈局重用,通常和merge標籤配合使用,因和本文主題關聯不大,此處不展開討論。
通過這幾步的優化以後,通常就不會再有佈局的性能問題,同時仍是要強調:優化是一個長期的工做,同時也必須結合具體場景:有取有舍!
歡迎關注微信公衆號:按期分享Java、Android乾貨!