LeakCanary

一、总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dPzeaHgC-1611411403417)(https://uploader.shimo.im/f/V9gp69g4D0TNV3Oc.png!thumbnail?fileGuid=Pk8VCdxhDKv8qkKh)]

总结:

1、在LeakCanary初始化的方法中,我们调用了application的registerActivityLifecycleCallbacks方法,在这里面可以监听到activity的生命周期。

在onDestroyed方法中,也就是activity生命结束的时候,我们调用refWatcher.watch(activity),进行内存泄漏的检测。

2、我们会将当前的activity生成唯一的id添加到集合retainedKeys中。然后将activity进行弱引用。当我们的弱引用对象被垃圾回收的时候,会将它添加到引用队列ReferenceQueue中。也就是引用队列存储着被垃圾回收的对象。

3、为了保证垃圾回收执行,我们主动调用一次垃圾回收。

4、遍历引用队列,取出对象生成唯一的key和之前集合的key进行对应,将retainedKeys集合中的数据删除。此时retainedKeys剩余的就是没有被回收的对象。说明发生了内存泄漏。

5、接下来进行内存泄漏分析,在这里使用了IdleHandler。当 looper 空闲的时候,会回调IdleHandler的 queueIdle 方法。

6、开启一个类型为IntentService的ForegroundService,在里面会创建通知,告诉用户发生了内存泄漏。点击会跳转到泄漏详情activity。

二、源码解析

1、LeakCanary.install()

public static @NonNull RefWatcher install(@NonNull Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}

代码解析:
LeakCanary.install()可以分为四步

  • 1、refWatcher(application):创建AndroidRefWatcherBuilder。并在父类RefWatcherBuilder的构造方法中新建了一个HeapDump的构造器heapDumpBuilder对象。其中HeapDump就是一个保存heap dump信息的数据结构。
  • 2、链式调用listenerServiceClass(DisplayLeakService.class):(1)传入了一个DisplayLeakService的Class对象,它的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面DisplayLeakActivity的通知;(2)在listenerServiceClass()这个方法中新建了一个ServiceHeapDumpListener对象。
  • 链式调用excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
  • 链式调用buildAndInstall():(1)创建activityRefWatcher实例(2)调用了application的registerActivityLifecycleCallbacks()方法,这样就能够监听activity对应的生命周期事件了。(3)在ActivityLifecycleCallbacks重写了onActivityDestroyed()方法,这样便能在所有Activity执行完onDestroyed()方法之后调用 refWatcher.watch(activity)这行代码进行内存泄漏的检测了。
  • 接下来重点到了当Activity执行完onDestroyed()方法之后,会调用refWatcher.watch(activity)

2、refWatcher.watch(activity)

->watch(watchedReference, "")
public void watch(Object watchedReference, String referenceName) {
   、、、省略部分代码
   //1、使用随机的UUID保证了每个检测对象对应 key 的唯一性。
  String key = UUID.randomUUID().toString();
   //2、将生成的key添加到类型为CopyOnWriteArraySet的Set集合中
  retainedKeys.add(key);
  //3、创建自定义的弱引用KeyedWeakReference
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);
  //4、调用ensureGoneAsync
  ensureGoneAsync(watchStartNanoTime, reference);
}

1)弱引用KeyedWeakReference

KeyedWeakReference继承自WeakReference,在它的构造方法中将watchedReference和ReferenceQueue关联,如果弱引用watchedReference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列ReferenceQueue中。即 :KeyedWeakReference 持有的 Activity 对象如果被GC回收,该对象就会加入到引用队列 referenceQueue 中。

//1、在watch方法里创建reference,参数里的
queue=new ReferenceQueue<>()
final KeyedWeakReference reference =
    new KeyedWeakReference(watchedReference, key, referenceName, queue);

2)ensureGoneAsync

  • 1、执行watchExecutor的execute方法。watchExecutor就是AndroidWatchExecutor。
  • 在execute方法中执行waitForIdle方法。
  • 2、在waitForIdle使用了MessageQueue.IdleHandler(),当 looper 空闲的时候,会回调IdleHandler的 queueIdle 方法,利用这个机制我们可以实现第三方库的延迟初始化,然后执行内部的postToBackgroundWithDelay()方法。
  • 3、postToBackgroundWithDelay()方法会执行RefWatcher#ensureGone()
