Android應用優化之流暢度實操

上一篇流暢度概念向你們詳細地描述了VSync機制和Choreographer編舞者的用法。可能所講解的內容偏向理論概念,所以這篇是流暢度優化實操,整篇主要分三層,UI層、代碼邏輯層、IO層來說述各個優化點,其中還會穿插多個輔助檢測插件。可謂乾貨滿滿,但願對你們有用。html

最基本的UI層顯示優化

  1. 調試GPU過分渲染

在App出現卡頓的時候,咱們第一時間會想到咱們的App是否是存在過分繪製的問題。爲何要先看過分繪製的問題,由於直接直觀方便啊,在每一臺手機的開發者選項裏中打開顯示過分繪製區域,經過顏色咱們就能辨別咱們的App是否是存在過分繪製的問題。 可能存在一部分的測試甚至開發的同窗不知道什麼是過分繪製?過分繪製指的是在屏幕一個像素上繪製屢次(超過一次),例如一個有背景的TextView,那顯示文字的那個像素至少繪製了兩次,一次是文字,一次是背景。 過分繪製顯示的各類顏色所示含義以下:android

Overdraw倍數 像素點繪製次數 可接受區域
無色 0X 1 全屏
藍色 1X 2 大部分
綠色 2X 3 局部
淡紅色 3X 4 小部分
深紅色 4X ≥5

如今你們能夠看一下本身的項目,找一個你認爲佈局稍微複雜的界面,而後在開發者選項打開顯示過分繪製區域瀏覽器

經過顏色的判斷,咱們檢查對應的佈局代碼來優化過分繪製問題。性能優化

  1. Tracer for OpenGL ES

針對上面咱們看到的過分繪製的區域,咱們要想想應該怎麼去優化,但這個時候咱們並不太清楚這個過分繪製的區域是怎麼造成的,因此咱們要藉助另一個工具Tracer for OpenGL ES,它能夠記錄和分析app每一幀的繪製過程,以及列出全部用到OpenGL ES的繪製函數和耗時,因此經過Tracer for OpenGL ES咱們能夠很容易的看出app的每一幀是怎麼畫出來的。bash

簡要使用步驟:網絡

  • 鏈接真機,在AndroidStudio中打開Android Device Monitor,接着Window-> Open Perspective -> Tracer for OpenGL ES。
  • 以下圖操做,點擊捕捉跟蹤按鈕,而後輸入對應的信息,點擊跟蹤。(華爲P10,MI5這兩款沒法正常跟蹤,最後使用的是華爲Mate7)

  • 點"Stop Tracing"結束,Trace log文件就會生成在預約的目錄下。

  • 結束後,Trace文件自動打開,以下圖介紹,咱們點擊glDraw函數欄,在右上方看到當前繪製函數所繪製的圖像。

我用一個簡單的Demo來介紹,咱們依次點擊glDraw函數欄,能夠看到所記錄和分析app每一幀的繪製過程。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">

    <Button
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@android:color/background_dark"
        android:text="Button"
        android:textColor="@android:color/white" />

</android.support.design.widget.CoordinatorLayout>

複製代碼

經過剛剛的演示,咱們就能夠用到了Tracer for OpenGL ES來查找過分繪製的地方來。app

小插曲:打開本身mac的AndroidStudio3.1時,居然一時找不到DDMS,查閱資料發現DDMS在AndroidStudio3.1已經不推薦使用了,只能用回公司AndroidStudio3.0截圖,這裏爲之後的優化工具文章留下一個伏筆。異步

  1. Hierarchy Viewer

接着咱們介紹一下Hierarchy Viewer,經過它咱們能夠查找佈局不合理的地方,Hierarchy Viewer的使用方法較爲簡單,AndroidStudio中,一樣經過Android Device Monitor,接着Window-> Open Perspective -> Hierarchy Viewer。經過Hierarchy Viewer能夠看到咱們打開的Activity的UI Tree狀況。(注意:咱們用模擬器做爲例子,先用模擬器運行打開你的應用,再打開Hierarchy Viewer面板。)ide

拿到UI Tree以後,咱們主要分析如下三個問題:(我用一個簡單的Demo來分析)函數

