應用開發進階必經之路之性能優化

爲了方便在手機上閱讀,文章也會在公衆號發出,更多原創文章和優質資源請關注公衆號:

公衆號:open_dev

更多Android技術資源交流請加羣“Android技術資源交流羣”,第1期羣分享精華已在微信公衆號發出,本羣的建羣宗旨是分享優質的Android技術資源。羣成員可以自由分享任何Android方面的技術資源和文章,並會不定期總結成文章方便大家閱讀。羣已滿,請加羣助理微信:Jf-1994,並註明原因是加Android技術資源交流羣。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

零、前言

性能優化在一款產品的迭代過程中非常重要;程序實現了功能、還原產品原型只能保證程序能用,但如果要讓用戶更願意使用,產品得好用。試想一下如果你開發的產品啓動慢、頁面顯示需要長時間轉圈加載、頁面切換卡頓、黑白屏、用一會機器就發燙、耗內存、OOM、程序切換到後臺後佔用內存無法釋放......,這些問題就像正在玩遊戲時彈出提示框這類糟糕的用戶體驗一樣讓用戶惱火,如果用戶不得不使用你的產品,可能還會一直忍受;但如果有很多同類競品,糟糕的用戶體驗會大大影響留存率。有時候產品在市場上的表現差,真不能全怪產品和運營,程序體驗問題也是很大一部分原因。

但大部分產品並沒有足夠重視性能問題,隨便打開一個應用,即使是大廠出品,也極大可能存在過渡繪製和內存泄露的問題;也有可能是開發人員意識到了程序存在性能問題,但完成迭代就夠忙的了,哪有時間去做這類不能體現績效的事情。其實在越來越重視體驗,同類產品競爭越來越激烈的環境下,對於開發人員來講,只完成迭代,把功能做完遠遠不夠,最重要的是把產品做好,讓更多人願意使用。重視性能問題,優化產品的體驗,比改幾個無關痛癢的bug會有價值得多。

網上能夠找到很多關於性能優化很有價值的參考資料(詳見文末),包括騰訊、阿里、魅族、豌豆莢、小米、UC等知名互聯網公司都做過關於APP性能優化的分享,如果你專注於應用開發,並且想做一款備受歡迎的產品,性能優化是你進階路上必須去學習和實踐的。

一、性能問題分類

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

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

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

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

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

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

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

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

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

二、性能優化指標

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

性能指標的定義應該具有可衡量、可比較的特點,所以每項性能指標可以是數值,也可以是一份報告,比如:

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

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

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

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

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

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

三、性能優化原則和方法

1、優化原則

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

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

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

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

2、優化方法

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

  • 瞭解問題:對於性能問題來講,這個步驟只適用於某些明顯的性能問題,很多無法感知的性能問題需要通過工具定位;

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

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

  • 解決問題:這個沒什麼可說的,如果是自己無法解決的問題,藉助搜索引擎,你遇到過的問題很多人都遇到過,並且極有可能已經被解決了;

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

四、性能優化工具

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

  • 開發者選項: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 View:

觀察視圖樹也可以看出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>

效果圖:

說明:

1、在主題中去掉Window的背景時要注意,去掉之後必須重新運行程序檢查一下,避免有些Activity並沒有設置背景導致界面背景爲黑色;

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

(5)其它:

1、通過Canvas的clipRect方法控制每個視圖每次刷新的區域,這樣可以避免刷新不必要的區域,從而規避過渡繪製的問題;

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

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

總結一下過渡繪製的檢測和解決方案:通過“開發者選項”中的“顯示過渡繪製”和Android提供的工具“HierarchyViewer”,以每個界面爲單位,可以完全檢測出每個界面的過渡繪製問題;因爲導致過渡繪製的原因不一,所以也會有多種對應的解決方案:

1、merge標籤可以解決相同佈局嵌套導致的過渡繪製問題;

2、ViewStub標籤可以解決動態加載頁面佈局,避免默認加載不必要佈局的問題;

3、Space標籤可以解決只佔位、不刷新的視圖問題;

4、去掉Window背景可以解決所有界面的過渡繪製問題;

5、clipRect可以解決只刷新固定區域的問題;

6、不必要的alpha值設置可以解決同一視圖被多次繪製的問題;

7、最重要的是產品設計合理,多和產品、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跟蹤是否存在耗時操作;通過“顯示過渡繪製”跟蹤是否存在過渡繪製等。

  • 啓用嚴格模式(Strict mode enabled):噹噹前界面在主線程中存在耗時操作時,會閃爍屏幕,但只會提示你存在耗時操作,不會告訴你具體的地方;如果要精確定位具體哪裏耗時,應該在代碼中添加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

