一、關(guān)于App性能優(yōu)化
1. 性能優(yōu)化分類(lèi)
Google官方給出的性能優(yōu)化教程,主要分為以下幾類(lèi):
1)布局與UI渲染優(yōu)化
2)運(yùn)算與內(nèi)存管理優(yōu)化
3)電池電量?jī)?yōu)化
4)高效代碼相關(guān)Tips
5)多線程操作
官方鏈接:https://developer.android.com/training/best-performance.html
中文博客:https://aeli.gitbooks.io/android-training-course/content/best-performance.html
2. 渲染性能與UI卡頓
大多數(shù)用戶感知到的卡頓等性能問(wèn)題的最主要根源都是因?yàn)殇秩拘阅?,Android系統(tǒng)很有可能無(wú)法及時(shí)完成那些復(fù)雜的界面渲染操作,Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI進(jìn)行渲染,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫(huà)面所需要的60fps,為了能夠?qū)崿F(xiàn)60fps,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。

如果你的某個(gè)操作花費(fèi)時(shí)間是24ms,系統(tǒng)在得到VSYNC信號(hào)的時(shí)候就無(wú)法進(jìn)行正常渲染,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在32ms內(nèi)看到的會(huì)是同一幀畫(huà)面。

用戶容易在UI執(zhí)行動(dòng)畫(huà)或者滑動(dòng)ListView的時(shí)候感知到卡頓不流暢,是因?yàn)檫@里的操作相對(duì)復(fù)雜,容易發(fā)生丟幀的現(xiàn)象,從而感覺(jué)卡頓。有很多原因可以導(dǎo)致丟幀,也許是因?yàn)槟愕膌ayout太過(guò)復(fù)雜,無(wú)法在16ms內(nèi)完成渲染,有可能是因?yàn)槟愕腢I上有層疊太多的繪制單元,還有可能是因?yàn)閯?dòng)畫(huà)執(zhí)行的次數(shù)過(guò)多。這些都會(huì)導(dǎo)致CPU或者GPU負(fù)載過(guò)重。
二、工具篇
我們可以通過(guò)一些工具來(lái)定位問(wèn)題,比如可以使用HierarchyViewer來(lái)查找Activity中的布局是否過(guò)于復(fù)雜,也可以使用手機(jī)設(shè)置里面的開(kāi)發(fā)者選項(xiàng),打開(kāi)Show GPU Overdraw等選項(xiàng)進(jìn)行觀察。你還可以使用TraceView來(lái)觀察CPU的執(zhí)行情況,更加快捷的找到性能瓶頸。
1. HierarchyViewer
HierarchyViewer是我們優(yōu)化程序的工具之一,它是Android自帶的非常有用的工具,可以幫助我們更好地檢測(cè)和設(shè)計(jì)用戶界面,能夠以可視化的角度直觀地獲得UI布局設(shè)計(jì)結(jié)構(gòu)和各種屬性的信息,幫助我們優(yōu)化布局設(shè)計(jì)。但是獨(dú)立的HierarchyViewer已廢棄,Android Studio中建議使用Android Device Monitor。
需使用模擬器或開(kāi)發(fā)版的真機(jī),因?yàn)樾枰到y(tǒng)中的Hierarchy Viewer服務(wù)開(kāi)啟。
使用方式:
1)點(diǎn)擊Tools->Android->Android Device Monitor,進(jìn)入Android Device Monitor,切換到HierarchyViewer標(biāo)簽。

2)或者直接進(jìn)入獨(dú)立的HierarchyViewer:進(jìn)入sdk/tools路徑,找到HierarchyViewer,雙擊運(yùn)行。

3)在左側(cè)樹(shù)狀結(jié)構(gòu)中選中要查看的activity,加載完畢后會(huì)顯示當(dāng)前界面的樹(shù)狀結(jié)構(gòu)層次,從PhoneWindow.DecorView出發(fā),右側(cè)是xml內(nèi)定義的布局結(jié)構(gòu),大小可以縮放,可以拖動(dòng),右側(cè)是屬性預(yù)覽區(qū)域。

