深入分析LeakCanary原理

1.LeakCanary简介

LeakCanary是Square公司开源的内存泄露检测工具。
github:LeakCanary

2.源码分析

LeakCanary源码分析我们从install方法开始,大家应该知道引入LeakCanary的时候install方法时在Application#onCreate的方法中执行的。

我们首先看isInAnalyzerProcess方法,看注释如果是LeakCanary进程就不允许初始化其他任务。这个进程是为LeakCanary分析堆内存用的。

if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
LeakCanary.install(this);

我们知道引入LeakCanary在你的项目编译成功之后会自动安装一个Leaks apk。看到上面LeakCanary.install(this)这行代码,是不是安装Leaks apk?是不是Leaks通过跨进程来检测我项目呢?检测原理又是怎样的?带着这些问题,接下来看看具体做了什么事情。会调用LeakCanary#install方法

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

首先我们看看buildAndInstall方法具体做了什么事情

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

看ActivityRefWatcher类名应该是Activity 引用监控,我们跟进这个installOnIcsPlus方法看看

public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
    if (SDK_INT < ICE_CREAM_SANDWICH) {
      // If you need to support Android < ICS, override onDestroy() in your base activity.
      return;
    }
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    activityRefWatcher.watchActivities();
  }

然后installOnIcsPlus方法又调用了ActivityRefWatcher#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);
        }
      };
public void watchActivities() {
    // Make sure you don't get installed twice.确保不要注册两次
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

我们首先看看Application#registerActivityLifecycleCallbacks方法,我们知道原理registerActivityLifecycleCallbacks是注册Activity生命周期回调的方法。管理生命周期的方法,每个Activity都会回调到这里。我们看lifecycleCallbacks这个变量对应着Activity的生命周期。
到这里所以我们是不是可以猜想到LeakCanary其实就是通过注册Activity生命周期回调来监控检查是否有内存泄露的。当Activity界面关闭回调用对应的onActivityDestroyed方法。
接下来我们看看Application.ActivityLifecycleCallbacks#onActivityDestroyed方法干了些什么事情。

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

接下来会调用到com.squareup.leakcanary.RefWatcher#watch(java.lang.Object)方法。

public 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引用包装成一个KeyedWeakReference弱引用。被WeakReference包装的引用如果被回收会添加到ReferenceQueue队列中,WeakReference和ReferenceQueue将在接下来的文章里分析。WeakReference和ReferenceQueue
通过检查ReferenceQueue里的Activity引用来检测是否能够被回收。下面具体看看检测方法:
ensureGoneAsync(watchStartNanoTime, reference)--->ensureGone(reference, watchStartNanoTime)

Retryable.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;
    }
    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);
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

通过ensureGone这个方法名可以看出应该是确保引用释放的意思。具体看看ensureGone方法做了些什么:
我们主要看removeWeaklyReachableReferences(),gcTrigger.runGc(),heapdumpListener.analyze()这三个方法。
1.通过removeWeaklyReachableReferences移除所有ReferenceQueue队列的WeakReference对象的Activity引用。如果可以回收就直接返回。

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

2.Activity引用如果通过removeWeaklyReachableReferences还是没有移除就通过gcTrigger.runGc()来GC触发回收。
3.接下来再触发步骤1的removeWeaklyReachableReferences方法来移除Activity引用,判断是否回收,如果可以回收就直接返回,反之就说明对象泄露了就会触发heapdumpListener.analyze()方法来分析,看到HeapDump对象,我们可以通过heapDumper.dumpHeap()方法看到dump当前的heap hprof快照文件。
具体通过Debug.dumpHprofData(heapDumpFile.getAbsolutePath())方法去dump内存文件的。实现如下:

@Override public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

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

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

接下来会调用com.squareup.leakcanary.ServiceHeapDumpListener#analyze方法将内存文件目录等相关信息通过HeapAnalyzerService#runAnalysis方法来进行对象内存泄露的分析。实现如下:

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

接着会启动HeapAnalyzerService IntentService服务,具体IntentService的分析在我的IntentService源码分析中有讲过IntentService源码分析

public static void runAnalysis(Context context, HeapDump heapDump,
      Class listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

具体在HeapAnalyzerService#onHandleIntent方法中通过HeapAnalyzer#checkForLeak来检测是否内存泄露的。实现如下:

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      Snapshot snapshot = parser.parse();
      deduplicateGcRoots(snapshot);

      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
      }

      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

接下来启动AbstractAnalysisResultService这个IntentService,启动后会调用onHandleIntent方法。

@Override protected final void onHandleIntent(Intent intent) {
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
    AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
    try {
      onHeapAnalyzed(heapDump, result);
    } finally {
      //noinspection ResultOfMethodCallIgnored
      heapDump.heapDumpFile.delete();
    }
  }

最终通过onHeapAnalyzed这个抽象方法将heapDump,AnalysisResult信息传入到子类实现。

@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d(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) {
        String size = formatShortFileSize(this, result.retainedHeapSize);
        String className = classSimpleName(result.className);
        if (result.excludedLeak) {
          contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
        } else {
          contentTitle = getString(R.string.leak_canary_class_has_leaked, 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);
  }

分析上面逻辑其实就是通过DisplayLeakService做内存泄露相关展示。

3.总结

到目前为止,我想大家应该知道LeakCanary是怎样实现内存泄露的检测。
利用Application#registerActivityLifecycleCallbacks Activity生命周期回调onActivityDestroyed方法通过调用RefWatcher#wather方法来检测对象是否回收,通过removeWeaklyReachableReferences--->gcTrigger.runGc--->removeWeaklyReachableReferences--->heapdumpListener.analyze 三步二次检测来确定内存泄露,最终dump 内存信息来分析到最终显示分析出的泄露信息。

4.LeakCanary原理实践

分析完LeakCanary原理,其实我们也可以在自己的项目中利用LeakCanary原理在打开每个Actvitiy或者Fragment时定义一个弱引用实例,然后关联在一个ReferenceQueue队列中,然后通过Application#registerActivityLifecycleCallbacks#onActivityDestroyed回调去关联的ReferenceQueue中检测当前的Actvitiy或者Fragment是否被回收来检测是否泄漏,这样就是一个精简版的LeakCanary。当发现内存泄漏,然后dump hprof内存快照文件到服务器,然后分析内存文件,从而可以发现内存泄漏。

That's All

你可能感兴趣的:(深入分析LeakCanary原理)