Android開發性能優化總結(二)

一、安卓UI性能檢測與優化
UI是安卓應用程序與用戶打交道的最直接途徑,UI設計的好不好,直接影響到用戶的體驗,如果沒有達到他們心目中的自然流暢細節,用戶要是能夠感覺出來,少則影響心情,多則卸載應用;所以一個應用的UI顯示性能問題就不得不被開發人員重視。

1.UI卡頓常見原因:
在UI線程中做了耗時操作,導致UI線程卡頓;

佈局Layout過於複雜,無法在16ms內完成渲染;

同一時間動畫執行的次數過多,導致CPU或GPU負載過重;

View過度繪製,導致某些像素在同一幀時間內被繪製多次,從而使CPU或GPU負載過重;

View頻繁的觸發measure、layout,導致measure、layout累計耗時過多及整個View頻繁的重新渲染;

內存頻繁觸發GC過多(同一幀中頻繁創建內存),導致暫時阻塞渲染操作;

冗餘資源及邏輯等導致加載和執行緩慢;

2.UI性能分析

2.1使用HierarchyViewer分析UI性能
Android SDK提供了一個工具HierarchyViewer,可以用來分析UI佈局複雜程度及冗餘等
在\sdk\tools\目錄下有個hierarchyviewer.bat,雙擊可啓動(如下圖),注意,啓動前你需要啓動你的模擬器或者接入你的手機進入調試模式
(1)選中其中一個item,點擊Load View Hierarchy,會出現如下圖:


上圖顯示了我的應用程序中MainActivity的View樹,通過這個樹可以分析出View嵌套的冗餘層級,左下角可以輸入View的id直接自動跳轉到中間顯示;Save as PNG用來把左側樹保存爲一張圖片;Capture Layers用來保存爲psd的PhotoShop素材;右上角爲View的總體框架圖;右側居中顯示的是選中View的當前屬性和狀態;右下角顯示當前View在Activity中的位置以及模塊分佈等;左下角三個進行切換;Load
 View Hierarchy用來手動刷新變化(在頁面變更時要手動刷新)。



上圖可以很方便的查看到當前View的許多信息;上圖最底那三個彩色圓點代表了當前View的性能指標,從左到右依次代表測量、佈局、繪製的渲染時間,紅色和黃色的點代表速度渲染較慢的View(當然了,有些時候較慢不代表有問題,譬如ViewGroup子節點越多、結構越複雜,性能就越差)。

在自定義View的性能調試時,可以使用HierarchyViewer上面的invalidate Layout和requestLayout按鈕,它可以幫助我們調試自定義View執行invalidate()和requestLayout()過程,我們只需要在代碼的相關地方打上斷點就行了,接下來通過它觀察繪製即可。
2.2使用Android Lint進行資源和佈局的優化

Android Lint是SDK Tools 16 (ADT 16)之後才引入的工具,通過它對Android工程源代碼進行掃描和檢查,可發現潛在的問題,以便程序員及早修正這個問題。也就是說,它可以用來檢查你的代碼是否合法,當然,它也可以用來檢測UI佈局和資源文件的冗餘性。
(1)在Android Studio中使用Lint:
將鼠標放在代碼區點擊右鍵->Analyze->Inspect
 Code–>界面選擇你要檢測的模塊->OK  出現如下圖:


如上,它提示我們,控件的顯示的字符串最好使用@String的形式,當前佈局中有個RelativeLayout冗餘了,應該去掉。
(2)在Eclipse中,則提供了一個全局的lint檢查,可以在Window->Show
 View->other->Lint Warnings調出lint檢查視圖(一般都默認出現在控制檯區域),Lint Warnings如下圖


它也給了我們相應的提示和問題出現的位置,大家可以自行嘗試。另外,Eclipse在編譯導出APK時,也會執行lint檢查,這時的lint檢查將更加詳細,你能看到更多的信息。
2.3 使用Traceview進行分析優化
關於UI卡頓問題我們還可以通過運行Traceview工具進行分析,它是一個分析器,記錄了應用程序中每個函數的執行時間;我們可以打開DDMS然後選擇一個進程,接着點擊上面的“Start
 Method Profiling”按鈕(紅色小點變爲黑色即開始運行),然後操作我們的卡頓UI(小範圍測試,所以操作最好不要超過5s),完事再點一下剛纔按的那個按鈕,稍等片刻即可出現下圖,如下:



整個界面上面是你測試的進程中每個線程運行的時間線,下面是每個方法執行的各個指標的值。通過上圖的時間面板可以直觀發現,整個trace時間段main線程做的事情特別多,其他的做的相對較少。當我們選擇上面的一個線程後可以發現下面列出了該方法的Parents和Children,它主要展示了線程中各個方法的調用信息(CPU使用時間、調用次數等),這些信息就是我們分析UI性能卡頓的核心關注點,所以我們先看幾個重要的屬性說明,如下:

