前言
本篇基于1.6.1版本源码阅读, 本篇内容就是搞懂LeakCanary如何做到内存泄漏定位的主要流程, 不抠具体细节.
正文
老样子, 我们直接从从LeakCanary.install(this)作为入口开始看1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
  public RefWatcher buildAndInstall() {
      if (LeakCanaryInternals.installedRefWatcher != null) {
        throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
      }
      RefWatcher refWatcher = build();
      if (refWatcher != DISABLED) {
        if (watchActivities) {
          ActivityRefWatcher.install(context, refWatcher);
        }
        if (watchFragments) {
          FragmentRefWatcher.Helper.install(context, refWatcher);
        }
      }
      LeakCanaryInternals.installedRefWatcher = refWatcher;
      return refWatcher;
    }
通过AndroidRefWatcherBuilder对象进行一系列相关对象的初始化, 包括ServiceHeapDumpListener, ExcludedRefs,以及最重要的RefWatcher.
- ServiceHeapDumpListener:堆解析监听, 主要负责启动解析Heap服务以及负责处理引用路径的解析服务的连接
- ExcludedRefs: 内存泄漏分析的白名单
- RefWatcher: 观测引用是否弱可达, 通过它来观测是否该回收的内存未被回收导致内存泄漏, 如果存在这种情况, 会触发HeapDumper的记录
我们通过ActivityRefWatcher.install(context, refWatcher)查看Actvitiy的内存泄漏的分析流程1
2
3
4
5
6
7
8
9
10
11
12
13public static void install(Context context, RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }
  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
         public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
可以看到注册了一个ActivityLifecycleCallbacks, 在页面生命周期走到onDestroy的时候, 会触发refWatcher.watch(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
25public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }
  /**
   * Watches the provided references and checks if it can be GCed. This method is non blocking,
   * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
   * with.
   *
   * @param referenceName An logical identifier for the watched object.
   */
  public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    ensureGoneAsync(watchStartNanoTime, reference);
  }
申明弱引用, 放入activity对象, 注册关联queue引用队列1
2
3
4
5
6
7private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
       public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }
在AndroidRefWatcherBuilder可以通过watchDelay(long delay, TimeUnit unit)设置是否延迟观测, 如果有设置, 则会在保证在主线程内延迟进行分析内存泄漏; 否则直接执行分析处理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
36
37
38
39Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    removeWeaklyReachableReferences();
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    // 不存在内存泄漏的对象
    if (gone(reference)) {
      return DONE;
    }
    // 重新执行gc
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();
      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }
| 1 | private void removeWeaklyReachableReferences() { | 
每个activity申明弱引用的时候都会有个ID, ID保存在retainedKeys集合中, 首先遍历移除被gc回收的对象, 如果这个时候retainedKeys集合为空, 则表示不存在内存泄漏的情况. 否则手动执行GC, 再次判断移除, 这个时候如果retainedKeys内仍存在ID, 则说明有内存泄漏的情况存在.
在存在内存泄漏的情况下, 通过heapDumper.dumpHeap()获取堆内存快照, 通过heapdumpListener.analyze去进行解析.1
2
3
4public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
| 1 | public static void runAnalysis(Context context, HeapDump heapDump, | 
这里启动了HeapAnalyzerServiceIntentService, 这个服务主要做的就是去解析我们的.hprof文件, 主要的工作内容在onHandleIntentInForeground方法内1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 protected void onHandleIntentInForeground(@Nullable Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
然后通过haha库, 将.hprof文件解析结果AnalysisResult对象, 通过AbstractAnalysisResultService.sendResultToListener传递启动DisplayLeakService服务.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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);
    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if (shouldSaveResult) {
      heapDump = renameHeapdump(heapDump);
      resultSaved = saveResult(heapDump, result);
    }
    PendingIntent pendingIntent;
    String contentTitle;
    String contentText;
    if (!shouldSaveResult) {
      contentTitle = getString(R.string.leak_canary_no_leak_title);
      contentText = getString(R.string.leak_canary_no_leak_text);
      pendingIntent = null;
    } else if (resultSaved) {
      pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
      if (result.failure == null) {
        if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
          String className = classSimpleName(result.className);
          if (result.excludedLeak) {
            contentTitle = getString(R.string.leak_canary_leak_excluded, className);
          } else {
            contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
          }
        } else {
          String size = formatShortFileSize(this, result.retainedHeapSize);
          String className = classSimpleName(result.className);
          if (result.excludedLeak) {
            contentTitle = getString(R.string.leak_canary_leak_excluded_retaining, className, size);
          } else {
            contentTitle =
                getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
          }
        }
      } else {
        contentTitle = getString(R.string.leak_canary_analysis_failed);
      }
      contentText = getString(R.string.leak_canary_notification_message);
    } else {
      contentTitle = getString(R.string.leak_canary_could_not_save_title);
      contentText = getString(R.string.leak_canary_could_not_save_text);
      pendingIntent = null;
    }
    // New notification id every second.
    int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
  }
将对应的.hprof文件重命名, 对应泄漏的内存对象关联key, 传递到DisplayLeakActivity做显示, 另外进行通知显示.
总结
通过上文的简易解析, 我们可以得出LeakCanary的一个大概的原理流程.通过application注册ActivityLifecycleCallbacks的回调, 在每个activity销毁的时候, 将activity的弱引用包装绑定在ReferenceQueue上, 当GC的时候, 可以通过queue移除已被回收的activity对象key, 获得始终未被回收的对象, 判断为是内存泄漏, 根据haha库解析heap dumps,获取引用路径最终在DisplayLeakActivity上显示我们熟悉的内存泄漏的列表内容.