Android:應用開發進階必經之路之性能優化(上)

前言

性能優化在一款產品的迭代過程當中很是重要;程序實現了功能、還原產品原型只能保證程序能用,但若是要讓用戶更願意使用,產品得好用。試想一下若是你開發的產品啓動慢、頁面顯示須要長時間轉圈加載、頁面切換卡頓、黑白屏、用一會機器就發燙、耗內存、OOM、程序切換到後臺後佔用內存沒法釋放......,這些問題就像正在玩遊戲時彈出提示框這類糟糕的用戶體驗同樣讓用戶惱火,若是用戶不得不使用你的產品,可能還會一直忍受;但若是有不少同類競品,糟糕的用戶體驗會大大影響留存率。有時候產品在市場上的表現差,真不能全怪產品和運營,程序體驗問題也是很大一部分緣由。 但大部分產品並無足夠重視性能問題,隨便打開一個應用,即便是大廠出品,也極大可能存在過渡繪製和內存泄露的問題;也有多是開發人員意識到了程序存在性能問題,但完成迭代就夠忙的了,哪有時間去作這類不能體現績效的事情。其實在愈來愈重視體驗,同類產品競爭愈來愈激烈的環境下,對於開發人員來說,只完成迭代,把功能作完遠遠不夠,最重要的是把產品作好,讓更多人願意使用。重視性能問題,優化產品的體驗,比改幾個無關痛癢的bug會有價值得多。 網上可以找到不少關於性能優化頗有價值的參考資料(詳見文末),包括騰訊、阿里、魅族、豌豆莢、小米、UC等知名互聯網公司都作過關於APP性能優化的分享,若是你專一於應用開發,而且想作一款備受歡迎的產品,性能優化是你進階路上必須去學習和實踐的。java

1、性能問題分類

除了交互、視覺、內容方面的問題外,在用戶使用過程當中,給用戶形成煩惱的問題均可以歸結爲是性能問題,好比上文中列出的這些都屬於性能問題,按照影響的方面不一樣,能夠分爲以下幾大類:android

  • 內存問題: 耗內存、OOM、程序切換到後臺後佔用內存沒法釋放(OOM會影響產品的穩定性;耗內存、內存泄露會影響整機的性能;佔用內存多預示着留給其它應用的剩餘內存空間小);面試

  • 功耗問題: 發燙(耗電);canvas

  • 流暢度問題: 啓動慢、頁面顯示須要長時間轉圈加載、頁面切換卡頓、黑白屏(卡慢崩會讓人煩躁);性能優化

針對上面一系列的性能問題,谷歌官方提供了各類各樣的工具來針對性的解決各個方面的問題,也有不少不錯的第三方工具值得嘗試:bash

  • 內存問題: 提供了Android Studio的靜態代碼檢測功能、Android Monitor;第三方內存泄露分析工具Leakcanary、MAT;架構

  • 功耗問題: 提供了GPU呈現模式、battery-historian、Android Monitor;app

  • 流暢度問題: 提供了Android Studio的靜態代碼檢測功能、Android Monitor、HierarchyViewer、StrictMode、過渡繪製檢測工具、TraceView等;ide

除了上面提到的這些性能優化工具外,谷歌還在Youtube上提供了一系列關於Android應用性能優化的短視頻Android Performance Patterns,介紹如何優化Android各個方面的性能問題。工具

2、性能優化指標

性能優化的效果僅憑感受很難衡量,一切應該看數聽說話,好比流暢度優化,刷新頻率每秒越接近60幀越理想,但只要每秒鐘超過24幀人眼就沒法辨別了,因此僅憑感受是沒法區分優化前的30幀和優化後的40幀的區別的。爲了說明作性能優化有足夠的價值,就有必要經過一系列指標來講明優化先後的區別。

性能指標的定義應該具備可衡量、可比較的特色,因此每項性能指標能夠是數值,也能夠是一份報告,好比:

  • 流暢度: FPS,即Frams per Second,一秒內的刷新幀數,越接近60幀越好;

  • 啓動時間: 時間,越短越好;

  • 內存泄露: AS靜態代碼檢測結果、MAT檢測結果,內存泄露很難用數值定義,但能夠經過將優化先後工具檢測的結果對比得出結論。沒有內存泄露最好;

  • 內存大小: 峯值,峯值越低越好;

  • 功耗: 單位時間內的掉電量,掉電量越少越好;

