天天看點

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

1 背景

其實有點不想寫這篇文章的,但是又想寫,有些沖突。不想寫的原因是随便上網一搜一堆關于性能的建議,感覺大家你一總結、我一總結的都說到了很多優化注意事項,但是看過這些文章後大多數存在一個問題就是隻給出啥啥啥不能用,啥啥啥該咋用等,卻很少有較為系統的進行真正性能案例分析的,大多數都是嘴上喊喊或者死記住規則而已(當然了,這話我自己聽着都有些刺耳,實在不好意思,其實關于性能優化的優質博文網上也還是有很多的,譬如Google官方都已經推出了優化專題,我這裡隻是總結下自的感悟而已,若有得罪歡迎拍磚,我願挨打,因為我之前工作的一半時間都是負責性能優化)。

當然了,本文不會就此編輯這麼一次,因為技術在發展,工具在強大(寫着寫着Android Studio 1.4版本都推送了),自己的經驗也在增加,是以本文自然不會覆寫所有性能優化及分析;解決的辦法就是該文章會長期維護更新,同時在評論區歡迎你關于性能優化點子的探讨。

Android應用的性能問題其實可以劃分為幾個大的子產品的,而且都具有相對不錯的優化調試技巧,下面我們就會依據一個項目正常開發的大類型來進行一些分析講解。

PS:之前呆過一家初創醫療網際網路公司,别提性能優化了,老闆立完新項目後一個月就要求見到上線成品,這種壓迫下談何性能優化,純屬扯蛋,是以不到三個月時間我主動選擇撤了,這種現象後來我一打聽發現在很多初創公司都很嚴重,都想速成卻忽略了體驗。

PPPS:本文隻是達到抛磚引玉的作用,很多東西細究下去都是值得深入研究的,再加上性能優化本來就是一個需要綜合考量的任務,不是說會了本文哪一點就能做性能分析了,需要面面俱到才可高效定位問題原因。

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

2 應用UI性能問題分析

UI可謂是一個應用的臉,是以每一款應用在開發階段我們的互動、視覺、動畫工程師都拼命的想讓它變得自然大方美麗,可是現實總是不盡人意,動畫和互動總會覺得開發做出來的應用用上去感覺不自然,沒有達到他們心目中的自然流暢細節;這種情況之下就更别提釋出給終端使用者使用了,使用者要是能夠感覺出來,少則影響心情,多則解除安裝應用;是以一個應用的UI顯示性能問題就不得不被開發人員重視。

2-1 應用UI卡頓原理

人類大腦與眼睛對一個畫面的連貫性感覺其實是有一個界限的,譬如我們看電影會覺得畫面很自然連貫(幀率為24fps),用手機當然也需要感覺螢幕操作的連貫性(尤其是動畫過度),是以Android索性就把達到這種流暢的幀率規定為60fps。

有了上面的背景,我們開發App的幀率性能目标就是保持在60fps,也就是說我們在進行App性能優化時心中要有如下準則:

換算關系:60幀/秒-----------16ms/幀;

準則:盡量保證每次在16ms内處理完所有的CPU與GPU計算、繪制、渲染等操作,否則會造成丢幀卡頓問題。
           

從上面可以看出來,所謂的卡頓其實是可以量化的,每次是否能夠成功渲染是非常重要的問題,16ms能否完整的做完一次操作直接決定了卡頓性能問題。

當然了,針對Android系統的設計我們還需要知道另一個常識;虛拟機在執行GC垃圾回收操作時所有線程(包括UI線程)都需要暫停,當GC垃圾回收完成之後所有線程才能夠繼續執行(這個細節下面小節會有詳細介紹)。也就是說當在16ms内進行渲染等操作時如果剛好遇上大量GC操作則會導緻渲染時間明顯不足,也就進而導緻了丢幀卡頓問題。

有了上面這兩個簡單的理論基礎之後我們下面就會探讨一些UI卡頓的原因分析及解決方案。

2-2 應用UI卡頓常見原因

我們在使用App時會發現有些界面啟動卡頓、動畫不流暢、清單等滑動時也會卡頓,究其原因,很多都是丢幀導緻的;通過上面卡頓原理的簡單說明我們從應用開發的角度往回推理可以得出常見卡頓原因,如下:

  1. 人為在UI線程中做輕微耗時操作,導緻UI線程卡頓;
  2. 布局Layout過于複雜,無法在16ms内完成渲染;
  3. 同一時間動畫執行的次數過多,導緻CPU或GPU負載過重;
  4. View過度繪制,導緻某些像素在同一幀時間内被繪制多次,進而使CPU或GPU負載過重;
  5. View頻繁的觸發measure、layout,導緻measure、layout累計耗時過多及整個View頻繁的重新渲染;
  6. 記憶體頻繁觸發GC過多(同一幀中頻繁建立記憶體),導緻暫時阻塞渲染操作;
  7. 備援資源及邏輯等導緻加載和執行緩慢;
  8. 臭名昭著的ANR;

可以看見,上面這些導緻卡頓的原因都是我們平時開發中非常常見的。有些人可能會覺得自己的應用用着還蠻OK的,其實那是因為你沒進行一些瞬時測試和壓力測試,一旦在這種環境下運作你的App你就會發現很多性能問題。

2-3 應用UI卡頓分析解決方法

分析UI卡頓我們一般都借助工具,通過工具一般都可以直覺的分析出問題原因,進而反推尋求優化方案,具體如下細說各種強大的工具。

2-3-1 使用HierarchyViewer分析UI性能

我們可以通過SDK提供的工具HierarchyViewer來進行UI布局複雜程度及備援等分析,如下:

選中一個Window界面item,然後點選右上方Hierarchy window或者Pixel Perfect window(這裡不介紹,主要用來檢查像素屬性的)即可操作。

先看下Hierarchy window,如下:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

一個Activity的View樹,通過這個樹可以分析出View嵌套的備援層級,左下角可以輸入View的id直接自動跳轉到中間顯示;Save as PNG用來把左側樹儲存為一張圖檔;Capture Layers用來儲存psd的PhotoShop分層素材;右側劇中顯示選中View的目前屬性狀态;右下角顯示目前View在Activity中的位置等;左下角三個進行切換;Load View Hierarchy用來手動重新整理變化(不會自動重新整理的)。當我們選擇一個View後會如下圖所示:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

類似上圖可以很友善的檢視到目前View的許多資訊;上圖最底那三個彩色原點代表了目前View的性能名額,從左到右依次代表測量、布局、繪制的渲染時間,紅色和黃色的點代表速度渲染較慢的View(當然了,有些時候較慢不代表有問題,譬如ViewGroup子節點越多、結構越複雜,性能就越差)。

