Android 檢測記憶體洩露

Bakumon發表於2017-03-17

Android 檢測記憶體洩漏,必須使用方便強大到滅絕人性的 leakcanary

leakcanary 是 square 公司開發的,square 擁有眾多強大的 Android 開源專案,如,OkHttp、retrofit、otto、picasso,簡直撐起了Android 開發的半邊天。

一行程式碼就可以捕找到已經洩漏的記憶體洩漏,並且顯示出出現記憶體洩漏的變數或執行緒、洩漏時的引用路徑和出現洩漏的地方。

使用

1.新增依賴

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
 }複製程式碼

2.初始化 leakcanary

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...
  }
}複製程式碼

用例

寫一段記憶體洩露的程式碼。

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onClick(View view) {
        test();
        finish();
        startActivity(new Intent(MainActivity.this, Main2Activity.class));
    }
    // 這裡會發生記憶體洩漏
    public void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}複製程式碼

MainActivity 中點選按鈕後,'test()' 方法內部匿名內部類執行了耗時任務,並且同時 finish() 掉 MainActivity,但是此匿名內部類依然在執行任務,並且隱式的持有 MainActivity 引用,導致 MainActivity 不能及時被 GC 回收,導致記憶體洩露。

LeakCanary 檢測出記憶體洩露後,會在狀態列顯示一條通知,點進去就可以看到詳細資訊。如下圖:

Android 檢測記憶體洩露
leak.png

含義:

標題欄顯示記憶體洩露的類和洩露的記憶體大小,選單欄提供分享出更詳細的資訊,包括堆疊資訊或者 .hprof 檔案。藍色欄顯示包名,第一行顯示出現洩露的執行緒,下面幾行顯示所有的引用,最後一行顯示洩露的類。

MainActivity$1.this$0 的含義:

符號 “$” 代表後者是前者的內部類,“.”就是物件呼叫方法那個點。
用 “.” 分為兩部分,前面整體代表 MainActivity 的一個匿名內部類,用 1 表示,在這裡代表 Runnable 匿名類,後面部分 this$0 整體代表外部類。

看到這個記憶體洩露資訊,首先定位到 MainActivity 中,同時可以看得出是 MainActivity 的例項出現的記憶體洩露,並且發生在子執行緒中,看到程式碼,我們就可以確認肯定是在 Runnale 匿名內部類中隱式的引用了 MainActivity 導致的記憶體洩露。

在這裡打一個斷點:

Android 檢測記憶體洩露
break.png

可以看到匿名類內部存在一個外部 MainActivity 的引用。

找到原因就好辦了,靜態化匿名內部類就解決問題了:

// 靜態
public static void test() {
    // ... 
}複製程式碼

Android 檢測記憶體洩露
break2.png

靜態化之後,發現該匿名內部類中不在持有外部類 MainActivity 對引用,也就不會在 MainActivity 銷燬後,出現記憶體洩露了。

本文由 Bakumon 創作,採用 知識共享署名4.0
國際許可協議進行許可
本站文章除註明轉載/出處外,均為本站原創或翻譯,轉載前請務必署名

相關文章