屬性名 含義
name    線程中調運的方法名;
Incl CPU Time   當前方法(包含內部調運的子方法)執行佔用的CPU時間;
Excl CPU Time   當前方法(不包含內部調運的子方法)執行佔用的CPU時間;
Incl Real Time  當前方法(包含內部調運的子方法)執行的真實時間,ms單位;
Excl Real Time  當前方法(不包含內部調運的子方法)執行的真實時間,ms單位;
Calls+Recur Calls/Total 當前方法被調運的次數及遞歸調運佔總調運次數百分比;
CPU Time/Call   當前方法調運CPU時間與調運次數比,即當前方法平均執行CPU耗時時間;
Real Time/Call  當前方法調運真實時間與調運次數比,即當前方法平均執行真實耗時時間;(重點關注)


有了對上面Traceview圖表的一個認識之後我們就來看看具體導致UI性能後該如何切入分析,一般Traceview可以定位兩類性能問題:

方法調運一次需要耗費很長時間導致卡頓;

方法調運一次耗時不長,但被頻繁調運導致累計時長卡頓。

譬如有時候我們寫完App在使用時不覺得有啥大的影響,但是當我們啓動完App後靜止在那卻十分費電或者導致設備發熱,這種情況我們就可以打開Traceview然後按照Cpu Time/Call或者Real Time/Call進行降序排列,然後打開可疑的方法及其child進行分析查看,然後再回到代碼定位檢查邏輯優化即可。
2.4 使用Systrace進行分析優化
Systrace它是對整個系統進行分析(同一時間軸包含應用及SurfaceFlinger、WindowManagerService等模塊、服務運行信息),不過這個工具需要你的設備內核支持trace(命令行檢查:/sys/kernel/debug/tracing)且設備是eng或userdebug版本纔可以,所以使用前麻煩自己確認一下。

打開DDMS->點擊上面工具欄的Capture system wide trace using Android systrace->設置時間與選項點擊OK就開始了抓取,接着操作APP,完事生成一個trace.html文件,用Chrome打開即可如下圖:

在瀏覽器中瀏覽分析該文件我們可以通過鍵盤的W-A-S-D鍵來放大縮小平移視圖,由於上面我們在進行trace時選擇了一些選項,所以上圖生成了左上方相關的CPU頻率、負載、狀態等信息,其中的CPU N代表了CPU核數,每個CPU行的柱狀圖表代表了當前時間段當前核上的運行信息;下面我們再來看看SurfaceFlinger的解釋,如下:

這裏寫圖片描述

可以看見上面左邊欄的SurfaceFlinger其實就是負責繪製Android程序UI的服務,所以SurfaceFlinger能反應出整體繪製情況,可以關注上圖VSYNC-app一行可以發現前5s多基本都能夠達到16ms刷新間隔,5s多開始到7s多大於了15ms,說明此時存在繪製丟幀卡頓;同時可以發現surfaceflinger一行明顯存在類似不規律間隔,這是因爲有的地方是不需要重新渲染UI,所以有大範圍不規律,有的是因爲阻塞導致不規律,明顯可以發現04s間大多是不需要渲染,而5s以後大多是阻塞導致;對應這個時間點我們放大可以看到每個部分所使用的時間和正在執行的任務,具體如下: 
這裏寫圖片描述

可以發現具體的執行明顯存在超時性能卡頓(原點不是綠色的基本都代表存在一定問題,下面和右側都會提示你選擇的幀相關詳細信息或者alert信息),但是遺憾的是通過Systrace只能大體上發現是否存在性能問題,具體問題還需要通過Traceview或者代碼中嵌入Trace工具類等去繼續詳細分析。
2.5 使用traces.txt文件分析ANR

ANR(Application Not Responding)是Android中應用響應超時的表現;ANR是直接卡死UI不動且必須要解掉的Bug,我們必須儘量在開發時避免它的出現。

我們應用開發中常見的ANR主要有如下幾類:

按鍵觸摸事件派發超時ANR,一般閾值爲5s(設置中開啓ANR彈窗,默認有事件派發纔會觸發彈框ANR);

廣播阻塞ANR,一般閾值爲10s(設置中開啓ANR彈窗,默認不彈框,只有log提示);

服務超時ANR,一般閾值爲20s(設置中開啓ANR彈窗,默認不彈框,只有log提示);

當ANR發生時除了logcat可以看見的log以外我們還可以在系統指定目錄下找到traces文件或dropbox文件進行分析,

然後我們用txt編輯器打開可以發現如下結構分析:

 //顯示進程id、ANR發生時間點、ANR發生進程包名

----- pid 19073 at 2015-10-08 17:24:38 -----

Cmd line: com.example.yanbo.myapplication

//一些GC等object信息,通常可以忽略

......

//ANR方法堆棧打印信息!重點!

DALVIK THREADS (18):