ensureGoneAsync(watchStartNanoTime, reference);
private void ensureGoneAsync() {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

3)RefWatcher#ensureGone()

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
   //reference 被回收的时候,会被加进 referenceQueue队列中。此时referenceQueue就存储着已经被回收的引用。
   //1、接下来while循环遍历 ReferenceQueue,从里面取出已经被回收的reference,并从 retainedKeys中移除对应的Reference的key。
  removeWeaklyReachableReferences();
  //2、判断 retainedKeys 集合中是否还含有 reference的key,若没有,证明已经被回收了。若含有,可能已经发生内存泄露(或Gc还没有执行回收)。
  if (gone(reference)) {
    return DONE;
  }
  //3、gcTrigger的runGc()方法进行垃圾回收
  gcTrigger.runGc();
  //4、再次执行removeWeaklyReachableReferences方法移除已经被回收的引用
  removeWeaklyReachableReferences();
  //5、再次判断retainedKeys 集合中是否还含有 reference的key,如果此时包含。说明发生了内存泄漏。
  if (!gone(reference)) {
    //6、调用堆信息转储者heapDumper的dumpHeap()生成相应的 hprof 文件。这里的heapDumper是一个HeapDumper接口,具体的实现是AndroidHeapDumper。
    File heapDumpFile = heapDumper.dumpHeap();
    //7、创建heapDump对象
    HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
        .referenceName(reference.name)
        .watchDurationMs(watchDurationMs)
        .gcDurationMs(gcDurationMs)
        .heapDumpDurationMs(heapDumpDurationMs)
        .build();
     //8、执行heapdumpListener的analyze()对新创建的HeapDump对象进行泄漏分析。
    heapdumpListener.analyze(heapDump);
  }
  return DONE;
}

补充:gcTrigger.runGc()垃圾回收

@Override public void runGc() {
  Runtime.getRuntime().gc();
  enqueueReferences();
  System.runFinalization();
}

解析:这里并没有使用System.gc()方法进行回收,因为system.gc()并不会每次都执行。而是调用Runtime.getRuntime().gc(),相比System.gc()更能够保证垃圾回收的工作。

❌4)File heapDumpFile = heapDumper.dumpHeap();

生成heapDumpFile泄漏文件

这里的核心操作就是调用了Android SDK的API Debug.dumpHprofData() 来生成 hprof 文件

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;
  }
}

5)heapdumpListener.analyze(heapDump);

1、heapdumpListener的实现者是ServiceHeapDumpListener,执行analyze方法。

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

2、开启了HeapAnalyzerService。HeapAnalyzerService是一个类型为IntentService的ForegroundService,并且这个服务在单独的进程中运行,为了避免降低app进程的性能或占用内存。
3、执行startForegroundService()之后,会回调onHandleIntentInForeground()方法。

6)调用HeapAnalyzerService的onHandleIntentInForeground方法

@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
 //1、创建heapAnalyzer对象
  HeapAnalyzer heapAnalyzer =
      new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//2、解析 hprof文件
  AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
      heapDump.computeRetainedHeapSize);
 //3、创建一个由PendingIntent构建的泄漏通知用于供用户点击去展示详细的泄漏界面DisplayLeakActivity。即创建一个供用户点击跳转到DisplayLeakActivity的延时通知
  AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

2、解析 hprof文件->checkForLeak方法

/**
 * 在堆转储中搜索具有相应键的实例,然后计算从该实例到GC根的最短强引用路径。
 */
public @NonNull AnalysisResult checkForLeak() {
   、、、
    //1、新建一个内存映射缓存文件buffer
    HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
    //2、使用buffer新建一个HprofParser解析器去解析出对应的引用内存快照文件snapshot
    HprofParser parser = new HprofParser(buffer);
     、、、
    //3、为了减少在Android 6.0版本中重复GCRoots带来的内存压力的影响,使用deduplicateGcRoots()删除了gcRoots中重复的根对象RootObj。
    deduplicateGcRoots(snapshot);
    listener.onProgressUpdate(FINDING_LEAKING_REF);
    //4、调用了findLeakingReference()方法将传入的referenceKey和snapshot对象里面所有类实例的字段值对应的keyCandidate进行比较,如果没有相等的,则表示没有发生内存泄漏
    Instance leakingRef = findLeakingReference(referenceKey, snapshot);
    、、、
    //回一个有泄漏分析结果的AnalysisResult对象
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
   、、、
}

3、展示泄漏路径信息的DisplayLeakService对象

AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);

解析:
AbstractAnalysisResultService的实现类DisplayLeakService对象。在里面直接创建一个由PendingIntent构建的泄漏通知用于供用户点击去展示详细的泄漏界面DisplayLeakActivity。

即创建一个供用户点击跳转到DisplayLeakActivity的延时通知

//创建通知PendingIntent
PendingIntent pendingIntent =
    DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值