4)觀察單個(gè)view,選擇單個(gè)view后會(huì)出現(xiàn)如下所示圖形,可以看到Measure、Layout、Draw的耗時(shí),從左到右三個(gè)圓點(diǎn),分別表示對(duì)Measure、Layout、Draw耗時(shí)性能的評(píng)級(jí),按照性能從高到低,分別為綠色、黃色和紅色,浮窗上是具體的數(shù)值

通過(guò)使用HierarchyViewer,我們可以通過(guò)減少頁(yè)面層級(jí),優(yōu)化布局結(jié)構(gòu)來(lái)實(shí)現(xiàn)UI性能的優(yōu)化,針對(duì)每個(gè)單獨(dú)控件的渲染性能數(shù)據(jù),可以獨(dú)立的針對(duì)某控件做特殊優(yōu)化。
2. GPU過(guò)度繪制
過(guò)度繪制就是Overdraw,是指在一幀的時(shí)間內(nèi)(16.67ms)像素被繪制了多次,理論上一個(gè)像素每次只繪制一次是最優(yōu)的,但是由于重疊的布局導(dǎo)致一些像素會(huì)被多次繪制,而每次繪制都會(huì)對(duì)應(yīng)到CPU的一組繪圖命令和GPU的一些操作,產(chǎn)生額外開(kāi)銷(xiāo),當(dāng)這個(gè)操作耗時(shí)超過(guò)16.67ms時(shí),就會(huì)出現(xiàn)掉幀現(xiàn)象,也就是我們所說(shuō)的卡頓,所以應(yīng)盡量減少Overdraw的發(fā)生。
Android提供了測(cè)量Overdraw的選項(xiàng),在開(kāi)發(fā)者選項(xiàng)-調(diào)試GPU過(guò)度繪制(Show GPU Overdraw),打開(kāi)選項(xiàng)就可以看到當(dāng)前頁(yè)面Overdraw的狀態(tài),就可以觀察屏幕的繪制狀態(tài)。該工具會(huì)使用三種不同的顏色繪制屏幕,來(lái)指示overdraw發(fā)生在哪里以及程度如何,其中:
無(wú)顏色:沒(méi)有overdraw。像素只畫(huà)了一次。
藍(lán)色:overdraw 1倍。像素繪制了兩次。大片的藍(lán)色還是可以接受的(若整個(gè)窗口是藍(lán)色的,可以擺脫一層)。
綠色:overdraw 2倍。像素繪制了三次。中等大小的綠色區(qū)域是可以接受的但你應(yīng)該嘗試優(yōu)化、減少它們。
淺紅:overdraw 3倍。像素繪制了四次,小范圍可以接受。
暗紅:overdraw 4倍。像素繪制了五次或者更多。這是錯(cuò)誤的,要修復(fù)它們。

減少過(guò)度繪制Tips:
- 不同控件容器特點(diǎn)不同,根據(jù)不同場(chǎng)景合理選擇控件容器,例如RelativeLayout
相對(duì)于LinearLayout,在某些場(chǎng)景下可減少嵌套層次,但LinearLayout使用簡(jiǎn)單,效率也更高 - 去掉window的默認(rèn)背景,DecorView會(huì)創(chuàng)建默認(rèn)背景,而自定義布局會(huì)蓋上一層背景圖或背景色,導(dǎo)致默認(rèn)背景無(wú)用,帶來(lái)繪制性能損耗
- 去掉其他不必要的背景,減少背景重疊或覆蓋
- 多使用輕量級(jí)的ViewStub(只有設(shè)置可見(jiàn)或inflate時(shí)才會(huì)實(shí)例化內(nèi)部的布局),避免將所有View寫(xiě)上,并動(dòng)態(tài)控制GONE、VISIBLE
- 使用Merge,減少一層View層級(jí)
- 慎用Alpha,對(duì)一個(gè)View做Alpha轉(zhuǎn)化,需要先將View繪制出來(lái),再做Alpha轉(zhuǎn)化,最后將轉(zhuǎn)換后效果繪制在界面上,也就是需要對(duì)當(dāng)前View繪制兩遍
3. Allocation Tracker
Android系統(tǒng)會(huì)依據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類(lèi)型分別執(zhí)行不同的GC操作,常見(jiàn)的導(dǎo)致GC頻繁執(zhí)行的原因主要可能是因?yàn)槎虝r(shí)間內(nèi)有大量頻繁的對(duì)象創(chuàng)建與釋放操作,也就是俗稱(chēng)的內(nèi)存抖動(dòng)現(xiàn)象,或者短時(shí)間內(nèi)已經(jīng)存在大量?jī)?nèi)存占用介于閾值邊緣,每當(dāng)有新對(duì)象創(chuàng)建時(shí)都會(huì)導(dǎo)致超越閾值觸發(fā)GC操作。
Allocation Tracker,內(nèi)存分配跟蹤器,可以記錄代碼運(yùn)行過(guò)程中的內(nèi)存分配情況,包括已分配對(duì)象的調(diào)用棧、大小、代碼位置等,可以幫助我們發(fā)現(xiàn)在相同的調(diào)用棧中,短時(shí)間迅速分配與回收的大量相似對(duì)象,也可以幫助我們發(fā)現(xiàn)代碼中可能對(duì)內(nèi)存性能產(chǎn)生不良影響的地方。
1)打開(kāi)Android Studio中的Android Monitor控制器,進(jìn)入Memory內(nèi)存監(jiān)控窗口,點(diǎn)擊如下所示的Start Allocation Tracking按鈕,開(kāi)始跟蹤