從上面各項性能指標的定義能夠看出,性能優化效果的評估主要是經過對比得出來的,性能如何只是相對的。只要針對同一個應用的同一項指標,優化後比優化前更優,就說明優化是有效果的。

3、性能優化原則和方法

1優化原則

解決性能問題的過程當中,遵循如下幾個原則,有助於提升解決問題的效率:

  • 足夠多的測量: 不要憑感受去檢測性能問題、評估性能優化的效果,應該保持足夠多的測量,數據不會說謊。使用各類性能工具備助於快速定位問題,這比憑感受要靠譜得多;

  • 使用低配置的設備: 一樣的程序,在低端配置的設備中,相同的問題會暴露得更爲明顯;高配的設備不少時候會讓你忽略掉性能問題;

  • 權衡利弊: 在可以保證產品穩定、按時交付的前提下去作優化,不能顧此失彼,爲了性能優化致使產品遲遲不能交付;

2優化方法

性能優化的指標不少,乍看上去無從下手,但和解bug同樣,只要講方法,事情就變得迎刃而解。對於大多數問題來說,只要遵循瞭解問題→定位問題→分析問題→解決問題→驗證問題的思路,基本上均可以解決:

  • 瞭解問題: 對於性能問題來說,這個步驟只適用於某些明顯的性能問題,不少沒法感知的性能問題須要經過工具定位;

  • 定位問題: 經過工具檢測、分析,定位在什麼地方存在性能問題;若是很難定位,能夠採用排除法(屏蔽部分代碼,看現象是否仍然存在,若是還存在,則說明被屏蔽的代碼沒有問題,這樣逐漸縮小問題的範圍);

  • 分析問題: 找到問題後,分析針對這個問題該如何解決,肯定解決方案;

  • 解決問題: 這個沒什麼可說的,若是是本身沒法解決的問題,藉助搜索引擎,你遇到過的問題不少人都遇到過,而且極有可能已經被解決了;

  • 驗證問題: 保證每一次優化都有效,沒有產生新問題,保證產品的穩定;

4、性能優化工具

本文重點介紹谷歌官方提供的一系列應用性能優化工具以及值得推薦的第三方性能優化工具,這些工具主要集中在以下幾個地方:

  • 開發者選項: GPU呈現模式分析、GPU過渡繪製、嚴格模式、應用無響應ANR等;

  • IDE中: Android Studio,好比靜態代碼檢測工具、Memory Monitor、CPU Monitor、NetWork Monitor、GPU Monitor、Layout Inspector、Analyze APK等;

  • SDK中: sdk\tools,好比DDMS、HierarchyViewer、TraceView等;

  • 第三方性能優化工具: MAT、Leakcanary等;

1開發者選項

首先從不須要依賴任何工具,直接藉助手機中的開發者選項進行應用性能檢測提及。開發者選項須要進入開發者模式後才能在系統設置中顯示,對於大多數設備,能夠經過以下方式在手機中開啓開發者選項:打開「系統設置」→點擊進入「關於手機」→連續點擊「版本號」選項直至提示已進入「開發者模式」,就能夠在「系統設置」中看到「開發者選項了」,打開「開發者選項」,能夠看到不少可以幫助開發者檢測應用性能的選項:

  • 調試GPU過渡繪製(Visualize GPU Overdraw): 過渡繪製用於檢測你的程序是否存在沒必要要的繪製(舉個栗子:同一個區域存在多個視圖,刷新的時候被遮擋的視圖也在繪製),致使顯示時的性能問題,它能夠幫助開發者解決以下問題:

(1)找出應用中哪些地方存在沒必要要的渲染;

(2)幫助開發者發現哪些地方能夠減小渲染,提升程序運行效率;

顯示過渡繪製區域的步驟以下:「開發者選項」→點擊「調試GPU 過渡繪製」→點擊「顯示過渡繪製區域」,一旦使能,對設備中的任何應用都有效:

