本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發(fā)布
前言
先通俗理解下內存泄漏女责,內存溢出耳鸯,OOM湿蛔,GC回收這幾個概念。
把app的堆內存空間想成了一個杯子县爬,內存就是里面的水阳啥。
當你的app啟動后,系統(tǒng)會分配給app一個堆空間财喳,起始不會很大比如是32M(根據(jù)你的app啟動時的內存申請為準)
- 隨著程序的運行對象的創(chuàng)建越來越多察迟,系統(tǒng)不斷加內存分配:32M -> 64M -> ...
- 而GC回收則會定時掃描內存斩狱,發(fā)現(xiàn)不被引用的對象即可回收。
正常來說你的app堆內存會有升有降扎瓶。
此時如果有某個Activity持有某個引用所踊,在onDestroy時還不把這個引用設為null,那么返回進入退出這個界面概荷,Activity就會創(chuàng)建很多次從而存在多個實例秕岛,導致堆內存直升不降!這就叫做內存泄漏误证。
當用戶重復這個操作或者有多個不同Activity內存泄漏時继薛,app運行一段時間堆內存超過系統(tǒng)規(guī)定的最大值 heapSize,杯子滿了就會發(fā)現(xiàn)內存溢出(OOM)雷厂,app崩潰惋增。
關鍵點
通過上面這個例子,我們知道查找內存泄漏有如下幾點關鍵點:
- 如何知道你的app上限值heapSize是多少
- 什么情況導致無法GC
- 怎么復現(xiàn)是哪個界面內存泄漏
下面通過一個實例來演示改鲫,如何借助AndroidStudio查找內存泄漏:
內存泄漏實例
當你的app在使用中莫名崩潰,如果是OOM那么會有如下日志:
接下來我們用下面的代碼獲取heapsize:
ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getMemoryClass();
int maxHeapSize = manager.getLargeMemoryClass(); // manafest.xml android:largeHeap="true"
- heapSize是設備分配給app的最大堆內存
-
maxHeapSize 是當配置了android:largeHeap="true" 才有的最大堆內存林束,一般是heapSize的2-3倍
以HTC ONE為例像棘,兩個值分別是: 192M和512M。這里我只關注192M壶冒,maxHeapSize放到后面再說缕题。然后嘗試復現(xiàn)問題,手機連接AndroidStudio打開monitor胖腾,反復進入/退出懷疑內存泄漏的界面:
如果發(fā)現(xiàn)內存一直上升烟零,并且到接近192M的時候不動了,此時已經OOM只不過不一定會崩潰(oom異诚套鳎可以try catch)
-
接著锨阿,用AndroidStudio自帶的內存分析工具分析,點擊Dump Java Heap:
(備注:ViewDefectPhotoActivity是我的app里面一個界面记罚,用ViewPager展示墅诡,ImageLoader加載很多照片)
會在代碼區(qū) 生成當前時間點的 .hprof格式文件,里面當前的每個對象 內存占用情況桐智,我們現(xiàn)在懷疑ViewDefectPhotoActivity 可能內存泄漏 看一下:ViewDefectPhotoActivity 確實占用了很高的內存 而且有8個實例化對象,足以證明它有內存泄漏末早,隨便點擊其中一個,發(fā)現(xiàn)都和EditDefectActivity的內部類 ShowEditDefectHelper有關说庭。而且ShowEditDefectHelpe前面帶有 黃藍三角樹 這個圖標表示然磷,他可以被GC訪問到,也就是無法回收:
接下來看看ShowEditDefectHelper這個對象持有什么引用刊驴,右鍵點擊 Go To Instance:
發(fā)現(xiàn)這個內部類也被實例化多次姿搜,隨便點擊一個,發(fā)現(xiàn)屬于EventBus的引用 ,并且EventBus帶有黃藍圖標痪欲,那么問題就大概找到了悦穿,因為EventBus一直持有這個內部類的引用,導致這個內部類無法被回收业踢,而這個內部類持有ViewDefectPhotoActivity引用栗柒,導致ViewDefectPhotoActivity無法被回收 這個界面有很多圖片資源 當實例化7,8次之后 出現(xiàn)了OOM。
知举!
EventBus是用于事件通知的開源庫瞬沦,應該很多人都了解。它需要在某個類里面綁定和解綁定雇锡,我這里是在ShowEditDefectHelper注冊的逛钻,看下Activity的內部類ShowEditDefectHelper:
解釋下這里的邏輯:進入這個activity我會顯示一個進度條,請求網絡數(shù)據(jù)锰提,用戶可以隨時取消進度條曙痘。然后onCancel里面對EventBus解綁定:
但是這里有個低級錯誤:EventBus.getDefult().unregister(this)傳入的this是onCancelled()的匿名內部類而不是ShowEditDefectHelper.class導致EventBus無法解綁!
!
所以應該這么解綁:
EventBus.getDefult().unregister(ShowEditDefectHelper.Class);
立肘!
通過這個示例边坤,我們回答了上面前兩個關鍵點:
- 如何知道你的app上限值heapSize是多少
-
什么情況導致無法GC
第三個關鍵點:3. 怎么復現(xiàn)是哪個界面內存泄漏谅年。內存泄漏不是一眼就能看出來了茧痒,需要測試人員配合。當然還有一個辦法就是facebook的開源庫:leakcanary融蹂,具體使用我不多介紹了旺订。它是一個apk安裝在手機上可以直接列出內存泄漏的Activity,但是有一定誤報幾率:
我更喜歡leakcanary + AndroidStudio的方式超燃,精確無誤地找出問題区拳。
怎么避免內存
一句話歸納:(生命周期比Activity長的類不要去強引用Activity)
內部類請使用static,因為非靜態(tài)內部類默認持有外部類的引用淋纲,比如在Activity里面直接放一個自定義的Adapter
靜態(tài)類(比如Application劳闹,單例類,其他static類)請不要持有Activity引用洽瞬,因為靜態(tài)類生命周期比Activity長本涕。解決辦法:在需要的地方用BaseApplication.getTopActivity』锴裕或者Activity作為弱引用傳入
注意Handler會默認持有當前Activity菩颖,用的時候最好不要直接new Handler().post(new Runnable...),除非你確定這個runnable會在Activity銷毀前執(zhí)行完
OK,假設你的項目比較緊急为障,想暫時規(guī)避內存泄漏問題怎么辦晦闰?可以在manifest.xml加入本文開頭給的那個設置:
android:largeHeap="true"
此時heapsize會增大2-3倍放祟,緩解OOM的發(fā)生,但是技術債終究要還的呻右。希望大家認真揣摩本文用AndroidStudio分析泄漏的方法跪妥,而不是過分依賴leakcanary這個開源庫。