當然了,在自定義View的性能調試時,HierarchyViewer上面的invalidate Layout和requestLayout按鈕的功能更加強大,它可以幫助我們debug自定義View執行invalidate()和requestLayout()過程,我們隻需要在代碼的相關地方打上斷點就行了,接下來通過它觀察繪制即可。

可以發現,有了HierarchyViewer調試工具,我們的UI性能分析變得十分容易,這個工具也是我們開發中調試UI的利器,在平時寫代碼時會時常伴随我們左右。

2-3-2 使用GPU過度繪制分析UI性能

我們對于UI性能的優化還可以通過開發者選項中的GPU過度繪制工具來進行分析。在設定->開發者選項->調試GPU過度繪制(不同裝置可能位置或者叫法不同)中打開調試後可以看見如下圖(對settings目前界面過度繪制進行分析):

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

可以發現,開啟後在我們想要調試的應用界面中可以看到各種顔色的區域,具體含義如下:

顔色 含義
無色 WebView等的渲染區域
藍色 1x過度繪制
綠色 2x過度繪制
淡紅色 3x過度繪制
紅色 4x(+)過度繪制

由于過度繪制指在螢幕的一個像素上繪制多次(譬如一個設定了背景色的TextView就會被繪制兩次,一次背景一次文本;這裡需要強調的是Activity設定的Theme主題的背景不被算在過度繪制層級中),是以最理想的就是繪制一次,也就是藍色(當然這在很多絢麗的界面是不現實的,是以大家有個度即可,我們的開發性能優化标準要求最極端界面下紅色區域不能長期持續超過螢幕三分之一,可見還是比較寬松的規定),是以我們需要依據此顔色分布進行代碼優化,譬如優化布局層級、減少沒必要的背景、暫時不顯示的View設定為GONE而不是INVISIBLE、自定義View的onDraw方法設定canvas.clipRect()指定繪制區域或通過canvas.quickreject()減少繪制區域等。

2-3-3 使用GPU呈現模式圖及FPS考核UI性能

Android界面流暢度除過視覺感覺以外是可以考核的(測試妹子專用),常見的方法就是通過GPU呈現模式圖或者實時FPS顯示進行考核,這裡我們主要針對GPU呈現模式圖進行下說明,因為FPS考核測試方法有很多(譬如自己寫代碼實作、第三方App測試、固件支援等),是以不做統一說明。

通過開發者選項中GPU呈現模式圖工具來進行流暢度考量的流程是(注意:如果是在開啟應用後才開啟此功能,記得先把應用結束後重新啟動)在設定->開發者選項->GPU呈現模式(不同裝置可能位置或者叫法不同)中打開調試後可以看見如下圖(對settings目前界面上下滑動清單後的圖表):

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

當然,也可以在執行完UI滑動操作後在指令行輸入如下指令檢視指令行列印的GPU渲染資料(分析依據:Draw + Process + Execute = 完整的顯示一幀時間 < 16ms):

打開上圖可視化工具後,我們可以在手機畫面上看到豐富的GPU繪制圖形資訊,分别展示了StatusBar、NavgationBar、Activity區域等的GPU渲染時間資訊,随着界面的重新整理,界面上會以實時柱狀圖來顯示每幀的渲染時間,柱狀圖越高表示渲染時間越長,每個柱狀圖偏上都有一根代表16ms基準的綠色橫線,每一條豎着的柱狀線都包含三部分(藍色代表測量繪制Display List的時間,紅色代表OpenGL渲染Display List所需要的時間,黃色代表CPU等待GPU處理的時間),隻要我們每一幀的總時間低于基準線就不會發生UI卡頓問題(個别超出基準線其實也不算啥問題的)。

可以發現,這個工具是有局限性的,他雖然能夠看出來有幀耗時超過基準線導緻了丢幀卡頓,但卻分析不到造成丢幀的具體原因。是以說為了配合解決分析UI丢幀卡頓問題我們還需要借助traceview和systrace來進行原因追蹤,下面我們會介紹這兩種工具的。

2-3-4 使用Lint進行資源及備援UI布局等優化

上面說了,備援資源及邏輯等也可能會導緻加載和執行緩慢,是以我們就來看看Lint這個工具是如何發現優化這些問題的(當然了,Lint實際的功能是非常強大的,我們開發中也是經常使用它來發現一些問題的,這裡主要有點針對UI性能的說明了,其他的雷同)。

在Android Studio 1.4版本中使用Lint最簡單的辦法就是将滑鼠放在代碼區點選右鍵->Analyze->Inspect Code–>界面選擇你要檢測的子產品->點選确認開始檢測,等待一下後會發現如下結果:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

可以看見,Lint檢測完後給了我們很多建議的,我們重點看一個關于UI性能的檢測結果;上圖中高亮的那一行明确說明了存在備援的UI層級嵌套,是以我們是可以點選跳進去進行優化處理掉的。

當然了,Lint還有很多功能,大家可以自行探索發揮,這裡隻是達到抛磚引玉的作用。

2-3-5 使用Memory監測及GC列印與Allocation Tracker進行UI卡頓分析

關于Android的記憶體管理機制下面的一節會詳細介紹,這裡我們主要針對GC導緻的UI卡頓問題進行詳細說明。

Android系統會依據記憶體中不同的記憶體資料類型分别執行不同的GC操作,常見應用開發中導緻GC頻繁執行的原因主要可能是因為短時間内有大量頻繁的對象建立與釋放操作,也就是俗稱的記憶體抖動現象,或者短時間内已經存在大量記憶體暫用介于門檻值邊緣,接着每當有新對象建立時都會導緻超越門檻值觸發GC操作。

如下是我工作中一個項目的一次經曆(我将代碼回退特意抓取的),出現這個問題的場景是一次壓力測試導緻整個系統卡頓,瞬間殺掉應用就OK了,究其原因最終查到是一個API的調運位置寫錯了方式,導緻一直被狂調,當普通使用時不會有問題,壓力測試必現卡頓。具體記憶體參考圖如下:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

與此抖動圖對應的LogCat抓取如下:

//截取其中比較密集一段LogCat,與上圖Memory檢測到的抖動圖對應,其中xxx為應用包名
......
10-06 00:59:45.619 xxx I/art: Explicit concurrent mark sweep GC freed 72515(3MB) AllocSpace objects, 65(2028KB) LOS objects, 80% free, 17MB/89MB, paused 3.505ms total 60.958ms
10-06 00:59:45.749 xxx I/art: Explicit concurrent mark sweep GC freed 5396(193KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.079ms total 100.522ms
......
10-06 00:59:48.059 xxx I/art: Explicit concurrent mark sweep GC freed 4693(172KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.227ms total 101.692ms
......
           

我們知道,類似上面logcat列印一樣,觸發垃圾回收的主要原因有以下幾種:

  • GC_MALLOC——記憶體配置設定失敗時觸發;
  • GC_CONCURRENT——當配置設定的對象大小超過一個限定值(不同系統)時觸發;
  • GC_EXPLICIT——對垃圾收集的顯式調用(System.gc()) ;
  • GC_EXTERNAL_ALLOC——外部記憶體配置設定失敗時觸發;

可以看見,這種不停的大面積列印GC導緻所有線程暫停的操作必定會導緻UI視覺的卡頓,是以我們要避免此類問題的出現,具體的常見優化方式如下:

  • 檢查代碼,盡量避免有些頻繁觸發的邏輯方法中存在大量對象配置設定;
  • 盡量避免在多次for循環中頻繁配置設定對象;
  • 避免在自定義View的onDraw()方法中執行複雜的操作及建立對象(譬如Paint的執行個體化操作不要寫在onDraw()方法中等);
  • 對于并發下載下傳等類似邏輯的實作盡量避免多次建立線程對象,而是交給線程池處理。

當然了,有了上面說明GC導緻的性能後我們就該定位分析問題了,可以通過運作DDMS->Allocation Tracker标簽打開一個新視窗,然後點選Start Tracing按鈕,接着運作你想分析的代碼,運作完畢後點選Get Allocations按鈕就能夠看見一個已配置設定對象的清單,如下:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

點選上面第一個表格中的任何一項就能夠在第二個表格中看見導緻該記憶體配置設定的棧資訊,通過這個工具我們可以很友善的知道代碼配置設定了哪類對象、在哪個線程、哪個類、哪個檔案的哪一行。譬如我們可以通過Allocation Tracker分别做一次Paint對象執行個體化在onDraw與構造方法的一個自定義View的記憶體跟蹤,然後你就明白這個工具的強大了。

PS一句,Android Studio新版本除過DDMS以外在Memory視圖的左側已經內建了Allocation Tracker功能,隻是用起來還是沒有DDMS的友善實用,如下圖:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

2-3-6 使用Traceview和dmtracedump進行分析優化

關于UI卡頓問題我們還可以通過運作Traceview工具進行分析,他是一個分析器,記錄了應用程式中每個函數的執行時間;我們可以打開DDMS然後選擇一個程序,接着點選上面的“Start Method Profiling”按鈕(紅色小點變為黑色即開始運作),然後操作我們的卡頓UI(小範圍測試,是以操作最好不要超過5s),完事再點一下剛才按的那個按鈕,稍等片刻即可出現下圖,如下:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

花花綠綠的一幅圖我們怎麼分析呢?下面我們解釋下如何通過該工具定位問題:

整個界面包括上下兩部分,上面是你測試的程序中每個線程運作的時間線,下面是每個方法(包含parent及child)執行的各個名額的值。通過上圖的時間面闆可以直覺發現,整個trace時間段main線程做的事情特别多,其他的做的相對較少。當我們選擇上面的一個線程後可以發現下面的性能面闆很複雜,其實這才是TraceView的核心圖表,它主要展示了線程中各個方法的調用資訊(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進行分析檢視,然後再回到代碼定位檢查邏輯優化即可;當然了,我們也可以通過該工具來trace我們自定義View的一些方法來權衡性能問題,這裡不再一一列舉喽。

可以看見,Traceview能夠幫助我們分析程式性能,已經很友善了,然而Traceview家族還有一個更加直覺強大的小工具,那就是可以通過dmtracedump生成方法調用圖。具體做法如下:

通過這個生成的方法調運圖我們可以更加直覺的發現一些方法的調運異常現象。不過本人優化到現在還沒怎麼用到它,每次用到Traceview分析就已經搞定問題了,是以說dmtracedump自己酌情使用吧。

PS一句,Android Studio新版本除過DDMS以外在CPU視圖的左側已經內建了Traceview(start Method Tracing)功能,隻是用起來還是沒有DDMS的友善實用(這裡有一篇AS MT個人覺得不錯的分析文章(引用自網絡,連結屬于原作者功勞)),如下圖:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

2-3-7 使用Systrace進行分析優化

Systrace其實有些類似Traceview,它是對整個系統進行分析(同一時間軸包含應用及SurfaceFlinger、WindowManagerService等子產品、服務運作資訊),不過這個工具需要你的裝置核心支援trace(指令行檢查/sys/kernel/debug/tracing)且裝置是eng或userdebug版本才可以,是以使用前麻煩自己确認一下。

我們在分析UI性能時一般隻關注圖形性能(是以必須選擇Graphics和View,其他随意),同時一般對于卡頓的抓取都是5s,最多10s。啟動Systrace進行資料抓取可以通過兩種方式,指令行方式如下:

python systrace.py --time= -o mynewtrace.html sched gfx view wm
           

圖形模式:

打開DDMS->Capture system wide trace using Android systrace->設定時間與選項點選OK就開始了抓取,接着操作APP,完事生成一個trace.html檔案,用Chrome打開即可如下圖:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

在Chrome中浏覽分析該檔案我們可以通過鍵盤的W-A-S-D鍵來搞定,由于上面我們在進行trace時選擇了一些選項,是以上圖生成了左上方相關的CPU頻率、負載、狀态等資訊,其中的CPU N代表了CPU核數,每個CPU行的柱狀圖表代表了目前時間段目前核上的運作資訊;下面我們再來看看SurfaceFlinger的解釋,如下:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

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

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

可以發現具體的執行明顯存在逾時性能卡頓(原點不是綠色的基本都代表存在一定問題,下面和右側都會提示你選擇的幀相關詳細資訊或者alert資訊),但是遺憾的是通過Systrace隻能大體上發現是否存在性能問題,具體問題還需要通過Traceview或者代碼中嵌入Trace工具類等去繼續詳細分析,總之很蛋疼。

PS:如果你想使用Systrace很輕松的分析定位所有問題,看明白所有的行含義,你還需要具備非常紮實的Android系統架構的原理才可以将該工具使用的得心應手。

2-3-8 使用traces.txt檔案進行ANR分析優化

ANR(Application Not Responding)是Android中AMS與WMS監測應用響應逾時的表現;之是以把臭名昭著的ANR單獨作為UI性能卡頓的分析來說明是因為ANR是直接卡死UI不動且必須要解掉的Bug,我們必須盡量在開發時避免他的出現,當然了,萬一出現了那就用下面介紹的方法來分析吧。

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

  • 按鍵觸摸事件派發逾時ANR,一般門檻值為5s(設定中開啟ANR彈窗,預設有事件派發才會觸發彈框ANR);
  • 廣播阻塞ANR,一般門檻值為10s(設定中開啟ANR彈窗,預設不彈框,隻有log提示);
  • 服務逾時ANR,一般門檻值為20s(設定中開啟ANR彈窗,預設不彈框,隻有log提示);

當ANR發生時除過logcat可以看見的log以外我們還可以在系統指定目錄下找到traces檔案或dropbox檔案進行分析,發生ANR後我們可以通過如下指令得到ANR trace檔案:

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

//顯示程序id、ANR發生時間點、ANR發生程序包名
----- pid  at -- :: -----
Cmd line: com.example.yanbo.myapplication
//一些GC等object資訊,通常可以忽略
......
//ANR方法堆棧列印資訊!重點!
DALVIK THREADS ():
"main" prio= tid= Sleeping
  | group="main" sCount= dsCount= obj= self=
  | sysTid= nice= cgrp=default sched=/ handle=
  | state=S schedstat=(    ) utm= stm= core= HZ=
  | stack=- stackSize=MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:)
  - locked <> (a java.lang.Object)
//真正導緻ANR的問題點,可以發現是onClick中有sleep導緻。我們平時可以類比分析即可,這裡不詳細說明。
  at java.lang.Thread.sleep(Thread.java:)
  at com.example.yanbo.myapplication.MainActivity$1.onClick(MainActivity.java:)
  at android.view.View.performClick(View.java:)
  at android.view.View$PerformClick.run(View.java:)
  at android.os.Handler.handleCallback(Handler.java:)
  at android.os.Handler.dispatchMessage(Handler.java:)
  at android.os.Looper.loop(Looper.java:)
  at android.app.ActivityThread.main(ActivityThread.java:)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:)
......
//省略一些不常關注堆棧列印
......
           

至此常見的應用開發中ANR分析定位就可以解決了。

2-4 應用UI性能分析解決總結

可以看見,關于Android UI卡頓的性能分析還是有很多工具的,上面隻是介紹了應用開發中我們經常使用的一些而已,還有一些其他的,譬如Oprofile等工具不怎麼常用,這裡就不再詳細介紹。

通過上面UI性能的原理、原因、工具分析總結可以發現,我們在開發應用時一定要時刻重視性能問題,如若真的沒留意出現了性能問題,不妨使用上面的一些案例方式進行分析。但是那終歸是補救措施,在我們知道上面UI卡頓原理之後我們應該盡量從項目代碼架構搭建及編寫時就避免一些UI性能問題,具體項目中常見的注意事項如下:

  • 布局優化;盡量使用include、merge、ViewStub标簽,盡量不存在備援嵌套及過于複雜布局(譬如10層就會直接異常),盡量使用GONE替換INVISIBLE,使用weight後盡量将width和heigh設定為0dp減少運算,Item存在非常複雜的嵌套時考慮使用自定義Item View來取代,減少measure與layout次數等。
  • 清單及Adapter優化;盡量複用getView方法中的相關View,不重複擷取執行個體導緻卡頓,清單盡量在滑動過程中不進行UI元素重新整理等。
  • 背景和圖檔等記憶體配置設定優化;盡量減少不必要的背景設定,圖檔盡量壓縮處理顯示,盡量避免頻繁記憶體抖動等問題出現。
  • 自定義View等繪圖與布局優化;盡量避免在draw、measure、layout中做過于耗時及耗記憶體操作,尤其是draw方法中,盡量減少draw、measure、layout等執行次數。
  • 避免ANR,不要在UI線程中做耗時操作,遵守ANR規避守則,譬如多次資料庫操作等。

當然了,上面隻是列出了我們項目中常見的一些UI性能注意事項而已,相信還有很多其他的情況這裡沒有說到,歡迎補充。還有一點就是我們上面所謂的UI性能優化分析總結等都是建議性的,因為性能這個問題是一個涉及面很廣很泛的問題,有些優化不是必需的,有些優化是必需的,有些優化掉以後又是得不償失的,是以我們一般着手解決那些必須的就可以了。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

3 應用開發Memory記憶體性能分析優化

說完了應用開發中的UI性能問題後我們就該來關注應用開發中的另一個重要、嚴重、非常重要的性能問題了,那就是記憶體性能優化分析。Android其實就是嵌入式裝置,嵌入式裝置核心關注點之一就是記憶體資源;有人說現在的裝置都在堆硬體配置(譬如國産某米的某兔跑分手機、盒子等),是以記憶體不會再像以前那麼緊張了,其實這句話聽着沒錯,但為啥再牛逼配置的Android裝置上有些應用還是越用系統越卡呢?這裡面的原因有很多,不過相信有了這一章下面的内容分析,作為一個移動開發者的你就有能力打理好自己應用的那一畝三分地記憶體了,能做到這樣就足以了。關于Android記憶體優化,這裡有一篇Google的官方指導文檔,但是本文為自己項目摸索,會有很多不一樣的地方。

3-1 Android記憶體管理原理

系統級記憶體管理:

Android系統核心是基于Linux,是以說Android的記憶體管理其實也是Linux的更新版而已。Linux在程序停止後就結束該程序,而Android把這些停止的程序都保留在記憶體中,直到系統需要更多記憶體時才選擇性的釋放一些,保留在記憶體中的程序預設(不包含背景service與Thread等單獨UI線程的程序)不會影響整體系統的性能(速度與電量等)且當再次啟動這些保留在記憶體的程序時可以明顯提高啟動速度,不需要再去加載。

再直白點就是說Android系統級記憶體管理機制其實類似于Java的垃圾回收機制,這下明白了吧;在Android系統中架構會定義如下幾類程序、在系統記憶體達到規定的不同level門檻值時觸發清空不同level的程序類型。

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

可以看見,所謂的我們的Service在背景跑着跑着挂了,或者盒子上有些大型遊戲啟動起來就挂(之前我在上家公司做盒子時遇見過),有一個直接的原因就是這個門檻值定義的太大,導緻系統一直認為已經達到門檻值,是以進行優先清除了符合類型的程序。是以說,該門檻值的設定是有一些講究的,額,扯多了,我們主要是針對應用層記憶體分析的,系統級記憶體回收了解這些就基本夠解釋我們應用在裝置上的一些表現特征了。

應用級記憶體管理:

在說應用級别記憶體管理原理時大家先想一個問題,假設有一個記憶體為1G的Android裝置,上面運作了一個非常非常吃記憶體的應用,如果沒有任何機制的情況下是不是用着用着整個裝置會因為我們這個應用把1G記憶體吃光然後整個系統運作癱瘓呢?

哈哈,其實Google的工程師才不會這麼傻的把系統設計這麼差勁。為了使系統不存在我們上面假想情況且能安全快速的運作,Android的架構使得每個應用程式都運作在單獨的程序中(這些應用程序都是由Zygote程序孵化出來的,每個應用程序都對應自己唯一的虛拟機執行個體);如果應用在運作時再存在上面假想的情況,那麼癱瘓的隻會是自己的程序,不會直接影響系統運作及其他程序運作。

既然每個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——外部記憶體配置設定失敗時觸發;

通過上面這幾點的分析可以發現,應用的記憶體管理其實就是一個蘿蔔一個坑,坑都一般大,你在開發應用時要保證的是記憶體使用同一時刻不能超過坑的大小,否則就裝不下了。

3-2 Android記憶體洩露性能分析

有了關于Android的一些記憶體認識,接着我們來看看關于Android應用開發中常出現的一種記憶體問題—-記憶體洩露。

3-2-1 Android應用記憶體洩露概念

衆所周知,在Java中有些對象的生命周期是有限的,當它們完成了特定的邏輯後将會被垃圾回收;但是,如果在對象的生命周期本來該被垃圾回收時這個對象還被别的對象所持有引用,那就會導緻記憶體洩漏;這樣的後果就是随着我們的應用被長時間使用,他所占用的記憶體越來越大。如下就是一個最常見簡單的洩露例子(其它的洩露不再一一列舉了):

public final class MainActivity extends Activity {
    private DbManager mDbManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //DbManager是一個單例模式類,這樣就持有了MainActivity引用,導緻洩露
        mDbManager = DbManager.getInstance(this);
    }
}
           