2)操作手機(jī)相關(guān)待檢測(cè)頁(yè)面,執(zhí)行相應(yīng)路徑代碼,此時(shí)Allocation Tracker會(huì)在后臺(tái)不斷記錄,注意操作時(shí)間不要過(guò)長(zhǎng),否則會(huì)產(chǎn)生過(guò)度的記錄,不便于發(fā)現(xiàn)問(wèn)題。然后再點(diǎn)擊一次Start Allocation Tracking按鈕,停止跟蹤

3)最終會(huì)形成上圖所示的陰影區(qū)域,表示內(nèi)存跟蹤的時(shí)間段,然后會(huì)生存以.alloc命名的文件,默認(rèn)會(huì)以線程來(lái)分組

4)可選擇Group by Method或Group by Allocator,每組內(nèi)的數(shù)據(jù)默認(rèn)按照對(duì)象的分配順序排列,可點(diǎn)擊每條展開(kāi),并可最終跟蹤到最底層調(diào)用,Count表示分配的內(nèi)存的次數(shù),Size表示分配內(nèi)存的大小

5)若選擇Group by Allocator,則按照包名來(lái)分組,組內(nèi)排序和使用與上述相同

6)標(biāo)題欄有個(gè)統(tǒng)計(jì)按鈕,點(diǎn)擊后,可以生存炫酷的內(nèi)存分配輪胎圖或柱狀圖,默認(rèn)是輪胎圖,輪胎圖是以圓心為起點(diǎn),最外層是其內(nèi)存實(shí)際分配的對(duì)象,每一個(gè)同心圓可能被分割成多個(gè)部分,代表了其不同的子孫,每一個(gè)同心圓代表他的一個(gè)后代,每個(gè)分割的部分代表了某一帶人有多人,你雙擊某個(gè)同心圓中某個(gè)分割的部分,會(huì)變成以你點(diǎn)擊的那一代為圓心再向外展開(kāi)。如果想回到原始狀態(tài),雙擊圓心就可以了

4. TraceView
TraceView 是 Android 平臺(tái)特有的數(shù)據(jù)采集和分析工具,它主要用于分析 Android 中應(yīng)用程序的 hotspot。TraceView 本身只是一個(gè)數(shù)據(jù)分析工具,而數(shù)據(jù)的采集則需要使用 Android SDK 中的 Debug 類(lèi)或者利用Android Device Monitor工具:
- 在一些關(guān)鍵代碼段開(kāi)始前調(diào)用 Android SDK 中 Debug 類(lèi)的 startMethodTracing 函數(shù),并在關(guān)鍵代碼段結(jié)束前調(diào)用 stopMethodTracing 函數(shù)。這兩個(gè)函數(shù)運(yùn)行過(guò)程中將采集運(yùn)行時(shí)間內(nèi)該應(yīng)用所有線程(注意,只能是 Java 線程)的函數(shù)執(zhí)行情況,并將采集數(shù)據(jù)保存到 /mnt/sdcard/ 下的一個(gè)文件中,然后利用 SDK 中的TraceView工具來(lái)分析這些數(shù)據(jù)
- 借助Android Device Monitor工具,采集系統(tǒng)中某個(gè)正在運(yùn)行的進(jìn)程的函數(shù)調(diào)用信息。對(duì)開(kāi)發(fā)者而言,此方法適用于沒(méi)有目標(biāo)應(yīng)用源代碼的情況
1)打開(kāi)Android Studio,進(jìn)入Tools->Android->Android Device Monitor,選擇相關(guān)進(jìn)程,并點(diǎn)擊Start Method Tracing按鈕,開(kāi)始追蹤

