LeakCanary源码分析

基本原理

LeakCanary里面注册了Application.ActivityLifecycleCallbacks的事件监听,在这个里面监听每一个Activity的onDestory()回调。

LeakCanary是从install()方法开始的,从这里为入口进行分析。

代码片段1 —— 何时触发检查?

AndroidRefWatcherBuilder.java

public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.install((Application) context, refWatcher);
    }
    return refWatcher;
  }

ActivityRefWatcher.java

public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        @Override public void onActivityStarted(Activity activity) {
        }

        @Override public void onActivityResumed(Activity activity) {
        }

        @Override public void onActivityPaused(Activity activity) {
        }

        @Override public void onActivityStopped(Activity activity) {
        }

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

        @Override public void onActivityDestroyed(Activity activity) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

之后就是进入watch阶段。

WeakReference & ReferenceQueue

WeakReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦WeakReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,WeakReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。

那ReferenceQueue有什么卵用呢?

ReferenceQueue的作用就是清除失去了引用值的WeakReference对象。

WeakReference作为具有保存“弱”值的特殊性以外,它自己其实还是一般的java对象,有着一般对象的特性。当“弱”值被回收后,虽然这个WeakReference对象的get()方法返回null,但是WeakReference对象还是存在的,没有释放,所以需要一个适当的清除机制,来避免大量的WeakReference对象锁带来的内存消耗和泄漏。

而ReferenceQueue的出现就是为了解决这个问题的,将WeakReference与ReferenceQueue绑定在一起,代码如下:

MyObject aMyObject = new MyObject(); 
ReferenceQueue queue = new ReferenceQueue(); 
WeakReference ref = new WeakReference(aMyObject, queue);

当aMyobject被GC的同时,ref对象会被加入到ReferenceQueue队列中,也就是说ReferenceQueue中保存的对象是WeakReference对象本身,而且是已经失去了它所引用值的WeakReference对象。

这样,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果返回非null,则我们可以将这个WeakReference对象清除掉。一般的代码:

SoftReference ref = null; 

while ((ref = (EmployeeRef) q.poll()) != null) { 
   // 清除ref 
}

完整的一个例子:WeakReference & ReferenceQueue实际使用的例子

在LeakCanary里,它是这样使用的:

// 随机产生的key
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);    

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

代码片段2 —— 如何判断泄漏?

入口是在RefWatcher.ensureGone()方法。

1. 首先把已经已经回收的对象的对应的key,从retainedKeys移除

private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

从上面的分析,可以知道,被监控的对象与queue绑定在了一起,只要被回收,那么queue中就有了值。

2. 判断这个被监控的对象的key是否在retainedKeys里

if (gone(reference)) {
   return DONE;
}

private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
}

如果不在retainedKeys里,则表示不在,则表示回收了。

3. 执行GC

立即把所有 WeakReference 引用的对象回收。

gcTrigger.runGc();

4. 重复1、2步骤

如果被监控的对象的key不在retainedKeys里,则已经通过GC回收了。

5. 利用 heapDumper 把内存情况 dump 成文件,并调用 heapdumpListener 进行内存分析,进一步确认是否发生内存泄漏

File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
    // Could not dump the heap.
    return RETRY;
}

6. 如果确认发生内存泄漏,调用 DisplayLeakService 发送通知

heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));

这样,核心的内存泄漏检测机制便看完了。

代码片段3 —— 如何dump内存情况?

这个整个的代码都是在AndroidHeapDumper里。

File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

    if (heapDumpFile == RETRY_LATER) {
      return RETRY_LATER;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);

    if (!waitingForToast.wait(5, SECONDS)) {
      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
      return RETRY_LATER;
    }

    Toast toast = waitingForToast.get();
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      cancelToast(toast);
      return heapDumpFile;
    } catch (Exception e) {
      CanaryLog.d(e, "Could not dump heap");
      // Abort heap dump
      return RETRY_LATER;
    }
  }

private void showToast(final FutureResult<Toast> waitingForToast) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        final Toast toast = new Toast(context);
        toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
        toast.setDuration(Toast.LENGTH_LONG);
        LayoutInflater inflater = LayoutInflater.from(context);
        toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
        toast.show();
        // Waiting for Idle to make sure Toast gets rendered.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            waitingForToast.set(toast);
            return false;
          }
        });
      }
    });
  }

这里是整个的dump的逻辑,核心的代码就是Debug.dumpHprofData()

技术细节点:

这个里面有个这个代码:

FutureResult<Toast> waitingForToast = new FutureResult<>();

使用这个进行了等待,最长5s(这个时间也是ANR产生的时间),因为GC不是马上执行,另外在showToast里Looper.myQueue().addIdleHandler,这个也是表示等待这个Looper Message都执行完之后才去采集。

代码片段4 —— 如何分析hump文件的?

入口:

public final class ServiceHeapDumpListener implements HeapDump.Listener {

  private final Context context;
  private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

  public ServiceHeapDumpListener(Context context,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabled(context, listenerServiceClass, true);
    setEnabled(context, HeapAnalyzerService.class, true);
    this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
    this.context = checkNotNull(context, "context").getApplicationContext();
  }

  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
}

它实际会走到HeapAnalyzer类:

至此,LeakCanary的源码部分已经分析完毕,它还有一些其他的技术点可以学习,详见:

细节点 — 1 细节点 — 2 细节点 — 3 细节点 — 4

最后附上一张完整的LeakCanary分析的流程图:

results matching ""

    No results matching ""