三方库详细解读——LeakCanary的实现

综述

我们知道LeakCanary是用来检查Activity和Fragment是否存在内存泄漏的。那么他是如何监测的呢?我们本篇文章就来解析

监测生命周期

监测Activity生命周期

public final class ActivityRefWatcher {

  public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
}

实现很简单,就是注册Android原生的ActivityLifecycleCallbacks的监听接口,可以监测到当前自己app里所有Activity的销毁的回调。
同样,我们看看Fragment

class AndroidOFragmentRefWatcher implements FragmentRefWatcher {

  private final RefWatcher refWatcher;

  AndroidOFragmentRefWatcher(RefWatcher refWatcher) {
    this.refWatcher = refWatcher;
  }

  private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {

        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }

        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };

  @Override public void watchFragments(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
  }
}

同样也是注册系统的Fragment的生命周期的接口,也就是FragmentLifecycleCallbacks。我们发现他们在Activity/Fragment销毁的时候,做了同一件事情,就是refWatcher.watch()方法,我们下面具体讲解如何监测。

检测是否内存泄漏

看看watch在干嘛?

  public void watch(Object watchedReference, String 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/Fragment包装到了KeyedWeakReference里面,这个类是什么?

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

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

就是一个弱引用,弱引用的特点我们也知道,虚拟机一旦发现其不可达就会回收。
那我们看看之后走了什么,主要实现代码ensureGoneAsync:

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    removeWeaklyReachableReferences(); // 函数一

    if (gone(reference)) {  // 函数二
      return DONE;
    }
    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;
  }

我们看一下实现函数一什么意思?

  private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

很简单,也就是遍历我们在queue中的存放的虚引用包装过的对象,找到销毁的,并从队列移除。
函数二:

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

也很简单,就是判断,到底还有没有对象没有释放了。
函数三:触发虚拟机的GC
函数四:dump 内存泄漏发生时的调用栈,改实现在AndroidHeapDumper中,我们看一下:

public File dumpHeap() {
   // 代码块一
    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;
    }

  // 代码块二
    Notification.Builder builder = new Notification.Builder(context)
        .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
    Notification notification = LeakCanaryInternals.buildNotification(context, builder);
    NotificationManager notificationManager =
        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    int notificationId = (int) SystemClock.uptimeMillis();
    notificationManager.notify(notificationId, notification);

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

我们看一下代码块一的实现,实际最主要的就是创建dump文件,大致的流程分为:
1、确认当前有没有未完成的dump文件,如果有,则直接返回RETRY_LATER。当然这些未完成的dump文件有个上限时间,超过上限时间,则认为他失效了。
2、删除失效的未完成dump。
3、如果1中没有正在进行的dump,则先申请存储权限,然后建立一个(随机字串)_pending.hrof的文件。_pending就是用来判断未完成的。

代码块二就是在生成通知。

代码块三则是真正dump文件的过程。调用的就是原生的接口
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
这个dump完成之后,就会生成hprof文件了。

总结

所以其实LeakCanary的实现很简单,主要还是在使用原生的接口,通过检测Activity/Fragment的生命周期,可以知道是否页面关闭了。
然后通过弱引用包装一下关闭的Activity/Fragment,通过主动触发一次GC,然后再检测弱引用里面是否还包含这些Activity/Fragment,来判断是否内存泄漏。
最后也是通过Debug.dumpHprofData接口实现了,dump栈的能力。

你可能感兴趣的:(android)