可以看見,上面例子中我們讓一個單例模式的對象持有了目前Activity的強引用,那在目前Acvitivy執行完onDestroy()後,這個Activity就無法得到垃圾回收,也就造成了記憶體洩露。

記憶體洩露可以引發很多的問題,常見的記憶體洩露導緻問題如下:

  • 應用卡頓,響應速度慢(記憶體占用高時JVM虛拟機會頻繁觸發GC);
  • 應用被從背景程序幹為空程序(上面系統記憶體原理有介紹,也就是超過了門檻值);
  • 應用莫名的崩潰(上面應用記憶體原理有介紹,也就是超過了門檻值OOM);

造成記憶體洩露洩露的最核心原理就是一個對象持有了超過自己生命周期以外的對象強引用導緻該對象無法被正常垃圾回收;可以發現,應用記憶體洩露是個相當棘手重要的問題,我們必須重視。

3-2-2 Android應用記憶體洩露察覺手段

知道了記憶體洩露的概念之後肯定就是想辦法來确認自己的項目是否存在記憶體洩露了,那該如何察覺自己項目是否存在記憶體洩露呢?如下提供了幾種常用的方式:

察覺方式 場景
AS的Memory視窗 平時用來直覺了解自己應用的全局記憶體情況,大的洩露才能有感覺。
DDMS-Heap記憶體監測工具 同上,大的洩露才能有感覺。
dumpsys meminfo指令 常用方式,可以很直覺的察覺一些洩露,但不全面且正常足夠用。
leakcanary神器 比較強大,可以感覺洩露且定位洩露;實質是MAT原理,隻是更加自動化了,當現有代碼量已經龐大成型,且無法很快察覺掌控全局代碼時極力推薦;或者是偶現洩露的情況下極力推薦。