Android經過不一樣顏色來區分同一個區域繪製的次數,顏色越深,表示過渡繪製的次數越多,過渡繪製越嚴重。以下圖所示,藍色表示存在一次過渡繪製;深紅色表示同一區域存在4次及以上的過渡繪製:

應用沒法徹底作到沒有過渡繪製,優化是儘可能避免沒必要要的過渡繪製,一般狀況下保證同一區域過渡繪製少於三次都是合理的,即只要是出現紅色(淡紅色和深紅色)的地方,就是須要優化的地方:

過渡繪製不只僅會影響程序的刷新頻率,還會致使程序啓動慢、黑白屏、耗內存等問題,由於過渡繪製主要是由於佈局複雜致使,android在加載佈局文件的時候,其實是讀取xml文件並解析,而後根據每一個視圖的關係去測量、繪製、顯示每個視圖;複雜的佈局會須要更長的解析、測量、繪製、顯示時間,也須要更多的內存(這與是否設置了視圖背景有關)。在實際開發過程當中,有以下幾種常見的過渡繪製優化方法:

(1) 使用merge標籤

merge標籤就是爲減小布局層次而生的,它經過減小View樹的層級來優化佈局,merge只能做爲xml佈局的根標籤使用(由於Activity的根佈局是FrameLayout,因此只有Activity對應的佈局文件根標籤爲FrameLayout時才適合使用merge標籤),若是在代碼中Inflate帶merge標籤的佈局時,必須爲這個自定義View指定一個父ViewGroup,而且設置attachToRoot爲true。merge只可以在xml佈局文件中使用,沒有對應的java類。下面的實例演示了merge標籤的用法,經過「GPU過渡繪製」查看優化先後的效果,能夠明顯看到經過merge標籤解決了過渡繪製的問題;經過Hierarch View觀察優化先後的視圖樹,能夠明顯看到使用merge標籤後的視圖層級減小了:

優化前

佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="fill_parent"
             android:layout_height="fill_parent"
             android:background="@android:color/white">
    <ImageView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scaleType="center"
        android:src="@drawable/golden_gate"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="24dp"
        android:text="No Merge Layout"
        android:textColor="@android:color/black"/>
</FrameLayout>
複製代碼

效果圖及過渡繪製顯示:

Hierarchy View視圖樹:

優化後

佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
       android:background="@android:color/white">
    <ImageView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scaleType="center"
        android:src="@drawable/golden_gate"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="24dp"
        android:text="Has Merge Layout"
        android:textColor="@android:color/black"/>
</merge>
複製代碼

效果圖及過渡繪製顯示:

Hierarchy View視圖樹:

(2) 使用ViewStub標籤 在開發應用的時候,常常會遇到這樣的狀況,在程序運行時根據條件來決定顯示/隱藏哪一個視圖;一般會在佈局文件中將其寫上去,默認隱藏,而後在代碼中根據條件去判斷是否顯示。這樣作的優勢是邏輯清晰,但缺點是耗費資源,在佈局文件中將某個視圖默認設置爲invisable或者gone,在Inflate佈局文件的時候仍然會被infalte,一樣會被實例化、設置屬性,但有可能默認被隱藏的視圖用戶在某一次操做中極可能不會去觸發它。爲了提升佈局文件加載效率和減小額外的資源消耗,強烈建議使用ViewStub標籤,ViewStub是一個用於在運行時加載佈局資源、不可見、寬高爲0的View,在佈局文件中使用它只是用於佔位,在代碼中沒有手動加載它時,並不會影響頁面的測量、繪製、顯示效率,在代碼中經過inflate加載ViewStub時,ViewStub會用在佈局文件中爲其指定的佈局文件來代替它自身,經過前面的解釋可想而知,ViewStub只可以被inflate一次,一旦加載後ViewStub對象就會被置爲空;ViewStub標籤有對應的java類ViewStub.java,經過閱讀源碼能夠發現,確實在初始化的時候設置爲隱藏、不繪製、寬高爲0,而且它複寫了View的dispatchDraw和draw方法,這倆方法是空方法,沒有調用super的方法,也沒有執行本身的代碼:

public final class ViewStub extends View {

    ...

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);

        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ViewStub, defStyleAttr, defStyleRes);
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle();

        setVisibility(GONE);
        setWillNotDraw(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0);
    }

    @Override
    public void draw(Canvas canvas) {

    }

    @Override
    protected void dispatchDraw(Canvas canvas) {

    }

    ...

}
複製代碼

下面是ViewStub在Inflate先後的佈局及視圖樹:

XML佈局文件

activityviewstublayout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/activity_view_stub_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <include
        android:id="@+id/inclueId"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="8"
        android:background="#ff00ff00"
        layout="@layout/inclue_viewstub_layout"/>
    />
    <View
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@android:color/black"/>
    <ViewStub
        android:id="@+id/viewStubId"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="8"
        android:layout="@layout/inclue_viewstub_layout"/>
    <Button
        android:id="@+id/inflateViewStubBtnId"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3"
        android:text="InflateViewStub"
        android:onClick="onClick"
        />
</LinearLayout>
複製代碼

inclueviewstublayout.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/titleTxtViewId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="ViewStub!!!!"
        android:textColor="@android:color/black"
        android:textSize="24dp"/>
</RelativeLayout>
複製代碼
代碼
public void onClick(View v){
    switch(v.getId()){
        case R.id.inflateViewStubBtnId:{
            inflateViewStub();
        }
        break;
    }
}

private void inflateViewStub(){
    mViewStub = (ViewStub)findViewById(R.id.viewStubId);
    if(null == mViewStub){
        Toast.makeText(this, "ViewStub is Empty", Toast.LENGTH_LONG).show();
        return;
    }
    mViewStub.inflate();
}
複製代碼
Inflate前

界面效果:

Hierachy View:

Inflate後

Hierachy View:

能夠看到在ViewStub Inflate前ViewStub不佔用佈局層級,因此不會消耗程序資源;Inflate後會佔用佈局層級;在試驗的過程當中,點擊兩次及以上Inflate按鈕時,會彈出「ViewStub is Empty」的Toast,說明mViewStub在實例化一次後再次實例化時會失敗,由於在Inflate時已經被replace掉,系統找不到這個資源ID。

(3) 使用Space

過渡繪製問題是由於繪製引發的,space標籤能夠只在佈局文件中佔位,不繪製,Space標籤有對應的java類Space.java,經過閱讀源碼能夠發現,它繼承至View.java,而且複寫了draw方法,該方法爲空,既沒有調用父類的draw方法,也沒有執行本身的代碼,表示該類是沒有繪製操做的,但onMeasure方法正常調用,說明是有寬高的。

XML佈局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:gravity="center"
        android:text="TextView 1"
        android:textColor="@android:color/black"
        android:textSize="28dp"
        android:background="@android:color/darker_gray"/>
    <Space
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@android:color/black"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:gravity="center"
        android:text="TextView 2"
        android:textColor="@android:color/black"
        android:textSize="28dp"
        android:background="@android:color/darker_gray"/>
    <Space
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:gravity="center"
        android:text="TextView 3"
        android:textColor="@android:color/black"
        android:textSize="28dp"
        android:background="@android:color/darker_gray"/>
</LinearLayout>
複製代碼
效果圖

能夠看到在佈局中給第一個Space控件設置了黑色背景,但從效果圖能夠看出Space並無變成黑色,說明沒有執行繪製方法。

Hierarchy Vieiw

觀察視圖樹也能夠看出Space會佔用空間(由於有寬高)。

(4) 去掉沒必要要的背景

若是不是經過測量和仔細分析,你很難發現這個不經意的細節會是致使過渡繪製、內存問題的主要緣由,每一個Activity都會在AndroidManifest.xml中設置主題,主題的目的是設置界面的顯示風格,但在設置主題的時候一般狀況下默認給Window設置了背景,注意是Window而不是Activity,Activity是依附在Window上的,Android系統在刷新整個界面時不只僅是刷新Activity,還會刷新Window。若是默認沒有去掉window的背景,而且在佈局文件中給Activity設置了背景,就會存在過渡繪製的問題,具體狀況能夠看下面的實例:

activitybackgroundlayout.xml

(這裏爲了演示在佈局文件中爲每一個視圖設置了背景,在真實狀況中沒有必要爲每一個視圖都設置):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/activity_background_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="10dp"
            android:background="@android:color/white"
            android:text="@string/more_text"/>
    </ScrollView>
</RelativeLayout>
複製代碼

使用該佈局的Activity對應的主題以下:

<style name="AppTheme" parent="@android:style/Theme.Light.NoTitleBar">
</style>
複製代碼

經過過渡繪製工具檢測,存在過渡繪製:

下面是去掉window背景後的效果(佈局文件不變,主題變更以下):

<style name="AppTheme" parent="@android:style/Theme.Light.NoTitleBar">
    <item name="android:windowBackground">@null</item>
</style>
複製代碼

效果圖:

說明:

一、在主題中去掉Window的背景時要注意,去掉以後必須從新運行程序檢查一下,避免有些Activity並無設置背景緻使界面背景爲黑色;

二、有的程序爲了不冷啓動時界面黑屏/白屏的問題,在主題中爲window設置了一張圖片,而後在佈局文件中爲Activity也設置了背景,這樣既會致使過渡繪製問題,還會致使內存問題(同一個頁面兩張全屏的圖片,雙倍內存);因此這種解決方式並不妥,若是是啓動速度問題,直接優化啓動速度比這種方式靠譜。

(5) 其餘

一、經過Canvas的clipRect方法控制每一個視圖每次刷新的區域,這樣能夠避免刷新沒必要要的區域,從而規避過渡繪製的問題;

二、如對一個View作Alpha轉化,須要先將View繪製出來,而後作Alpha轉化,最後將轉換後的效果繪製在界面上。通俗點說,作Alpha轉化就須要對當前View繪製兩遍,可想而知,繪製效率會大打折扣,耗時會翻倍;

三、多和產品、UI溝通,避免過渡設計,過渡繪製最好的解決方案是界面的佈局自己就不復雜,這樣程序實現出來的界面就會少不少過渡繪製,優化也會簡單不少。

總結一下過渡繪製的檢測和解決方案:經過「開發者選項」中的「顯示過渡繪製」和Android提供的工具「HierarchyViewer」,以每一個界面爲單位,能夠徹底檢測出每一個界面的過渡繪製問題;由於致使過渡繪製的緣由不一,因此也會有多種對應的解決方案:

一、merge標籤能夠解決相同佈局嵌套致使的過渡繪製問題;

二、ViewStub標籤能夠解決動態加載頁面佈局,避免默認加載沒必要要佈局的問題;

三、Space標籤能夠解決只佔位、不刷新的視圖問題;

四、去掉Window背景能夠解決全部界面的過渡繪製問題;

五、clipRect能夠解決只刷新固定區域的問題;

六、沒必要要的alpha值設置能夠解決同一視圖被屢次繪製的問題;

七、最重要的是產品設計合理,多和產品、UI溝通,避免無心義的工做。

GPU呈現模式分析(Profiling GPU Rendering)

從Android 4.1開始,在「開發者選項」中提供了GPU呈現模式分析的選項,GPU呈現模式是一個方便你快速觀察UI渲染效率的工具,主要做用是實時查看每一幀的渲染效率,定位哪裏存在渲染的性能問題;經過以下方式能夠打開GPU呈現模式分析:「系統設置」→「開發者選項」→「GPU呈現模式分析」→在彈出的窗口中選擇「在屏幕上顯示成條形圖(On screen as bars)」。

打開GPU呈現模式後,你能夠在機器的任何界面看到以下圖所示的條形圖,頂部通知欄、當前活動程序(主窗口)、底部導航欄都會有對應的呈現模式條形圖,用於觀察通知欄、當前活動界面、導航欄的渲染效率。

隨着界面的刷新,界面上會滾動顯示錘子的柱狀圖來表示每幀畫面說須要的渲染時間,柱狀圖越高表示花費的渲染時間越長。中間有一根綠色的橫線,表明每幀的最長渲染時間:16ms,咱們須要確保每一幀花費的總時間都低於這條橫線,這樣纔可以避免出現卡頓的問題。