2、IDE Android Studio

Android Studio是谷歌官方提供的集成開發環境(後面簡稱AS),同樣作爲Android集成開發環境的Eclipse很好用,但AS更高效、順手、便捷,這在沒有使用AS之前是無法感受到的。AS不僅提供了程序開發、構建、調試的環境,還提供了一系列優化應用質量的工具,這些工具包括靜態代碼檢測工具Inspect Code、Android Monitor、Analyze APK...,同時還集成了Android Device Monitor。通過這三個工具基本上可以檢測、分析、定位大部分Android應用的性能問題。

Inspect Code:

AS中的Insepct Code是用於對代碼進行靜態質量分析的工具,它是lint的增強版,可以檢測出來很多潛在的問題,同時給你提供改善建議;它不僅可以對整個工程、某個module、某個文件進行所有規則的代碼靜態質量檢測,還可以針對某一項規則對整個工程進行檢測:

在Inspection窗口的左側,有提供了一系列快捷按鈕用於快速分析、定位、修復代碼中的問題:

打開Settings的Inspections選項,可以看到這個工具能夠檢測出很多關於Java和Android方面的性能問題,比如佈局導致的過渡繪製、在onDraw方法中創建新的實例、Handler內部類導致的潛在內存泄露、使用SparseArray代替HashMap的建議、佈局層次太深、TypedArrays和VelocityTrackers沒有調用recycle方法導致的內存泄露、存在沒有使用的資源文件、系統方法取代自定義實現功能代碼塊的建議、IO操作導致的內存泄露問題、String和StringBuilder的相互替換等等;隨着AS的不斷更新,這個功能在不斷完善,最新的AS版本中,很多代碼層面的問題都能夠被檢測出來。

通過這個工具可以刪掉無用資源,檢測出明顯的性能問題,以及對代碼可讀性和性能方面的建議,使用起來很簡單,建議每天作爲日常,提交代碼前都檢測一次,這比在持續集成過程中,使用sonar等代碼質量工具分析更方便。

更多關於Inspect Code的描述參見:

(1)Inspection Tool Window

(2)Android Studio提高代碼質量必殺技:Inspact Code

Android Monitor:

Andorid Monitor提供了一系列的性能檢測工具,通過它可以幫助你剖析應用的性能,以便優化、調試和改善應用各方面的性能問題;Android Monitor可以從如下幾個方面對真機/模擬器中正在運行的程序進行性能監控:

(1)Log日誌,包括系統日誌和自定義日誌;

(2)實時監控內存、CPU、GPU的使用情況;

(3)實時監控網絡流量的消耗(只適合於真機);

(4)採集運行時信息並保存爲文件,供工具分析;

LogCat日誌窗口:

通過Logcat日誌窗口可以查看系統事件以及程序自定義的日誌信息,比如GC消息、程序運行時異常日誌、當前啓動應用的包名及入口等;它不僅提供了實時查看設備日誌信息的功能,還有一段時間的日誌緩存;同時提供了按照搜索(支持正則表達式)、按照等級/自定義標籤/指定包名篩選日誌的功能,以幫助你快速定位問題。

Logcat窗口是以行爲單位對日誌進行緩存,當窗口中的緩存日誌超過指定的行數上限時,會刪掉最先緩存的日誌。如果你覺得窗口中的日誌緩存清除太快了,不便於跟蹤問題,可以在AS安裝目錄下的"bin/idea.properties"文件中增加“idea.cycle.buffer.size=你想緩存的行數”來調整窗口給的日誌緩存行數,但建議不要調得太高,否則會嚴重影響AS的體驗,緩存行數越多AS就會越卡,控制在5000行以內已經基本滿足需求了。

在程序中使用Log類打印日誌時,TAG的長度建議不要超過23個字符,否則會做截斷處理,影響問題的準確跟蹤。

Logcat窗口的左側有一列工具快捷按鈕,方便我們快速找到我們需要的信息:

內存監控窗口:Memory Monitor

CPU監控窗口:CPU Monitor

GPU監控窗口:GPU Monitor

網絡流量監控窗口:Network Monitor

HPROF查看和分析工具:HPROF Viewer and Analyzer

內存分配跟蹤工具:Allocation Tracker

函數調用棧分析工具:Method Tracer

查看系統信息工具:System Information

Analyze APK...:

3、其它性能優化工具

五、參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章