AS的Memory視窗如下,詳細的說明這裡就不解釋了,很簡單很直覺(使用頻率高):

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

DDMS-Heap記憶體監測工具視窗如下,詳細的說明這裡就不解釋了,很簡單(使用頻率不高):

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

dumpsys meminfo指令如下(使用頻率非常高,非常高效,我的最愛之一,平時一般關注幾個重要的Object個數即可判斷一般的洩露;當然了,adb shell dumpsys meminfo不跟參數直接展示系統所有記憶體狀态):

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

leakcanary神器使用這裡先不說,下文會專題介紹,你會震撼的一B。有了這些工具的定位我們就能很友善的察覺我們App的記憶體洩露問題,察覺到以後該怎麼定位分析呢,繼續往下看。

3-2-3 Android應用記憶體洩露leakcanary工具定位分析

leakcanary是一個開源項目,一個記憶體洩露自動檢測工具,是著名的GitHub開源組織Square貢獻的,它的主要優勢就在于自動化過早的發覺記憶體洩露、配置簡單、抓取貼心,缺點在于還存在一些bug,不過正常使用百分之九十情況是OK的,其核心原理與MAT工具類似。

關于leakcanary工具的配置使用方式這裡不再詳細介紹,因為真的很簡單,詳情點我參考官方教程學習使用即可。

PS:之前在優化性能時發現我們有一個應用有兩個界面退出後Activity沒有被回收(dumpsys meminfo發現一直在加),是以就懷疑可能存在記憶體洩露。但是問題來了,這兩個Activity的邏輯十分複雜,代碼也不是我寫的,相關聯的代碼量也十分龐大,更加郁悶的是很難判斷是哪個版本修改導緻的,這時候隻知道有洩露,卻無法定位具體原因,使用MAT分析解決掉了一個可疑洩露後發現洩露又變成了機率性的。可以發現,對于這種機率性的洩露用MAT去主動抓取肯定是很耗時耗力的,是以決定直接引入leakcanary神器來檢測項目,後來很快就徹底解決了項目中所有必現的、偶現的記憶體洩露。

總之一點,工具再強大也隻是幫我們定位可能的洩露點,而最核心的GC ROOT洩露資訊推導出洩露問題及如何解決還是需要你把住代碼邏輯及洩露核心概念去推了解決。

3-2-4 Android應用記憶體洩露MAT工具定位分析

Eclipse Memory Analysis Tools(點我下載下傳)是一個專門分析Java堆資料記憶體引用的工具,我們可以使用它友善的定位記憶體洩露原因,核心任務就是找到GC ROOT位置即可,哎呀,關于這個工具的使用我是真的不想說了,自己搜尋吧,實在簡單、傳統的不行了。

PS:這是開發中使用頻率非常高的一個工具之一,麻煩務必掌握其核心使用技巧,雖然Android Studio已經實作了部分功能,但是真的很難用,遇到問題目前還是使用Eclipse Memory Analysis Tools吧。

原諒我該小節的放蕩不羁!!!!(其實我是困了,嗚嗚!)

3-2-5 Android應用開發規避記憶體洩露建議