"main" prio=5 tid=1 Sleeping

  | group="main" sCount=1 dsCount=0 obj=0x7497dfb8 self=0x7f9d09a000

  | sysTid=19073 nice=0 cgrp=default sched=0/0 handle=0x7fa106c0a8

  | state=S schedstat=( 125271779 68162762 280 ) utm=11 stm=1 core=0 HZ=100

  | stack=0x7fe90d3000-0x7fe90d5000 stackSize=8MB

  | held mutexes=

  at java.lang.Thread.sleep!(Native method)

  - sleeping on <0x0a2ae345> (a java.lang.Object)

  at java.lang.Thread.sleep(Thread.java:1031)

  - locked <0x0a2ae345> (a java.lang.Object)

//真正導致ANR的問題點,可以發現是onClick中有sleep導致。我們平時可以類比分析即可,這裏不詳細說明。

  at java.lang.Thread.sleep(Thread.java:985)

  at com.example.yanbo.myapplication.MainActivity$1.onClick(MainActivity.java:21)

  at android.view.View.performClick(View.java:4908)

  at android.view.View$PerformClick.run(View.java:20389)

  at android.os.Handler.handleCallback(Handler.java:815)

  at android.os.Handler.dispatchMessage(Handler.java:104)

  at android.os.Looper.loop(Looper.java:194)

  at android.app.ActivityThread.main(ActivityThread.java:5743)

  at java.lang.reflect.Method.invoke!(Native method)

  at java.lang.reflect.Method.invoke(Method.java:372)

  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)

  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)

......

//省略一些不常關注堆棧打印

......
2.6 UI性能優化總結
佈局優化;儘量使用include、merge、ViewStub標籤,儘量不存在冗餘嵌套及過於複雜佈局,儘量使用GONE替換INVISIBLE,使用weight後儘量將width和heigh設置爲0dp減少運算,Item存在非常複雜的嵌套時考慮使用自定義View來取代,減少measure與layout次數等。

列表及Adapter優化;儘量複用getView方法中的相關View,不重複獲取實例導致卡頓,列表儘量在滑動過程中不進行UI元素刷新等。

背景和圖片等內存分配優化;儘量減少不必要的背景設置,圖片儘量壓縮處理顯示,儘量避免頻繁內存抖動等問題出現。

自定義View等繪圖與佈局優化;儘量避免在draw、measure、layout中做過於耗時及耗內存操作,尤其是draw方法中,儘量減少draw、measure、layout等執行次數。

避免ANR,不要在UI線程中做耗時操作,遵守ANR規避守則,譬如多次操作數據庫操作等。

二、內存性能檢測優化

每個Android應用程序都執行在自己的虛擬機中,那瞭解Java的一定明白,每個虛擬機必定會有堆內存閾值限制(值得一提的是這個閾值一般都由廠商依據硬件配置及設備特性自己設定,沒有統一標準,可以爲64M,也可以爲128M等;它的配置是在Android的屬性系統的/system/build.prop中配置dalvik.vm.heapsize=128m即可,若存在dalvik.vm.heapstartsize則表示初始申請大小),也即一個應用進程同時存在的對象必須小於閾值規定的內存大小纔可以正常運行。

接着我們運行的App在自己的虛擬機中內存管理基本就是遵循Java的內存管理機制了,系統在特定的情況下主動進行垃圾回收。但是要注意的一點就是在Android系統中執行垃圾回收(GC)操作時所有線程(包含UI線程)都必須暫停,等垃圾回收操作完成之後其他線程才能繼續運行。這些GC垃圾回收一般都會有明顯的log打印出回收類型,常見的如下:

GC_MALLOC——內存分配失敗時觸發;

GC_CONCURRENT——當分配的對象大小超過一個限定值(不同系統)時觸發;

GC_EXPLICIT——對垃圾收集的顯式調用(System.gc()) ;

GC_EXTERNAL_ALLOC——外部內存分配失敗時觸發;
1.Android內存泄露檢測
衆所周知,在Java中有些對象的生命週期是有限的,當它們完成了特定的邏輯後將會被垃圾回收;但是,如果在對象的生命週期本來該被垃圾回收時這個對象還被別的對象所持有引用,那就會導致內存泄漏;這樣的後果就是隨着我們的應用被長時間使用,他所佔用的內存越來越大。

(1)AndroidStudio中有個Memory窗口如下(在Debug模式下查看內存波動),這裏就不詳細說明了:



(2)DDMS-Heap內存監測工具,窗口如下



選擇工具欄中的update heap既可以在Heap視圖中看到相關內存分配和GC信息
(3)使用MAT內存檢測工具
一個基於Eclipse的內存分析工具,是一個快速、功能豐富的JAVA heap分析工具

這個工具是非常專業級的工具,很多大神使用,但它過於龐大和複雜,這裏不再贅述,有興趣的可以自行搜索
(4)使用LeakCanary內存泄漏檢測工具
發佈了43 篇原創文章 · 獲贊 9 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章