Android內存泄漏檢測利器:LeakCanary 轉

是什麼?

一言以蔽之:LeakCanary是一個傻瓜化並且可視化的內存泄露分析工具

爲什麼需要LeakCanary?

因爲它簡單,易於發現問題,人人可參與。

  • 簡單:只需設置一段代碼即可,打開應用運行一下就能夠發現內存泄露。而MAT分析需要Heap Dump,獲取文件,手動分析等多個步驟。
  • 易於發現問題:在手機端即可查看問題即引用關係,而MAT則需要你分析,找到Path To GC Roots等關係。
  • 人人可參與:開發人員,測試測試,產品經理基本上只要會用App就有可能發現問題。而傳統的MAT方式,只有部分開發者纔有精力和能力實施。

如何集成

儘量在app下的build.gradle中加入以下依賴

1
2
3
4
5
 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
 }

在Application中加入類似如下的代碼

1
2
3
4
5
6
7
public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

到這裏你就可以檢測到Activity的內容泄露了。其實現原理是設置Application的ActivityLifecycleCallbacks方法監控所有Activity的生命週期回調。內部實現代碼爲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public final class ActivityRefWatcher {
    private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        public void onActivityStarted(Activity activity) {
        }

        public void onActivityResumed(Activity activity) {
        }

        public void onActivityPaused(Activity activity) {
        }

        public void onActivityStopped(Activity activity) {
        }

        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        public void onActivityDestroyed(Activity activity) {
            ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
    };
    private final Application application;
    private final RefWatcher refWatcher;

    public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        if(VERSION.SDK_INT >= 14) {
            ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
            activityRefWatcher.watchActivities();
        }
    }
....
}

想要檢測更多?

首先我們需要獲得一個RefWatcher,用來後續監控可能發生泄漏的對象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyApplication extends Application {
    private static RefWatcher sRefWatcher;


    @Override
    public void onCreate() {
        super.onCreate();
        sRefWatcher = LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher() {
        return sRefWatcher;
    }
}

監控某個可能存在內存泄露的對象

1
MyApplication.getRefWatcher().watch(sLeaky);

哪些需要進行監控

默認情況下,是對Activity進行了檢測。另一個需要監控的重要對象就是Fragment實例。因爲它和Activity實例一樣可能持有大量的視圖以及視圖需要的資源(比如Bitmap)即在Fragment onDestroy方法中加入如下實現

1
2
3
4
5
6
7
public class MainFragment extends Fragment {
    @Override
    public void onDestroy() {
        super.onDestroy();
        MyApplication.getRefWatcher().watch(this);
    }
}

其他也可以監控的對象

  • BroadcastReceiver
  • Service
  • 其他有生命週期的對象
  • 直接間接持有大內存佔用的對象(即Retained Heap值比較大的對象)

何時進行監控

首先,我們需要明確什麼是內存泄露,簡而言之,某個對象在該釋放的時候由於被其他對象持有沒有被釋放,因而造成了內存泄露。

因此,我們監控也需要設置在對象(很快)被釋放的時候,如Activity和Fragment的onDestroy方法。

一個錯誤示例,比如監控一個Activity,放在onCreate就會大錯特錯了,那麼你每次都會收到Activity的泄露通知。

如何解決

常用的解決方法思路如下

  • 儘量使用Application的Context而不是Activity的
  • 使用弱引用或者軟引用
  • 手動設置null,解除引用關係
  • 將內部類設置爲static,不隱式持有外部的實例
  • 註冊與反註冊成對出現,在對象合適的生命週期進行反註冊操作。
  • 如果沒有修改的權限,比如系統或者第三方SDK,可以使用反射進行解決持有關係

加入例外

有些特殊情況,我們需要忽略一些問題,這時候就需要添加例外規則。比如ExampleClass.exampleField會導致內存泄漏,我們想要忽略,如下操作即可。

1
2
3
4
5
6
7
8
9
// ExampleApplication is defined in "Customizing and using the no-op dependency"
public class DebugExampleApplication extends ExampleApplication {
  protected RefWatcher installLeakCanary() {
    ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults()
        .instanceField("com.example.ExampleClass", "exampleField")
        .build();
    return LeakCanary.install(this, DisplayLeakService.class, excludedRefs);
  }
}

如何實現的

LeakCanary實際上就是在本機上自動做了Heap dump,然後對生成的hprof文件分析,進行結果展示。和手工進行MAT分析步驟基本一致。

如何不影響對外版APK

是的,這個問題確實值得關注,因爲LeakCanary確實是影響程序運行的,尤其是heap dump操作,不過好在這件事Square已經考慮了,即在我們增加依賴時

1
2
3
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1

其中releaseCompile和testCompile這兩個的依賴明顯不同於debugCompile的依賴。它們的依賴屬於NOOP操作。

NOOP,即No Operation Performed,無操作指令。常用的編譯器技術會檢測無操作指令並出於優化的目的將無操作指令剔除。

因而,只要配置好releaseCompile和testCompile的依賴,就無需擔心對外版本的性能問題了。

實踐中的問題

  • 如果targetSdkVersion爲23,在6.0的機器上會存在問題,卡死,因爲LeakCanary並沒有很好支持Marshmallow運行時權限,所以始終得不到sd卡權限,進而導致卡死。
  • 目前LeakCanary已經完美支持運行時權限,大家可以放心使用。

注意

  • 目前LeakCanary一次只能報一個泄漏問題,如果存在內存泄漏但不是你的模塊,並不能說明這個模塊沒有問題。建議建議將非本模塊的泄漏解決之後,再進行檢測。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章