2)操作應(yīng)用待檢測(cè)部分,不要時(shí)間太久,然后再次點(diǎn)擊Start Method Tracing按鈕,停止追蹤,便可自動(dòng)生成.trace文件

TraceViewUI 劃分為上下兩個(gè)面板,Timeline Panel(時(shí)間線面板)和 Profile Panel(分析面板),Timeline Panel 又可細(xì)分為左右兩個(gè) Panel:
左邊 Panel 顯示的是測(cè)試數(shù)據(jù)中所采集的線程信息
右邊 Pane 所示為時(shí)間線,時(shí)間線上是每個(gè)線程測(cè)試時(shí)間段內(nèi)所涉及的函數(shù)調(diào)用信息。這些信息包括函數(shù)名、函數(shù)執(zhí)行時(shí)間等
下半部分的Profile Panel 是 TraceView 的核心界面,主要展示了某個(gè)線程(先在 Timeline Panel 中選擇線程)中各個(gè)函數(shù)調(diào)用的情況,包括 CPU 使用時(shí)間、調(diào)用次數(shù)等信息。而這些信息正是查找 hotspot 的關(guān)鍵依據(jù)。下表是Profile Panel 中比較重要的列名及其描述:

可點(diǎn)擊上述任一排序項(xiàng)進(jìn)行排序
3)雙擊任一條記錄,可展開(kāi)詳細(xì)信息,包括調(diào)用者(Parents)和子函數(shù)(Children),可詳細(xì)的對(duì)調(diào)用次數(shù)過(guò)多的函數(shù)和每次執(zhí)行時(shí)間過(guò)長(zhǎng)的函數(shù)做排查,便于發(fā)現(xiàn)HotSpot
二、布局優(yōu)化
1. 抽象布局標(biāo)簽
1)<include>標(biāo)簽
include標(biāo)簽常用于將布局中的公共部分提取出來(lái)供其他layout共用,以實(shí)現(xiàn)布局模塊化,這在布局編寫(xiě)方便提供了大大的便利。
2)<viewstub>標(biāo)簽
viewstub標(biāo)簽同include標(biāo)簽一樣可以用來(lái)引入一個(gè)外部布局,但是,viewstub引入的布局默認(rèn)不會(huì)擴(kuò)張,即既不會(huì)占用顯示也不會(huì)占用位置,從而在解析layout時(shí)節(jié)省cpu和內(nèi)存。viewstub常用來(lái)引入那些默認(rèn)不會(huì)顯示,只在特殊情況下顯示的布局,如進(jìn)度布局、網(wǎng)絡(luò)失敗顯示刷新布局、信息出錯(cuò)出現(xiàn)提示布局等。
3)<merge>標(biāo)簽
在使用了include后可能導(dǎo)致布局嵌套過(guò)多,多余不必要的layout節(jié)點(diǎn),從而導(dǎo)致解析變慢,merge標(biāo)簽可用于兩種典型情況:
- 布局頂結(jié)點(diǎn)是FrameLayout且不需要設(shè)置background或padding等屬性,可以用merge代替,因?yàn)锳ctivity內(nèi)容試圖的parent view就是個(gè)FrameLayout,所以可以用merge消除只剩一個(gè)
- 某布局作為子布局被其他布局include時(shí),使用merge當(dāng)作該布局的頂節(jié)點(diǎn),這樣在被引入時(shí)頂結(jié)點(diǎn)會(huì)自動(dòng)被忽略,而將其子節(jié)點(diǎn)全部合并到主布局中
2. 去除不必要的嵌套和View節(jié)點(diǎn)
1)首次不需要使用的節(jié)點(diǎn)設(shè)置為GONE或使用viewstub
2)使用RelativeLayout代替LinearLayout
3. 減少不必要的infalte
1)對(duì)于inflate的布局可以直接緩存,用全部變量代替局部變量,避免下次需再次inflate
三、代碼優(yōu)化
1. 降低執(zhí)行時(shí)間
包括:緩存、數(shù)據(jù)存儲(chǔ)優(yōu)化、算法優(yōu)化、JNI、邏輯優(yōu)化等幾種優(yōu)化方式 。
1)緩存
緩存主要包括對(duì)象緩存、IO緩存、網(wǎng)絡(luò)緩存、DB緩存,對(duì)象緩存能減少內(nèi)存的分配,IO緩存減少磁盤(pán)的讀寫(xiě)次數(shù),網(wǎng)絡(luò)緩存減少網(wǎng)絡(luò)傳輸,DB緩存較少Database的訪問(wèn)次數(shù)。
在內(nèi)存、文件、數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)的讀寫(xiě)速度中,內(nèi)存都是最優(yōu)的,且速度數(shù)量級(jí)差別,所以盡量將需要頻繁訪問(wèn)或訪問(wèn)一次消耗較大的數(shù)據(jù)存儲(chǔ)在緩存中。
Android中常使用緩存:
- 線程池,對(duì)線程的緩存
- Android圖片緩存
- 消息緩存,通過(guò)handler.obtainMessage復(fù)用之前的message,如下:handler.sendMessage(handler.obtainMessage(0, object));
- ListView緩存
- 網(wǎng)絡(luò)緩存,數(shù)據(jù)庫(kù)緩存http response,根據(jù)http頭信息中的Cache-Control域確定緩存過(guò)期時(shí)間
- 文件IO緩存,使用具有緩存策略的輸入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.對(duì)文件、網(wǎng)絡(luò)IO皆適用
- layout緩存
- 其他需要頻繁訪問(wèn)或訪問(wèn)一次消耗較大的數(shù)據(jù)緩存
2)數(shù)據(jù)存儲(chǔ)優(yōu)化
包括數(shù)據(jù)類(lèi)型、數(shù)據(jù)結(jié)構(gòu)的選擇:
- 數(shù)據(jù)類(lèi)型選擇
字符串拼接用StringBuilder代替String,在非并發(fā)情況下用StringBuilder代替StringBuffer。
64位類(lèi)型如long double的處理比32位如int慢。
使用SoftReference、WeakReference相對(duì)正常的強(qiáng)應(yīng)用來(lái)說(shuō)更有利于系統(tǒng)垃圾回收。
final類(lèi)型存儲(chǔ)在常量區(qū)中讀取效率更高。
枚舉值相比常量會(huì)占用更大的內(nèi)存空間和運(yùn)行開(kāi)銷(xiāo),適用于強(qiáng)類(lèi)型安全的場(chǎng)景,對(duì)于普通常量的匯總,就不一定需要。
盡量使用原始數(shù)據(jù)類(lèi)型,避免頻繁的自動(dòng)裝箱。
避免使用非靜態(tài)內(nèi)部類(lèi),當(dāng)創(chuàng)建并實(shí)例化了一個(gè)非靜態(tài)內(nèi)部類(lèi),內(nèi)部就包含一個(gè)指向外部類(lèi)型的隱含引用,如果這個(gè)內(nèi)部類(lèi)實(shí)例比外部類(lèi)型存活的時(shí)間還長(zhǎng),那即使不需要這個(gè)外部類(lèi)型,它還是會(huì)被保存在內(nèi)存中。
LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高。
如果對(duì)象中某個(gè)方法的調(diào)用不依賴(lài)于該對(duì)象的實(shí)例化,則建議將該方法定義成static,可提高訪問(wèn)與調(diào)用效率。
- 數(shù)據(jù)結(jié)構(gòu)選擇
常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)選擇如:
ArrayList和LinkedList的選擇,ArrayList隨機(jī)訪問(wèn)更快,LinkedList更占內(nèi)存、隨機(jī)插入刪除更快速、擴(kuò)容效率更高。
HashMap、LinkedHashMap、HashSet的選擇,HashMap為鍵值對(duì)數(shù)據(jù)結(jié)構(gòu),LinkedHashMap可以記住加入次序的hashMap,HashSet不允許重復(fù)元素。
HashMap、WeakHashMap選擇,WeakHashMap中元素可在適當(dāng)時(shí)候被系統(tǒng)垃圾回收器自動(dòng)回收,所以適合在內(nèi)存緊張型中使用。
Collections.synchronizedMap和ConcurrentHashMap的選擇,ConcurrentHashMap為細(xì)分鎖,鎖粒度更小,并發(fā)性能更優(yōu)。Collections.synchronizedMap為對(duì)象鎖,自己添加函數(shù)進(jìn)行鎖控制更方便。
Android也提供了一些性能更優(yōu)的數(shù)據(jù)類(lèi)型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。
Sparse系列的數(shù)據(jù)結(jié)構(gòu)是為key為int情況的特殊處理,采用二分查找及簡(jiǎn)單的數(shù)組存儲(chǔ),加上不需要泛型轉(zhuǎn)換的開(kāi)銷(xiāo),相對(duì)Map來(lái)說(shuō)性能更優(yōu)。
當(dāng)需要存儲(chǔ)一對(duì)元數(shù)據(jù)數(shù)組,例如(Foo,Bar),采用兩個(gè)平行的二維數(shù)組Foo[ ]和Bar[ ],要比直接存儲(chǔ)對(duì)象(Foo,Bar)數(shù)組的效率高。
使用增強(qiáng)的for-each循環(huán)對(duì)實(shí)現(xiàn)了iterable接口的collections以及數(shù)組做遍歷,for (Foo a : mArray)和for (int i = 0; i < len; ++i)效率最高,避免使用for (int i = 0; i < mArray.length; ++i)
3)算法優(yōu)化
這個(gè)主題比較大,需要具體問(wèn)題具體分析,盡量不用O(n*n)時(shí)間復(fù)雜度以上的算法,必要時(shí)候可用空間換時(shí)間。查詢(xún)考慮hash和二分,盡量不用遞歸。
4)JNI
Android應(yīng)用程序大都通過(guò)Java開(kāi)發(fā),需要編譯器將Java字節(jié)碼轉(zhuǎn)換成本地代碼運(yùn)行,而本地代碼可以直接由設(shè)備管理器直接執(zhí)行,節(jié)省了中間步驟,所以執(zhí)行速度更快。不過(guò)需要注意從Java空間切換到本地空間需要開(kāi)銷(xiāo),同時(shí)編譯器也能生成優(yōu)化的本地代碼,所以糟糕的本地代碼不一定性能更優(yōu)。
5)邏輯優(yōu)化
主要是理清程序業(yè)務(wù)邏輯,減少不必要的操作和分支判斷等。
2. 異步,利用多線程提高TPS
充分利用多核CPU優(yōu)勢(shì),利用線程解決密集型計(jì)算、IO、網(wǎng)絡(luò)等操作。
3. 提前或延遲操作,錯(cuò)開(kāi)時(shí)間段提高TPS
1)延遲操作
不在Activity、Service、BroadcastReceiver的生命周期等對(duì)響應(yīng)時(shí)間敏感函數(shù)中執(zhí)行耗時(shí)操作,可適當(dāng)delay,Java中延遲操作可使用ScheduledExecutorService, Android中除了支持ScheduledExecutorService之外,還有一些delay操作,如handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定時(shí)等。
2)提前操作
對(duì)于第一次調(diào)用較耗時(shí)操作,可統(tǒng)一放到初始化中,將耗時(shí)提前。如得到壁紙wallpaperManager.getDrawable();
四、關(guān)于內(nèi)存泄漏
1. 使用Android Studio Java Heap Dump檢測(cè)
1)打開(kāi)Android Studio,進(jìn)入Android Device Monitor,切換到Memory監(jiān)控窗口
2)選擇待調(diào)試進(jìn)程com.baidu.ls.waimai,并操作App中可能發(fā)生內(nèi)存泄漏的地方,點(diǎn)擊Initiate GC按鈕進(jìn)行強(qiáng)制GC,然后點(diǎn)擊Dump Java Heap按鈕,導(dǎo)出.hprof格式文件