有了上面的原理及案例處理其實還不夠,因為上面這些處理辦法是補救的措施,我們正确的做法應該是在開發過程中就養成良好的習慣和敏銳的嗅覺才對,是以下面給出一些應用開發中常見的規避記憶體洩露建議:

  • Context使用不當造成記憶體洩露;不要對一個Activity Context保持長生命周期的引用(譬如上面概念部分給出的示例)。盡量在一切可以使用應用ApplicationContext代替Context的地方進行替換(原理我前面有一篇關于Context的文章有解釋)。
  • 非靜态内部類的靜态執行個體容易造成記憶體洩漏;即一個類中如果你不能夠控制它其中内部類的生命周期(譬如Activity中的一些特殊Handler等),則盡量使用靜态類和弱引用來處理(譬如ViewRoot的實作)。
  • 警惕線程未終止造成的記憶體洩露;譬如在Activity中關聯了一個生命周期超過Activity的Thread,在退出Activity時切記結束線程。一個典型的例子就是HandlerThread的run方法是一個死循環,它不會自己結束,線程的生命周期超過了Activity生命周期,我們必須手動在Activity的銷毀方法中中調運thread.getLooper().quit();才不會洩露。
  • 對象的注冊與反注冊沒有成對出現造成的記憶體洩露;譬如注冊廣播接收器、注冊觀察者(典型的譬如資料庫的監聽)等。
  • 建立與關閉沒有成對出現造成的洩露;譬如Cursor資源必須手動關閉,WebView必須手動銷毀,流等對象必須手動關閉等。
  • 不要在執行頻率很高的方法或者循環中建立對象,可以使用HashTable等建立一組對象容器從容器中取那些對象,而不用每次new與釋放。
  • 避免代碼設計模式的錯誤造成記憶體洩露。

關于規避記憶體洩露上面我隻是列出了我在項目中經常遇見的一些情況而已,肯定不全面,歡迎拍磚!當然了,隻有我們做到好的規避加上強有力的判斷嗅覺洩露才能讓我們的應用駕馭好自己的一畝三分地。

3-3 Android記憶體溢出OOM性能分析

上面談論了Android應用開發的記憶體洩露,下面談談記憶體溢出(OOM);其實可以認為記憶體溢出與記憶體洩露是交集關系,具體如下圖:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

下面我們就來看看記憶體溢出(OOM)相關的東東吧。

3-3-1 Android應用記憶體溢出OOM概念

上面我們探讨了Android記憶體管理和應用開發中的記憶體洩露問題,可以知道記憶體洩露一般影響就是導緻應用卡頓,但是極端的影響是使應用挂掉。前面也提到過應用的記憶體配置設定是有一個門檻值的,超過門檻值就會出問題,這裡我們就來看看這個問題—–記憶體溢出(OOM–OutOfMemoryError)。

記憶體溢出的主要導緻原因有如下幾類:

  • 應用代碼存在記憶體洩露,長時間積累無法釋放導緻OOM;
  • 應用的某些邏輯操作瘋狂的消耗掉大量記憶體(譬如加載一張不經過處理的超大超高清圖檔等)導緻超過門檻值OOM;

可以發現,無論哪種類型,導緻記憶體溢出(OutOfMemoryError)的核心原因就是應用的記憶體超過門檻值了。

3-3-2 Android應用記憶體溢出OOM性能分析

通過上面的OOM概念和那幅交集圖可以發現,要想分析OOM原因和避免OOM需要分兩種情況考慮,洩露導緻的OOM,申請過大導緻的OOM。

記憶體洩露導緻的OOM分析:

這種OOM一旦發生後會在logcat中列印相關OutOfMemoryError的異常棧資訊,不過你别高興太早,這種情況下導緻的OOM列印異常資訊是沒有太大作用,因為這種OOM的導緻一般都如下圖情況(圖示為了說明問題資料和場景有誇張,請忽略):

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

從圖檔可以看見,這種OOM我們有時也遇到,第一反應是去分析OOM異常列印棧,可是後來發現列印棧列印的地方沒有啥問題,沒有可優化的餘地了,于是就郁悶了。其實這時候你留心觀察幾個現象即可,如下:

  • 留意你執行觸發OOM操作前的界面是否有卡頓或者比較密集的GC列印;
  • 使用指令檢視下目前應用占用記憶體情況;

确認了以上這些現象你基本可以斷定該OOM的log真的沒用,真正導緻問題的原因是記憶體洩露,是以我們應該按照上節介紹的方式去着手排查記憶體洩露問題,解決掉記憶體洩露後紅色空間都能得到釋放,再去顯示一張0.8M的優化圖檔就不會再報OOM異常了。

不珍惜記憶體導緻的OOM分析:

上面說了記憶體洩露導緻的OOM異常,下面我們再來看一幅圖(資料和場景描述有誇張,請忽略),如下:

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

可見,這種類型的OOM就很好定位原因了,一般都可以從OOM後的log中得出分析定位。

如下例子,我們在Activity中的ImageView放置一張未優化的特大的(30多M)高清圖檔,運作直接崩潰如下:

//抛出OOM異常
- :: -/? E/art: Throwing OutOfMemoryError "Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"
- :: -/? E/art: Throwing OutOfMemoryError "Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"
//堆棧列印
- :: -/? E/AndroidRuntime: FATAL EXCEPTION: main
- :: -/? E/AndroidRuntime: Process: com.example.application, PID: 
- :: -/? E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.application/com.example.myapplication.MainActivity}: android.view.InflateException: Binary XML file line #21: Error inflating class <unknown>
- :: -/? E/AndroidRuntime:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:)
- :: -/? E/AndroidRuntime:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:)
- :: -/? E/AndroidRuntime:     at android.app.ActivityThread.access$800(ActivityThread.java:)
- :: -/? E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:)
- :: -/? E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:)
- :: -/? E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:)
- :: -/? E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:)
- :: -/? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
- :: -/? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:)
- :: -/? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:)
- :: -/? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:)
//出錯地點,原因是行的ImageView設定的src是一張未優化的M的高清圖檔
- :: -/? E/AndroidRuntime:  Caused by: android.view.InflateException: Binary XML file line #21: Error inflating class <unknown>
- :: -/? E/AndroidRuntime:     at android.view.LayoutInflater.createView(LayoutInflater.java:)
           

通過上面的log可以很友善的看出來問題原因所在地,那接下來的做法就是優化呗,降低圖檔的相關規格即可(譬如使用BitmapFactory的Option類操作等)。

PS:提醒一句的是記得應用所屬的記憶體是區分Java堆和native堆的!

3-3-3 Android應用規避記憶體溢出OOM建議

