一、如何發現內存泄露了
1.打開android studio,運行APP,android studio底部欄選擇 “Android Monitor”的“Monitors”視圖
2.在Monitors界面的上部分,左邊下拉框選擇運行APP的手機或模擬器,右邊下拉框選擇要調試的APP進程。
3.在Monitors界面的中間部分重點關注“Memory”這一塊的內存值的變化。
? 當打開一個Activity后,已分配內存“Allocated”值會變大,再退出,按一下gc按鈕
此時Activity正常情況下應該會被回收,已分配內存值“Allocated”應該會恢復成打開之前的值。
4.生成hprof文件進行驗證與分析
點擊“Dump java Heap”生成hprof文件
大概5秒后,hprof文件會被自動生成,并自動顯示在代碼瀏覽區域
此視圖會顯示對象的類型與實例個數,我們可以按包名進行分類,這樣更方便查找自己定義的類
二、通過hprof文件分析內存泄露
用Package Tree View分類,能很快找到我們需要分析的Activity
(本APP的 launcher Activity點擊進去,第二級的Activity名為MainActivity,當按返回按鈕后,MainActivity正常情況下要被回收,我們正是分析MainActivity為什么發生內存泄露)
我在launcher Activity里點擊進MainActivity 4次并返回,通過上圖可以發現,回到launcher Activity后,MainActivty每次創建的Activity實例在返回后并沒有被回收,如果這樣重復操作很多次,程序肯定會因內存不足而崩潰。
點擊上圖的右上“Instance”區域里的某一個實例,在下方“Reference Tree”區域里會列出所有持有該實例的引用對象。
只靠人工分析hprof文件是否能找出內存泄露點?
??????? 本APP的MainActivity非常復雜,所有子界面都采用的是Fragment來進行切換,持有MainActivity引用的其它對象眾多,所以只靠人工這樣去看,很難發現問題所在。有人建議使用MAT工具打開hprof文件進行分析,本篇有更簡單的方法,即使用LeakCanary工具。
三、使用LeakCanary工具查找內存泄漏
1.進入“https://github.com/square/leakcanary”查看LeakCanary的最新版本與使用方法
最新版本是1.5.1,使用方法很簡單,只有兩步。
2.集成LeakCanary到APP中
第一步:build.gradle里添加依賴
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
第二步:在自定義Application中初始化
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
OK。
3.重新RUN APP,在模擬器中進行測試
?? 我進入MainActivity4次,做一些操作,最后每次都返回到launcher Activity中,不一會兒,模擬器標題欄收到4個通知圖標
下拉點擊某一個
4.進入details界面
上圖中MainActivity對象生成后,從下到上一直追述到 android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper.mMainLooper,
同時我們可以查看logcat
顯示android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper.mMainLooper為GC ROOT,正是因為MainActivtiy被GC ROOT所持有,所以它不能被收回,發生內存泄露。
5.分析leaks details界面
?????? 里面引用MainActivity實例的都是系統對象,而且引用鏈條顯示是直接的引用,換句話就是說,如果在MainActivity里有一個Fragment,Fragment里面的ImageView引用了MainActivity,那么此leaks details界面的引用鏈不會把Fragment實例顯示出來,只會顯示ImageView實例。
?????? 這樣又增加了分析的難度,但是仔細分析,我們發現中間有一個
?????? references android.support.v7.widget.AppCompatImageView.mContext
?說明是某個ImageView持有MainActivity引用沒有被釋放,而又有一條
????? references android.animation.ValueAnimator$AnimationHandler.mAnimations
說明很可能是ImageView執行了屬性動畫,導致了內存泄露。
???? 分析到此處,我們大概鎖定了內存泄露的點,由于代碼很多,去一個一個找有點麻煩,那么就在項目里用關鍵字“ValueAnimator”或“ObjectAnimator”進行全局搜索??
??? 終于在“CustomProgressDialog.java”查找到了動畫的使用,ivIcon剛好是一個ImageView實例
private void startPropertyAnim() {
// 第二個參數"rotation"表明要執行旋轉
// 0f -> 360f,從旋轉360度,也可以是負值,負值即為逆時針旋轉,正值是順時針旋轉。
ObjectAnimator anim = ObjectAnimator.ofFloat(ivIcon, "rotation", 0f, 360f);
anim.setRepeatCount(INFINITE);
// 動畫的持續時間,執行多久?
anim.setDuration(5000);
anim.setInterpolator(new LinearInterpolator());
// 正式開始啟動執行動畫
anim.start();
}
6.分析定位出的代碼,修正
CustomProgressDialog是一個自定義對話框,對話框顯示時,ImageView會執行旋轉動畫,但是對話框消失時,動畫并沒有被取消,導致了內存泄露。最后進行修正
private void startPropertyAnim() {
// 第二個參數"rotation"表明要執行旋轉
// 0f -> 360f,從旋轉360度,也可以是負值,負值即為逆時針旋轉,正值是順時針旋轉。
if (anim != null){
anim.cancel();
}
anim = ObjectAnimator.ofFloat(ivIcon, "rotation", 0f, 360f);
anim.setRepeatCount(INFINITE);
// 動畫的持續時間,執行多久?
anim.setDuration(5000);
anim.setInterpolator(new LinearInterpolator());
// 正式開始啟動執行動畫
anim.start();
}
@Override
public void dismiss() {
super.dismiss();
if (anim != null){
anim.cancel();
}
}
最后重新運行修改的程序,測試,發現內存泄露成功解決。
總結:
?????? ?當項目比較小,代碼量不多時,可能人工檢查一下,就能解決內存泄露的問題,但是當項目越來越龐大,代碼量非常大時,就需要利用工具來幫助進行檢查。就像上面這個問題,在沒有利用工具的情況下,本人花了大量時間看代碼都沒有檢查出來,而利用工具,很好的進行了定位,查找起來方向性非常明確,最后順利解決隱藏很深的一個內存泄露點。