One, the introduction

Memory leaks have always been a problem to avoid in Android development, so finding and locating memory leaks is our top priority. The most popular memory leak detection component on the market right now is the well-known LeakCanary, which easily and intuitively displays the chain of reference to the location of a memory leak, helping us to quickly locate it. In addition, it is very simple and friendly to use. For developers, only to meet the use is not enough, as far as possible to know why, so there is this source code analysis.

Two, use and principle

The source code analysis is based on LeakCanary version 1.6.3

2.1 Usage Mode

LeakCanary can detect memory leaks from specific activities and fragments, as well as the objects you want to observe. It is very simple to use, to detect Activity as an example, the following lines of code.

public class MyApplication extends Application {
    @Override
    public void onCreate(a) {
        super.onCreate();
        if(! LeakCanary.isInAnalyzerProcess(this)) {
            LeakCanary.install(this); }}}Copy the code

To customize the Application class, call the LeakCanary.install() method.

2.2 Detection Principle

LeakCanary is detected by:

Weak-reference objects are added to the reference queue after their wrapper object is reclaimed (the weak-reference object was passed into the reference queue when it was created).ReferenceQueue).

Through theReferenceQueueTo detect whether there is a weak reference object of the target object, and then determine whether the target object is reclaimed.

Such as:

. Activity mActivity; . ReferenceQueue<Activity> mQueue =new ReferenceQueue<>();
WeakReference<Activity> mWeakReference = newWeakReference<>(mActivity, mQueue); .Copy the code

When creating a weak reference object for the target mActivity, if a reference queue mQueue is passed in the constructor, the mWeakReference object will be added to the reference queue when the mActivity object is reclaimed.

Third, source code analysis

After understanding the detection principle, now to detect the Activity whether there is a memory leak as an example, to analyze the implementation of the source code.

3.1 Use entranceinstallmethods

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

Do some initialization construction, focusing on the buildAndInstall method

3.2,buildAndInstallmethods

public @NonNull RefWatcher buildAndInstall(a) {
    if(LeakCanaryInternals.installedRefWatcher ! =null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    / / 1.
    RefWatcher refWatcher = build();
    / / 2.
    if(refWatcher ! = DISABLED) {if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if(watchFragments) { FragmentRefWatcher.Helper.install(context, refWatcher); }}/ / 3.
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }
Copy the code

Install RefWatcher (RefWatcher) RefWatcher (RefWatcher) RefWatcher (RefWatcher)

(2) Some custom Settings are mainly performed, such as whether to display the link display page for memory leakage, whether to detect the memory leakage of the Activity, and whether to detect the memory leakage of the Fragment. The default value is No, yes, and yes. So will call ActivityRefWatcher. Install () method and FragmentRefWatcher Helper. Install method.

The two methods are to set when to detect whether an Activity/Fragment object leaks.

With ActivityRefWatcher. Install () method, for example

// ActivityRefWatcher.class.public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
	/ / (2). 1
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

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

Registering the Activity’s lifecycle callback, LeakCanary has registered to detect whether the current Activity object has a memory leak when the onDestroy lifecycle method is executed.

For Fragment logic, check onFragmentDestroyed and onFragmentViewDestroyed

(3) will create RefWatcher objects assigned to LeakCanaryInternals. InstalledRefWatcher object

3.3. Detect the logical entryrefWatcher.watch()

The following code

// RefWatcher.class.public void watch(Object watchedReference) {(1) watch (watchedReference,"");
  }

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    / / 2.
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    / / 3.
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
	/ / 4.
    ensureGoneAsync(watchStartNanoTime, reference);
  }
Copy the code

① For activities and fragments, the referenceName passed in is an empty string

② During each detection, a random number is generated and added to the set. This random number is treated as an attribute of KeyedWeakReference object, so it can refer to the detected Activity/Fragment object.

③ Then create KeyedWeakReference object, where the reference queue is passed in. So when the observed Activity or Fragment is reclaimed, the corresponding reference object will be added to the reference queue.

④ Start the specific logic of memory leak detection

// RefWatcher.class.private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    / / 5.
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run(a) {
        returnensureGone(reference, watchStartNanoTime); }}); }Copy the code

WatchExecutor is a functional interface, only a execute method, see its implementation class AndroidWatchExecutor, call

The execute method

// AndroidWatchExecutor.class
@Override public void execute(@NonNull Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0); }}private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    mainHandler.post(new Runnable() {
      @Override public void run(a) { waitForIdle(retryable, failedAttempts); }}); }private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle(a) {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false; }}); }private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    / / 6.
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run(a) {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1); } } }, delayMillis); }...Copy the code

If it’s on the main thread, it calls waitForIdle, if it’s on the main thread, it calls postWaitForIdle, and it’s always going to call waitForIdle.

Using IdleHandler postToBackgroundWithDelay when the message queue free to perform tasks, and executed only once.

⑥ Delay the execution of runnable in the child thread for 5s. EnsureGone method will be executed

3.4 ensureGone method for leak determination

EnsureGone method is mainly used to determine whether there is a memory leak, and finally determine whether there is a memory leak through two decisions.

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

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    / / 2.
    if (gone(reference)) {
      return DONE;
    }
    / / 3.
    gcTrigger.runGc();
    / / 4.
    removeWeaklyReachableReferences();
    / / 5.
    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 boolean gone(KeyedWeakReference reference) {
    return! retainedKeys.contains(reference.key); }private void removeWeaklyReachableReferences(a) {
    // 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); }}Copy the code

(1) Fetch the object from the reference queue. If it is not empty, delete the random number of the object stored in the collection. If the object is reclaimed and exists in the reference queue, the random numbers in the collection are removed, which means there is no memory leak.

② Determine whether the random number is still in the retainedKeys set. If it is not, it means that the reference queue has the object, and the object is reclaimed, and there is no memory leak.

(3) If the retainedKeys collection still has that random number, it means that the object is not in the reference queue, the object is not collected, and a GC is triggered manually.

④ After GC, the random number referring to the detected object in the retainedKeys set was removed again. If the observed object can be reclaimed after GC, the reference queue exists and the keys in the retainedKeys collection can be removed.

⑤ The retainedKeys collection also has this random number, indicating that the object can not be reclaimed, and a memory leak occurred. Start to export heap files, analysis, display leaked reference link page, etc.

3.5 other

LeakCanary defaults to detecting memory leaks from activities/fragments. The refWatcher. Watch method is the core method for detecting memory leaks, so if a refWatcher object is created, it can detect memory leaks from any object.

4, summarize

This article describes how to use LeakCanary and how to detect it. The core principle is: using the reference queue that is passed in when the object is recycled with weak reference, the weak reference object will be put into the reference queue when the wrapped object is recycled, and whether the object is recycled can be determined by detecting its existence in the reference queue.

In addition, the source code is analyzed, the timing of memory detection, the specific logic of detection, etc.