3)打開(kāi).hprof文件,可選擇Package Tree View,選擇應(yīng)用包內(nèi)想要查看的對(duì)象類(lèi)型,可在右側(cè)Instance窗口查看該類(lèi)型已存在的實(shí)例化對(duì)象數(shù)量,在下面Reference Tree窗口查看該對(duì)象引用路徑

4)上面窗口有個(gè)Total Count篩選項(xiàng),可以看到該對(duì)象目前在內(nèi)存中存在的數(shù)量,若某個(gè)對(duì)象數(shù)量存在異常,比如此時(shí)App所有的點(diǎn)菜頁(yè)已關(guān)閉,但ShopMenuFragment數(shù)量不為0,則該對(duì)象很可能發(fā)生內(nèi)存泄漏,需要根據(jù)reference tree做相關(guān)排查,并找到最終的GC Root
2. 使用LeakCanary檢測(cè)
LeakCanary是一個(gè)用于檢測(cè)內(nèi)存泄露的開(kāi)源類(lèi)庫(kù),提供了一種便捷、自動(dòng)的檢測(cè)方式,并產(chǎn)出可視化報(bào)告,以很直白的方式將內(nèi)存泄露鏈條展示給我們。
1)接入,在build.gradle中根據(jù)不同的編譯方式,選擇加入不同的引用方式,
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}
2)LeakCanary.install(context) 會(huì)返回一個(gè)預(yù)定義的 RefWatcher,同時(shí)也會(huì)啟用一個(gè) ActivityRefWatcher,用于自動(dòng)監(jiān)控調(diào)用 Activity.onDestroy() 之后泄露的 activity。
在Application中進(jìn)行配置:
public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}
private RefWatcher refWatcher;
@Override public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
}
3)使用RefWatcher監(jiān)控Fragment
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
4)產(chǎn)出可視化報(bào)告