還是那句話,等待OOM發生是為時已晚的事,我們應該将其扼殺于萌芽之中,至于如何在開發中規避OOM,如下給出一些我們應用開發中的常用的政策建議:

  • 時刻記得不要加載過大的Bitmap對象;譬如對于類似圖檔加載我們要通過BitmapFactory.Options設定圖檔的一些采樣比率和複用等,具體做法點我參考官方文檔,不過過我們一般都用fresco或Glide開源庫進行加載。
  • 優化界面互動過程中頻繁的記憶體使用;譬如在清單等操作中隻加載可見區域的Bitmap、滑動時不加載、停止滑動後再開始加載。
  • 有些地方避免使用強引用,替換為弱引用等操作。
  • 避免各種記憶體洩露的存在導緻OOM。
  • 對批量加載等操作進行緩存設計,譬如清單圖檔顯示,Adapter的convertView緩存等。
  • 盡可能的複用資源;譬如系統本身有很多字元串、顔色、圖檔、動畫、樣式以及簡單布局等資源可供我們直接使用,我們自己也要盡量複用style等資源達到節約記憶體。
  • 對于有緩存等存在的應用盡量實作onLowMemory()和onTrimMemory()方法。
  • 盡量使用線程池替代多線程操作,這樣可以節約記憶體及CPU占用率。
  • 盡量管理好自己的Service、Thread等背景的生命周期,不要浪費記憶體占用。
  • 盡可能的不要使用依賴注入,中看不中用。
  • 盡量在做一些大記憶體配置設定等可疑記憶體操作時進行try catch操作,避免不必要的應用閃退。
  • 盡量的優化自己的代碼,減少備援,進行編譯打包等優化對齊處理,避免類加載時浪費記憶體。

可以發現,上面隻是列出了我們開發中常見的導緻OOM異常的一些規避原則,還有很多相信還沒有列出來,大家可以自行追加參考即可。

3-4 Android記憶體性能優化總結

無論是什麼電子裝置的開發,記憶體問題永遠都是一個很深奧、無底洞的話題,上面的這些記憶體分析建議也單單隻是Android應用開發中一些常見的場景而已,真正的達到合理的優化還是需要很多知識和功底的。

合理的應用架構設計、設計風格選擇、開源Lib選擇、代碼邏輯規範等都會決定到應用的記憶體性能,我們必須時刻頭腦清醒的意識到這些問題潛在的風險與優劣,因為記憶體優化必須要有一個度,不能一味的優化,亦不能置之不理。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

4 Android應用API使用及代碼邏輯性能分析

在我們開發中除過正常的那些經典UI、記憶體性能問題外其實還存在很多潛在的性能優化、這種優化不是十分明顯,但是在某些場景下卻是非常有必要的,是以我們簡單列舉一些常見的其他潛在性能優化技巧,具體如下探讨。

4-1 Android應用String/StringBuilder/StringBuffer優化建議

字元串操作在Android應用開發中是十分常見的操作,也就是這個最簡單的字元串操作卻也暗藏很多潛在的性能問題,下面我們執行個體來說說。

先看下面這個關于String和StringBuffer的對比例子:

//性能差的實作
String str1 = "Name:";
String str2 = "GJRS";
String Str = str1 + str2;
//性能好的實作
String str1 = "Name:";
String str2 = "GJRS";
StringBuffer str = new StringBuilder().append(str1).append(str2);
           

通過這個例子可以看出來,String對象(記得是對象,不是常量)和StringBuffer對象的主要性能差別在于String對象是不可變的,是以每次對String對象做改變操作(譬如“+”操作)時其實都生成了新的String對象執行個體,是以會導緻記憶體消耗性能問題;而StringBuffer對象做改變操作每次都會對自己進行操作,是以不需要消耗額外的記憶體空間。

我們再看一個關于String和StringBuffer的對比例子:

//性能差的實作
StringBuffer str = new StringBuilder().append("Name:").append("GJRS");
//性能好的實作
String Str = "Name:" + "GJRS";
           

在這種情況下你會發現StringBuffer的性能反而沒有String的好,原因是在JVM解釋時認為

String Str = "Name:" + "GJRS";

就是

String Str = "Name:GJRS";

,是以自然比StringBuffer快了。

可以發現,如果我們拼接的是字元串常量則String效率比StringBuffer高,如果拼接的是字元串對象,則StringBuffer比String效率高,我們在開發中要酌情選擇。當然,除過注意StringBuffer和String的效率問題,我們還應該注意另一個問題,那就是StringBuffer和StringBuilder的差別,其實StringBuffer和StringBuilder都繼承自同一個父類,隻是StringBuffer是線程安全的,也就是說在不考慮多線程情況下StringBuilder的性能又比StringBuffer高。

PS:如果想追究清楚他們之間具體細節差異,麻煩自己檢視實作源碼即可。

4-2 Android應用OnTrimMemory()實作性能建議

OnTrimMemory是Android 4.0之後加入的一個回調方法,作用是通知應用在不同的情況下進行自身的記憶體釋放,以避免被系統直接殺掉,提高應用程式的使用者體驗(冷啟動速度是熱啟動的2~3倍)。系統會根據目前不同等級的記憶體使用情況調用這個方法,并且傳入目前記憶體等級,這個等級有很多種,我們可以依據情況實作不同的等級,這裡不詳細介紹,但是要說的是我們應用應該至少實作如下等級:

  • TRIM_MEMORY_BACKGROUND

    記憶體已經很低了,系統準備開始根據LRU緩存來清理程序。這時候如果我們手動釋放一些不重要的緩存資源,則當使用者傳回我們應用時會感覺到很順暢,而不是重新啟動應用。

可以實作OnTrimMemory方法的系統元件有Application、Activity、Fragement、

Service、ContentProvider;關于OnTrimMemory釋放哪些記憶體其實在架構階段就要考慮清楚哪些對象是要常駐記憶體的,哪些是伴随元件周期存在的,一般需要釋放的都是緩存。

如下給出一個我們項目中常用的例子:

@Override
public void onTrimMemory(int level) {
   if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
       clearCache();
   }
}
           

通常在我們代碼實作了onTrimMemory後很難複顯這種記憶體消耗場景,但是你又怕引入新Bug,想想辦法測試。好在我們有一個快捷的方式來模拟觸發該水準記憶體釋放,如下指令:

packagename為包名或者程序id,value為ComponentCallbacks2.java裡面定義的值,可以為80、60、40、20、5等,我們模拟觸發其中的等級即可。

4-3 Android應用HashMap與ArrayMap及SparseArray優化建議

在Android開發中涉及到資料邏輯部分大部分用的都是Java的API(譬如HashMap),但是對于Android裝置來說有些Java的API并不适合,可能會導緻系統性能下降,好在Google團隊已經意識到這些問題,是以他們針對Android裝置對Java的一些API進行了優化,優化最多就是使用了ArrayMap及SparseArray替代HashMap來獲得性能提升。

HashMap:

HashMap内部使用一個預設容量為16的數組來存儲資料,數組中每一個元素存放一個連結清單的頭結點,其實整個HashMap内部結構就是一個哈希表的拉鍊結構。HashMap預設實作的擴容是以2倍增加,且擷取一個節點采用了周遊法,是以相對來說無論從記憶體消耗還是節點查找上都是十分昂貴的。

SparseArray:

SparseArray比HashMap省記憶體是因為它避免了對Key進行自動裝箱(int轉Integer),它内部是用兩個數組來進行資料存儲的(一個存Key,一個存Value),它内部對資料采用了壓縮方式來表示稀疏數組資料,進而節約記憶體空間,而且其查找節點的實作采用了二分法,很明顯可以看見性能的提升。

ArrayMap:

ArrayMap内部使用兩個數組進行資料存儲,一個記錄Key的Hash值,一個記錄Value值,它和SparseArray類似,也會在查找時對Key采用二分法。

有了上面的基本了解我們可以得出結論供開發時參考,當資料量不大(千位級内)且Key為int類型時使用SparseArray替換HashMap效率高;當資料量不大(千位級内)且資料類型為Map類型時使用ArrayMap替換HashMap效率高;其他情況下HashMap效率相對高于二者。

4-4 Android應用ContentProviderOperation優化建議

ContentProvider是Android應用開發的核心元件之一,有時候在開發中需要使用ContentProvider對多行資料進行操作,我們的做法一般是多次調運相關操作方法,殊不知這種實作方式是非常低性能的,取而代之的做法應該是使用批量操作,具體為了使批量更新、插入、删除資料操作更加友善官方提供了ContentProviderOperation工具類。是以在我們開發中遇到類似情景時請務必使用批量操作,具體的優勢如下:

  • 所有的操作都在一個事務中執行,可以保證資料的完整性。
  • 批量操作在一個事務中執行,是以隻用打開、關閉一個事務。
  • 減輕應用程式與ContentProvider間的多次頻繁互動,提升性能。

可以看見,這對于資料庫操作來說是一個非常有用的優化措施,煩請務必重視(我們項目優化過,的确有很大提升)。

4-5 Android應用其他邏輯優化建議

關于API及邏輯性能優化其實有多知識點的,這裡無法一一列出,隻能給出一些重要的知識點,下面再給出一些常見的優化建議:

  • 避免在Android中使用Java的枚舉類型,因為編譯後不但占空間,加載也費時,完全沒有static final的變量好用、高效。
  • Handler發送消息時盡量使用obtain去擷取已經存在的Message對象進行複用,而不是新new Message對象,這樣可以減輕記憶體壓力。
  • 在使用背景Service時盡量将能夠替換為IntentService的地方替換為此,這樣可以減輕系統壓力、省電、省記憶體、省CPU占用率。
  • 在目前類内部盡量不要通過自己的getXXX、setXXX對自己内部成員進行操作,而是直接使用,這樣可以提高代碼執行效率。
  • 不要一味的為了設計模式而過分的抽象代碼,因為代碼抽象系數與代碼加載執行時間成正比。
  • 盡量減少鎖個數、減小鎖範圍,避免造成性能問題。
  • 合理的選擇使用for循環與增強型for循環,譬如不要在ArrayList上使用增強型for循環等。

哎呀,類似的小優化技巧有很多,這裡不一一列舉了,自行發揮留意即可。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

5 Android應用移動裝置電池耗電性能分析

有了UI性能優化、記憶體性能優化、代碼編寫優化之後我們在來說說應用開發中很重要的一個優化子產品—–電量優化。

5-1 Android應用耗電量概念

在盒子等開發時可能電量優化不是特别重視(視盒子待機真假待機模式而定),但是在移動裝置開發中耗電量是一個非常重要的名額,如果使用者一旦發現我們的應用非常耗電,不好意思,他們大多會選擇解除安裝來解決此類問題,是以耗電量是一個十分重要的問題。

關于我們應用的耗電量情況我們可以進行定長時間測試,至于具體的耗電量統計等請參考此文,同時我們還可以直接通過Battery Historian Tool來檢視詳細的應用電量消耗情況。最簡單常用辦法是通過指令直接檢視,如下:

adb shell dumpsys batterystats
           

其實我們一款應用耗電量最大的部分不是UI繪制顯示等,常見耗電量最大原因基本都是因為網絡資料互動、GPS定位、大量記憶體性能問題、備援的背景線程和Service等造成。

5-2 Android應用耗電量優化建議

優化電量使用情況我們不僅可以使用系統提供的一些API去處理,還可以在平時編寫代碼時就養成好的習慣。具體的一些建議如下:

  • 在需要網絡的應用中,執行某些操作前盡量先進行網絡狀态判斷。
  • 在網絡應用傳輸中使用高效率的資料格式和解析方法,譬如JSON等。
  • 在傳輸使用者回報或者下載下傳OTA更新包等不是十分緊急的操作時盡量采用壓縮資料進行傳輸且延遲到裝置充電和WIFI狀态時進行。
  • 在有必要的情況下盡量通過PowerManager.WakeLock和JobScheduler來控制一些邏輯操作達到省電優化。
  • 對定位要求不太高的場景盡量使用網絡定位,而不是GPS定位。
  • 對于定時任務盡量使用AlarmManager,而不是sleep或者Timer進行管理。
  • 盡可能的減少網絡請求次數和減小網絡請求時間間隔。
  • 背景任務要盡可能少的喚醒CPU,譬如IM通信的長連接配接心跳時間間隔、一些應用的背景定時喚醒時間間隔等要設計合理。
  • 特殊耗電業務情況可以進行彈窗等友好的互動設計提醒使用者該操作會耗用過多電量。

可以看見,上面隻是一些常見的電量消耗優化建議。總之,作為應用開發者的我們要意識到電量損耗對于使用者來說是非常敏感的,隻有我們做到合理的電量優化才能赢得使用者的芳心。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

6 Android應用開發性能優化總結

性能優化是一個很大的話題,上面我們談到的隻是應用開發中常見的性能問題,也是應用開發中性能問題的冰山一角,更多的性能優化技巧和能力不是靠看出來,而是靠經驗和實戰結果總結出來的,是以說性能優化是一個涉及面非常廣的話題,如果你想對你的應用進行性能你必須對你應用的整個架構有一個非常清晰的認識。

當然了,如果在我們開發中隻是一味的追求各種極緻的優化也是不對的。因為優化本來就是存在風險的,甚至有些過度的優化會直接導緻項目的臃腫,是以不要因為極緻的性能優化而破壞掉了你項目的合理架構。

總之一句話,性能優化适可而止,請酌情優化。

PS:附上Google關于Android開發的一些專題建議視訊連結,不過在天朝需要自備梯子哦。

【工匠若水 http://blog.csdn.net/yanbober 轉載請注明出處。點我開始Android技術交流】

Android應用開發性能優化完全分析1 背景2 應用UI性能問題分析3 應用開發Memory記憶體性能分析優化4 Android應用API使用及代碼邏輯性能分析5 Android應用移動裝置電池耗電性能分析6 Android應用開發性能優化總結

繼續閱讀