標籤:
轉載請標明出處(請勿轉載刪除底部微博、等資訊):
http://blog.csdn.net/Y1258429182/article/details/51176424
本文出自:楊哲丶的部落格
上一篇文章總結的布局最佳化的問題,如果對布局最佳化不是很熟悉的,可以看一下Android Studido下的應用效能最佳化總結–布局最佳化 , 這周一直籌劃總結一下記憶體最佳化的問題,因為現在對於應用最佳化的文章很多,但是還是想完善一下才想分享這篇文章的,我會從項目中遇到的一個問題,通過解決問題的過程來分享知識,希望大家有所收穫。
“A small leak will sink a great ship.” - Benjamin Franklin
小樓不修補,大船也會翻。——本傑明 富蘭克林
出現的問題(What)
- 情境(Scene): Y君某年某月某日碰到一個奇怪的問題:A頁面跳轉到B頁面,然後跳轉到其他頁面沒有問題,但是在A跳轉B,然後B跳轉到A頁面,就開始發生卡頓,程式也不崩潰!!OMG,這是什麼問題,Y君當時確實有點懵逼了,這是腫麼一個情況,但是Y君畢竟也是上過刀山下過火海,在bug堆裡走出來的人(突然想起了那句話,我要成為海賊王的男人…),扯遠了,還是回到問題上來.
分析問題(Why)
由於出現卡頓現象,大部分都出自記憶體的問題上,所以我們開始分析自己應用的記憶體使用量情況,但是問題來了,用什麼工具查看應用的記憶體使用量情況?
因為以前Ecliplse的記憶體查看略顯繁瑣,而且Goole推出的新的開發工具Android Studio,有很多我們方便我們安卓開發程式猿調試和分析問題,在這裡我就介紹一下Android Studio中的記憶體分析工具Memory,如果不熟悉的可以按照箭頭指示,點擊應用相對應的包名,就可以查看該應用記憶體情況,分析如下
- Y君處理記憶體還是有稍許經驗的,不急不緩的亮出自己珍藏已久的記憶體分析三劍客:
- LeakCanary
- Memory Analyzer Tool(MAT)
- TraceView
- 那麼我們開始分析問題出在哪裡吧 ,Are You Ready? Go!
LeakCanary的使用方法
LeakCanary的GitHub地址
工具介紹:LeakCanary是Square開源的一個記憶體泄露自動探測神器,它是一個Android和Java的記憶體泄露檢測庫,可以大幅度減少了開發中遇到的OOM問題,對於安卓開發人員來說,無疑是個福音。
GitHub裡邊介紹的已經很詳細了,我這裡也簡短的介紹一下使用方法!
在項目的build.gradle檔案添加:
dependencies { debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.4-beta2‘ releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2‘ testCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2‘ }
在Application裡邊初始化
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); }}
當正常接入的情況下,測試應用出現記憶體不足的時候會接受到通知,如下
但是Y君的這個bug出現這種分析,沒有接到通知,如下
- Leakcanary是的主要優勢就在於自動化過早的發覺記憶體泄露、配置簡單、抓取貼心,缺點在於還存在一些bug,不過正常使用百分之九十情況是OK的,然後我就成了那百分之十,具體問題我也不清楚,但是不妨礙這是一個很好的檢查工具。
Memory Analyzer Tool(MAT)的使用方法
MAT獨立版記憶體偵查工具下載
工具介紹:MAT(Memory Analyzer Tool)工具在分析 大記憶體的dump檔案時,可以非常直觀的看到各個對象在堆空間中所佔用的記憶體大小、類執行個體數量、對象參考關聯性、利用OQL物件查詢,以及可以很方便的找出對象GC Roots的相關資訊,當然最迷人的還是能夠快速為開發人員產生記憶體泄露報表,方便定位問題和分析問題。
測試Demo:我寫了一個Demo來類比當時我遇到的情況!讓計時器一直發送訊息到Handler重新整理時間,代碼如下
private void getCurrentTime() { //為了測試更加明顯 new Thread() { @Override public void run() { super.run(); while (flag) {// SystemClock.sleep(1000); long endTime = System.currentTimeMillis(); long time = endTime - mStartTime; String result = new SimpleDateFormat("mm:ss").format(new Date(time)); Message message = handler.obtainMessage(); message.what = MessageConstants.UPDATETIMEFLAG; mFinallyTime = result;//為了跳轉攜帶資料 message.obj = result; handler.sendMessage(message); } } }.start(); }
- 步驟一:我只講Android Studio下如何抓取記憶體使用量的情況,點擊左上方的箭頭提示,會抓取你相對應的位置的記憶體使用量情況,4
- 步驟二:稍等一會,產生的檔案會出現在captures中,然後選擇檔案,點擊右鍵轉換成標準的hprof檔案,就可以在MAT中開啟了。
再次我解釋一下,MAT分兩種類型,一種是用於Eclispse外掛程式版,一種是獨立版,因為我們用是Android Studio所以只能使用用獨立版的MAT.
- 步驟三:在MAT中開啟抓取到的檔案後,如下
MAT中提供了非常多的功能,這裡我們只要學習幾個最常用的就可以了。最中央的那個餅狀圖展示了最大的幾個對象所佔記憶體的比例,這張圖中提供的內容並不多,我們可以忽略它。
圖片中紅色框有使我們經常使用的模組,它們的職責分別是:
- Histogram可以列出記憶體中每個對象的名字、數量以及大小。
- Dominator Tree會將所有記憶體中的對象按大小進行排序,並且我們可以分析對象之間的引用結構。
MAT中的Dominator Tree 的分析
現在點擊Dominator Tree,如下:
這張圖包含的資訊非常多,我來帶著大家一起解析一下。首先Retained Heap表示這個對象以及它所持有的其它引用(包括直接和間接)所佔的總記憶體,因此從中看,前兩行的Retained Heap是最大的,我們分析記憶體流失時,記憶體最大的對象也是最應該去懷疑的。
另外大家應該可以注意到,在每一行的最左邊都有一個檔案型的表徵圖,這些表徵圖有的左下角帶有一個紅色的點,有的則沒有。帶有紅點的對象就表示是可以被GC Roots訪問到的,根據上面的講解,可以被GC Root訪問到的對象都是無法被回收的。那麼這就說明所有帶紅色的對象都是泄漏的對象嗎?當然不是,因為有些對象系統需要一直使用,本來就不應該被回收。我們可以注意到,當中所有帶紅點的對象最右邊都有寫一個System Class,說明這是一個由系統管理的對象,並不是由我們自己建立並導致記憶體流失的對象。
那麼中就無法看出記憶體流失的原因了嗎?確實,記憶體流失本來就不是這麼容易找出的,我們還需要進一步進行分析。當中,除了帶有System Class的行之外,最大的就是第一行的MessageQueue對象了,雖然MessageQueue對象現在不能被GC Roots訪問到,但不代表著Bitmap所持有的其它引用也不會被GC Roots訪問到。現在我們可以對著第二行點擊右鍵 -> Path to GC Roots -> exclude weak references,為什麼選擇exclude weak references呢?因為弱引用是不會阻止對象被記憶體回收行程回收的,所以我們這裡直接把它排除掉,結果如所示:
最終我們找到了罪魁禍首,開啟的很多個線程,while死迴圈沒有停止,我跳轉的時候也沒把迴圈關掉,所以有這麼多的Thread。
MAT中的Histogram 的分析
首先我們學習一下Histogram的用法,回到Overview介面,點擊Histogram,結果如所示
這裡是把當前應用程式中所有的對象的名字、數量和大小全部都列出來了,需要注意的是,這裡的對象都是只有Shallow Heap而沒有Retained Heap的,那麼Shallow Heap又是什麼意思呢?就是當前對象自己所佔記憶體的大小,不包含參考關聯性的,比如說當中,String對象的Shallow Heap最高,說明我們應用程式中用了很多String類型的資料,我們不斷的發送Handler不斷的創作String對象。可以通過右鍵 -> List objects -> with incoming references來查看具體是誰在使用這些String,分析下,發現然並卵。
那麼通過Histogram又怎麼去分析記憶體流失的原因呢?當然其實也可以用和Dominator Tree中比較相似的方式,即分析大記憶體的對象,比如中byte[]對象記憶體佔用很高,我們通過分析byte[],最終也是能找到記憶體流失所在的,但是這裡我準備使用另外一種更適合Histogram的方式。大家可以看到,Histogram中是可以顯示對象的數量的,那麼比如說我們現在懷疑MainActivity中有可能存在記憶體流失,就可以在第一行的Regex框中搜尋“MainActivity”,如下所示:
可以看到,這裡將包含“MainActivity”字樣的所有對象全部列出了出來,其中第一行就是MainActivity的執行個體。但是大家有沒有注意到,當前記憶體中是有30個MainActivity的執行個體的,這太不正常了,通過情況下一個Activity應該只有一個執行個體才對。其實這些對象就是由於我們剛才不斷地橫豎屏切換所產生的,因為橫豎屏切換一次,Activity就會經曆一個重新建立的過程,但是由於LeakClass的存在,之前的Activity又無法被系統回收,那麼就出現這種一個Activity存在多個執行個體的情況了。
接下來對著MainActivity右鍵 -> List objects -> with incoming references查看具體MainActivity執行個體,如所示
發現跟上邊的效果一樣,但是對比起來上邊的方法比較方便,如果你無法判斷是哪一個Acitivity出的問題,你就無法處理了,所以推薦用Dominator Tree分析問題。
總結: 這大概就是MAT工具最常用的一些用法了,當然這裡還要提醒大家一句,工具是死的,人是活的,MAT也沒有辦法保證一定可以將記憶體流失的原因找出來,還是需要我們對程式的代碼有足夠多的瞭解,知道有哪些對象是存活的,以及它們存活的原因,然後再結合MAT給出的資料來進行具體的分析,這樣才有可能把一些隱藏得很深的問題原因給找出來。
TraceView的使用方法
借鑒了許多關於TraceView的分析,發現都是抄來抄去,最終都是一種解決辦法,不是針對各種問題解決問題,針對我這次碰到的這個問題,我慢慢琢磨了這麼工具的使用,發現直接可以分析出具體那個方法佔用CPU的時間,個人認為從CPU角度思考記憶體最佳化,可以考慮使用這個工具來分析。
如何使用開啟DDMS,然後
先按下Start Method Profiling(箭頭所指,開始有紅點),點擊後紅點變成灰色小方塊,然後過一會可以按Stop Method Profiling,然後出現這種跟答題卡一樣的,我們只看下部分的分析
顯而易見,分析Intel Cpu Time 屬性,點擊它按照從大到小的排序後,我們清晰發現run()方法居然佔了93%的CPU運行記憶體,並且直接具體到哪一個類裡邊的方法。剩下的一些屬性估計有用,在這裡我也就不一一介紹了!如果你想詳細瞭解,點擊文章最後的參考連結即可。
解決問題(How)
通過這三種方法分析的分析結果:
1. LeakCanary反饋的資訊沒有,無法分析
2. Memory Analyzer Tool(MAT)反饋的資訊是MainAcitity中建立了很多的Thread,可以直接分析到死迴圈的毛病
3. TraceView通過CPU的使用方式非常直觀的顯示出,MainAcitivity中的run()方法佔用95%的使用,乾淨利索的分析出問題的所在
最後解決辦法:加1s的sleep時間,A跳轉到B頁面的時候,關掉迴圈!!
總結
文章也寫到了最後:
如果遇到卡頓問題的時候,推薦分析的步驟:
1. 先用Memory產看記憶體使用量情況
2. 在用LeakCanary看是否能直接分析出那一個類出的錯誤
3. 如果LeakCanary分析的步驟不夠清晰,匯出檔案用MAT具體分析
4. 如果在MAT你也沒有檢查出哪裡出的錯誤,可以嘗試TraceView來分析記憶體使用量情況,因為他可以具體到哪一個方法。
參考文章:
1. Android最佳效能實踐(二)——分析記憶體的使用方式
2. 正確使用Android效能分析工具——TraceView
3. Android效能最佳化之被忽視的Memory Leaks
問題修改:
tips: 感謝PleaseCallMeCoder的勘誤!修改了TraceView的描述!
歡迎關注我的微博:
http://weibo.com/u/5345060833
關注公眾號:YangZheShare
(歡迎關注,最新最實用的技術乾貨分享)
Android Studio下的應用效能最佳化總結-記憶體最佳化