3. 常見(jiàn)避免內(nèi)存泄露方式
1)對(duì)Activity等組件的引用應(yīng)該控制在Activity的生命周期之內(nèi),如果不能就考慮使用getApplicationContext或者getApplication,以避免Activity被外部長(zhǎng)生命周期的對(duì)象引用而泄露
2)盡量不要在靜態(tài)變量或者靜態(tài)內(nèi)部類(lèi)中使用非靜態(tài)外部成員變量(包括
context ),即使要使用,也要考慮適時(shí)把外部成員變量置空;也可以在內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的變量
3)對(duì)于生命周期比Activity長(zhǎng)的內(nèi)部類(lèi)對(duì)象,并且內(nèi)部類(lèi)中使用了外部類(lèi)的成員變量,可以這樣做避免內(nèi)存泄漏:將內(nèi)部類(lèi)改為靜態(tài)內(nèi)部類(lèi)、靜態(tài)內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的成員變量
4)Handler持有的引用對(duì)象最好使用弱引用,資源釋放時(shí)也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的時(shí)候,取消掉該 Handler 對(duì)象的 Message和 Runnable
5)在 Java 的實(shí)現(xiàn)過(guò)程中,也要考慮其對(duì)象釋放,最好的方法是在不使用某對(duì)象時(shí),顯式地將此對(duì)象賦值為 null,比如使用完Bitmap 后先調(diào)用 recycle(),再賦為null,清空對(duì)圖片等資源有直接引用或者間接引用的數(shù)組(使用 array.clear() ; array = null)等,最好遵循誰(shuí)創(chuàng)建誰(shuí)釋放的原則
6)正確關(guān)閉資源,對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,游標(biāo) Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo)
7)保持對(duì)對(duì)象生命周期的敏感,特別注意單例、靜態(tài)對(duì)象、全局性集合等的生命周期
六、參考鏈接
- Android官方性能優(yōu)化:
https://developer.android.com/training/best-performance.html - Android官方性能典范教程:
http://hukai.me/android-performance-patterns/ - 性能優(yōu)化博客合輯:
https://github.com/Juude/awesome-android-performance - Trinea性能優(yōu)化合輯:
http://www.trinea.cn/android/performance/ - Android性能優(yōu)化多途徑實(shí)現(xiàn):
http://blog.csdn.net/yanbober/article/details/48394201 - Android Studio官方profile工具使用:
https://developer.android.com/studio/profile/index.html - Android內(nèi)存泄露總結(jié):
https://yq.aliyun.com/articles/3009 - Android TraceView使用:
http://www.cnblogs.com/sunzn/p/3192231.html - LeakCanary使用:
http://m.itdecent.cn/p/7db231163168