【問題1】沒有用的父佈局
使用Hierarchy Viewer查看咱們的UI Tree,如發現紅框的RelativeLayout是CustomTestView惟一子View,咱們能夠看看是否能把RelativeLayout子View又放到CustomTestView裏,這樣就能夠把RelativeLayout這一層去掉,經過查看代碼,咱們發現其實RelativeLayout這一層是多餘的,咱們直接經過merge標籤把RelativeLayout和CustomTestView合併.(這種狀況在自定義View很是常見)

【問題2】某種狀況纔會使用的UI被設置成View.GONE 咱們在開發應用程序的時候,常常會遇到這種狀況,會在運行時動態根據條件來決定顯示哪個View或者ViewGroup,把最早要顯示的放在第一位顯示,不是第一時間要顯示的暫時設置爲View.GONE。這樣的作法優勢是邏輯簡單,並且控制起來很是的方便,可是缺點是會消耗資源,雖然把View或者ViewGroup的初始可見設爲View.GONE,可是在Inflate佈局的時候,View仍是被Inflate,也就是說仍然會建立對象,會被實例化,會被設置屬性,也就是說會消耗內存等資源的。官方推薦的作法是使用ViewStub,ViewStub是一個輕量級的View,他是一個使用資源很是小的控件。(若是不明白設置成GONE,仍然會消耗資源的同窗,能夠經過關於View的建立與ViewStub的源碼分析進行理解)在咱們的代碼中,錯誤頁面ErrorView常常會出現這種狀況。

【問題3】使用LinearLayout排版致使佈局層次加深 從下圖能夠發現,下面佈局是用兩個LinearLayout嵌套實現的,可是經過使用一個RelativeLayout咱們能夠實現一樣的效果,這樣就能夠減小一個層次,從圖中能夠很明顯的看出優化效果。

  1. 移除或修改Window默認的Background 咱們一般在設置通用Theme時候,都用設置一個默認背景色,做爲應用的基礎色
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/app_status_bar_bg_color</item>
        <item name="colorPrimaryDark">@color/app_status_bar_bg_color</item>
        <item name="android:textColorPrimary">@android:color/black</item>
        <item name="android:windowBackground">@color/app_frame_bg_color</item>
    </style>
複製代碼

可是在佈局頁面,設計人員設計的底色,根本不是默認的背景色,若是咱們在這個頁面的根佈局再設一個背景的話就是多繪製一層背景。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"><!-- 會致使過分繪製的寫法 -->
    
    ***
   
</RelativeLayout>
複製代碼

這種狀況,咱們能夠這樣處理:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setBackgroundDrawableResource(android.R.color.white);
        setContentView(R.layout.activity_main);
        ***
    }
複製代碼

這樣修改佈局的背景色,咱們能夠避免出現過分繪製的狀況。另外上面的設置背景代碼,要注意書寫順序,這裏可包含了很多View的建立的知識,有興趣的同窗能夠自行查閱。

  1. 減小寫View與ViewGroup
  • 可使用RelativeLayout減小層級的就使用RelativeLayout,不然使用LinearLayout線性佈局。由於Android中RelativeLayout的測量次數比LinearLayout(不含weight的狀況下)多,能夠了解一下關於RelativeLayout、LinearLayout、FrameLayout的ViewGroup的測量源碼分析。
  • 使用SpannableString。相信你們對SpannableString都很是熟悉了,這是一個優化減小書寫View的利器。
  • 優雅的給LinearLayout、RecyclerView設置分割線。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:divider="@drawable/divider_horizontal_w7_transparent"
    android:orientation="horizontal"
    android:showDividers="middle">
    ***
</LinearLayout>
複製代碼
mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
    }
    
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
    }
});
複製代碼

強烈不建議,直接在itemView中直接畫分割線,雖然是簡單,可是咱們是一個有追求的開發者,儘可能把代碼寫得漂亮一點。

  • 使用merge標籤、ViewStub標籤、include標籤。上面咱們都有講解過。
  • drawableLeft 代替ImageView + TextView
  • 使用ConstraintLayout。做爲AndroidStudio新版本的推薦的默認佈局,可想它的強大之處,它是RelativeLayout的增強版,它是百分比佈局(已被Deprecated)的替換品。
  • %1$d代替TextView + TextView。(若是須要多語言適配你就懂了這的重要性)