從圖中能夠看出,每一條柱狀線包含三種顏色,但從Android 6.0開始,你看到的每條柱狀線已不止三種顏色:

每種顏色表明每一幀渲染過程當中須要完成的某一件事情,由於6.0以前的三種顏色不大可以清晰地幫助咱們定位性能問題的具體緣由,因此從6.0開始,將每一幀的渲染過程拆分紅了8個步驟,每一個步驟一種顏色,每種顏色的意義以下:

(1)Swap Buffers: 表示處理任務的時間,也能夠說是CPU等待GPU完成任務的時間,線條越高,表示GPU作的事情越多;

(2)Command Issue: 表示執行任務的時間,這部分主要是Android進行2D渲染顯示列表的時間,爲了將內容繪製到屏幕上,Android須要使用Open GL ES的API接口來繪製顯示列表,紅色線條越高表示須要繪製的視圖更多;

(3)Sync & Upload: 表示的是準備當前界面上有待繪製的圖片所耗費的時間,爲了減小該段區域的執行時間,咱們能夠減小屏幕上的圖片數量或者是縮小圖片的大小;

(4)Draw: 表示測量和繪製視圖列表所須要的時間,藍色線條越高表示每一幀須要更新不少視圖,或者View的onDraw方法中作了耗時操做;

(5)Measure/Layout: 表示佈局的onMeasure與onLayout所花費的時間,一旦時間過長,就須要仔細檢查本身的佈局是否是存在嚴重的性能問題;

(6)Animation: 表示計算執行動畫所須要花費的時間,包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦這裏的執行時間過長,就須要檢查是否是使用了非官方的動畫工具或者是檢查動畫執行的過程當中是否是觸發了讀寫操做等等;

(7)Input Handling: 表示系統處理輸入事件所耗費的時間,粗略等於對事件處理方法所執行的時間。一旦執行時間過長,意味着在處理用戶的輸入事件的地方執行了複雜的操做;

(8)Misc Time/Vsync Delay: 表示在主線程執行了太多的任務,致使UI渲染跟不上vSync的信號而出現掉幀的狀況;出現該線條的時候,能夠在Log中看到這樣的日誌:

I/Choreographer(*): Skipped XXX frames! The application may be doing too much work on its main thread
複製代碼

經過GPU呈現模式能夠清晰地檢測出致使渲染問題的具體緣由,但不能定位是哪一行代碼出了問題,從上面的描述可知,減小過渡繪製能夠很好地提高GPU呈現模式的表現力;若是要跟蹤具體哪一行代碼致使了渲染的性能問題,須要藉助各類性能檢測工具。好比經過TraceView跟蹤是否存在耗時操做;經過「顯示過渡繪製」跟蹤是否存在過渡繪製等。

啓動嚴格模式

噹噹前界面在主線程中存在耗時操做時,會閃爍屏幕,但只會提示你存在耗時操做,不會告訴你具體的地方;若是要精肯定位具體哪裏耗時,應該在代碼中添加StrictMode檢查,在log中會報詳細的耗時信息:

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites()
        .detectNetwork()
        .penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()
        .detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
複製代碼

關於嚴苛模式的詳細說明,能夠參考官網的這篇文章,很詳細:StrictMode。

顯示全部「應用無響應」(Show all ANRs)

當任何一個應用(包括後臺應用)無響應時會彈出「App Not Responding」對話框,主要用於識別程序之間是否存在干擾;

更多關於開發者選項的信息,能夠查閱這篇文章:All about your phone's developer options。

篇幅緣由,未完待續,敬請期待............

最後送福利了,如今關注我而且加入羣聊能夠獲取包含源碼解析,自定義View,動畫實現,架構分享等。 內容難度適中,篇幅精煉,天天只需花上十幾分鍾閱讀便可。你們能夠跟我一塊兒探討,歡迎加羣探討,有flutter—性能優化—移動架構—資深UI工程師—NDK相關專業人員和視頻教學資料,還有更多面試題等你來拿~ Android開發交流羣:1018342383

下一篇:Android:應用開發進階必經之路之性能優化(下)

相關文章
相關標籤/搜索