這裏幫你們整理幾個比較經典的注意點,因爲有不一樣層次的讀者,因此這裏不用具體代碼來說解,若是有不理解的同窗,能夠單獨對某個點進行查閱。

  1. 代碼檢測神器——Lint檢測工具 估計有一部分同窗看完上面的分析講解以後會以爲,好麻煩呀,要打開這個而後又要那裏弄一下。而後就放棄了。接下來這個真的很是適合這部分同窗使用。 打開Lint的步驟:Analyze -> Inspect Code -> 選擇你須要分析的目錄,而後點擊肯定分析

在Android Lint:Performance這個錯誤節點下,很是清晰地描述了你都有哪些錯誤,每個錯誤都有很是清晰的描述,你應該如何去改,在右邊的箭頭,程序幫咱們直接定位到錯誤代碼地方,是否是很是方便!

代碼邏輯層優化

通過上述的分析調整後,咱們接着分析一下關於代碼邏輯層的優化。

  1. Traceview

Traceview是Android設備的一個很是好用的性能分析工具,它能夠經過詳細的界面,讓咱們跟蹤程序的性能,而且能清晰地查看到每個函數的耗時和調用次數,因此咱們用Traceview的時候要主要兩種影響流暢度的緣由。一:主線程佔用cpu時間很長的方法函數;二:線程調用的次數

我經過具體的應用來具體分析,好比說商城類型的首頁,經過是使用RecyclerView,那麼咱們能夠先推斷影響RecyclerView的流暢度大多數是RecyclerView.Adapter#onBindViewHolder的方法。

一樣是經過Android Device Monitor面板,在下圖左方選中須要分析的應用,再點擊左上角按鈕,當你以爲數據收集足夠時,再次點擊那個按鈕便可,這時Traceview會自動打開trace文件。

那麼經過Traceview面板的上部分爲時間線面板,左上方面板顯示的是採集數據中所採集的線程信息,右邊上方面板爲時間線,時間線上,每一條線程在採集時間段內所涉及的函數調用信息。而下部分爲函數分析面板,是traceview核心界面,它所提供的信息數據很是多,他主要展現了某條線程中各個函數方法調用的狀況,包括cpu使用時間,函數方法調用次數,和函數方法真實執行時間等信息,這些信息就是咱們分析流暢度的關鍵所在。

咱們瞭解一下操做,獲取方法的調用順序:

  • 在traceview中搜索響應的方法名
  • 搜索出的方法會自動展開,其中包含Parents 和 Children 兩組信息
  • 點擊Parents下的方法名,直接跳轉到調用當前的方法處。Children則相反

Profile Panel各列功能描述說明

列名 描述
Name 調用的函數方法名
Incl Cpu Time 函數佔用的CPU時間,包含內部調用其它函數的CPU時間
Excl Cpu Time 函數佔用的CPU時間,但不含內部調用其它函數所佔用的CPU時間
Incl Real Time 函數運行的真實時間(以毫秒爲單位),內含調用其它函數所佔用的真實時間
Excl Real Time 函數運行的真實時間(以毫秒爲單位),不含調用其它函數所佔用的真實時間
Call+Recur Calls/Total 函數被調用次數、遞歸調用佔總調用次數的百分比
Cpu Time/Call 函數調用CPU時間與調用次數的比,至關於該函數平均執行時間
Real Time/Call 函數調用CPU時間與調用次數的比,至關於該函數平均執行時間,這個時間包含來內部調用的其餘函數的執行時間

看回上圖,我經過搜索RecyclerView.Adapter#onBindViewHolder中調用的抽象方法inflateFromModel,找到了首頁某一個ViewHolder,從這個ViewHolder#inflateFromModel方法中,找到它調用了兩個方法,一個是圖片顯示的方法,另外一個是正則判斷的方法,因爲ViewHolder#inflateFromModel在滑動機制中會不斷地調用,而這個正則判斷的目的是對點擊事件中的控件進行setTag操做的值進行髒數據驗證,其實這個正則判斷其實沒有必要在這裏執行,那麼咱們能夠不做任何判斷,將拿到的值直接View#setTag,而後在對應的onClick()再進行正則判斷,這樣就能夠減小那些沒必要要佔用主線程cpu時間的方法函數,達到提升流暢度的效果。

  1. Systrace

Systrace很是直觀地展現每一個線程上面的API的調用順序和耗時狀況。一樣是經過Android Device Monitor面板,下圖中的箭頭,建議跟蹤持續時間不要太長,爲了更好地定位問題.接着生成trace.html文件,經過Google Chrome瀏覽器打開。

先了解一下幾個經常使用的快捷鍵:

操做 做用
w 放大
s 縮小
a 左移
d 右移
m 標記當前選定區域
/ 搜索關鍵字

下拉trace.html咱們能夠看到frame,每一幀就顯示爲圓圈,正常繪製是1秒60幀,大約一幀16.6毫秒,在這個值如下是正常顏色綠色,若是超過它就會變成紅色、黃色。非綠色的都說明有問題。這時須要經過’w’鍵放大那一幀,而後按‘m’鍵高亮,進一步分析問題。

Systrace能自動分析trace中的事件,並能自動高亮性能問題做爲一個Alerts,咱們能夠根據提示進行分析優化。

可是,這裏所標的問題,咱們怎麼能定位到具體哪一部分的代碼呢?Systrace爲咱們提供了對應的API,而後在對應的持續時間。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Trace.beginSection("Activity_onCreate");
    ***
    Trace.endSection();
    }
複製代碼

總體來講,Systrace展示的信息是不少的,可是如何加以利用還得繼續研究,對應上面涉及代碼定位看上去就像一個Log,監測開始與結束時間罷了,感受有點雞肋。

App的IO層代碼優化

IO可分紅爲網絡請求和磁盤讀寫IO,相信你們都知道,MVP模式下的Model層也是對IO層進行操做。而在主線程中進行長時間和頻繁的IO操做,對流暢度是有很是大的影響的,對於網絡請求在安卓4.0以後,就已經不能在主線程進行網絡操做了,不然程序會出現crash,所以咱們對IO層的操做要進行監控。而Android爲咱們提供了StrictMode方式來監控代碼是否出現上述的狀況。

StrictMode主要有兩種策略,一是線程方面策略(TreadPolicy),二是VM方面策略(VmPolicy).

  1. 線程策略主要用於檢測UI線程中是否存在讀寫磁盤的操做,是否有網絡請求操做,以及檢查自定義代碼是否在UI線程執行得比較慢的狀況
  • 自定義的耗時調用 使用detectCustomSlowCalls()開啓
  • 磁盤讀取操做 使用detectDiskReads()開啓
  • 磁盤寫入操做 使用detectDiskWrites()開啓
  • 網絡操做 使用detectNetwork()開啓
  1. VmPolicy策略主要用於發現內存問題,好比Activity內存泄漏,SQL對象內存泄漏,IO操做對象資源未釋放。
  • Activity泄露 使用detectActivityLeaks()開啓
  • 未關閉的Closable對象泄露 使用detectLeakedClosableObjects()開啓
  • 泄露的Sqlite對象 使用detectLeakedSqlLiteObjects()開啓
  • 檢測實例數量 使用setClassInstanceLimit()開啓

只要主線程中配置了並啓動,它就能監聽主線程的運行狀況,當發現有重大問題時和違背策略的時候,就會以logcat的形式提示用戶。

流暢度優化經驗總結

最後我來總結一下通篇對流暢度優化上的經驗:

  1. UI佈局優化
  • 使用LinearLayout代替RelativeLayout,由於LinearLayout性能上稍微好一點
  • 若是複雜的佈局,咱們可使用RelativeLayout來解決複雜的佈局關係
  • 儘可能少用LinearLayout的layout_weight屬性,由於它會消耗較大的性能
  • 對應能夠複用的佈局使用include標籤來進行復用
  • 使用ViewStub標籤來加載一些不是一定出現使用的佈局
  • 使用merge來減小沒必要要的層級嵌套
  • 去除多餘的背景顏色,減小過分繪製問題
  • 使用compound drawables、%1$d 減小布局的建立

2.RecyclerView性能優化

  • 在RecyclerView.Adapter#onBindViewHolder函數下的複用問題,注意哪些沒必要要的變量建立
  • 異步加載圖片
  • 對於一些沒必要要的操做不要在滑動複用部分進行實現,這樣會影響cpu運算
  1. UI主線程
  • 異步請求網絡數據
  • 若是較爲耗時的操做不要放在UI線程中實現
  • 不要在UI線程外操做UI

4.第三方平臺

  • 騰訊開源工具——GT
  • 聽雲——應用性能監控平臺

寫在結尾:我在這篇博客的時候,剛剛出現了AndroidStudio3.2金絲雀版本,而部分上述的工具,Google已經再也不推薦使用,接下來我會繼續更新Google新推薦的優化工具文章,努力成爲一個性能優化的好手。

相關文章
相關標籤/搜索