LeakCanary principle analysis

LeakCanary is a memory leak detection tool for and by Square .AndroidJava

use

LeakCanaryThe integration process is very simple, first add the dependencies in the build.gradlefile :

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

debugand releaseversions are using different libraries. LeakCanaryThe runtime performs GCoperations , releasewhich affects efficiency in the version. android-no-opThere is basically no logic implementation in the version, which is used for the releaseversion .

Then implement your own Applicationclass :

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    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);
    // Normal app init code...
  }
}

This completes the integration. LeakCanaryWhen a memory leak is detected, it will automatically pop up Notificationto notify the developer of the memory leak Activityand reference chain for repair.

Source code analysis

LeakCanary.install(this)Start the analysis from the entry function :

LeakCanary.install

LeakCanary.java

/**
 * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
 * references (on ICS+).
 */
public static RefWatcher install(Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}

LeakCanary.refWatcher

LeakCanary.java

/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
  return new AndroidRefWatcherBuilder(context);
}

refWatcher()The method creates a new AndroidRefWatcherBuilderobject , which inherits from the RefWatcherBuilderclass, configures some default parameters, and uses the builder to build an RefWatcherobject .

AndroidRefWatcherBuilder.listenerServiceClass

AndroidRefWatcherBuilder.java

public AndroidRefWatcherBuilder listenerServiceClass(
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

RefWatcherBuilder.java

/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
  this.heapDumpListener = heapDumpListener;
  return self();
}

DisplayLeakService.java

/**
 * Logs leak analysis results, and then shows a notification which will start {@link
 * DisplayLeakActivity}.
 *
 * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,
 * String)} to add custom behavior, e.g. uploading the heap dump.
 */
public class DisplayLeakService extends AbstractAnalysisResultService {}

listenerServiceClass()The method is bound to a background service DisplayLeakService, which is mainly used to analyze the results of memory leaks and send notifications. You can inherit and override this class to perform some custom operations, such as uploading analysis results.

RefWatcherBuilder.excludedRefs

RefWatcherBuilder.java

public final T excludedRefs(ExcludedRefs excludedRefs) {
  this.excludedRefs = excludedRefs;
  return self();
}

AndroidExcludedRefs.java

/**
 * This returns the references in the leak path that can be ignored for app developers. This
 * doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs
 * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app
 * developers except by resorting to serious hacks, so we remove the noise caused by those leaks.
 */
public static ExcludedRefs.Builder createAppDefaults() {
  return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}

public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
  ExcludedRefs.Builder excluded = ExcludedRefs.builder();
  for (AndroidExcludedRefs ref : refs) {
    if (ref.applies) {
      ref.add(excluded);
      ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
    }
  }
  return excluded;
}

excludedRefs()The method defines some paths that can be ignored by the developer, which means that even if there is a memory leak here, the LeakCanarynotification will not pop up. Most of these are caused by system bugs and do not need to be dealt with by the user.

AndroidRefWatcherBuilder.buildAndInstall

Finally call the buildAndInstall()method to build RefWatcherthe instance and start listening Activityfor references:

AndroidRefWatcherBuilder.java

/**
 * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
 */
public RefWatcher buildAndInstall() {
  RefWatcher refWatcher = build();
  if (refWatcher != DISABLED) {
    LeakCanary.enableDisplayLeakActivity(context);
    ActivityRefWatcher.install((Application) context, refWatcher);
  }
  return refWatcher;
}

Take a look at the main build()and install()methods:

RefWatcherBuilder.build

RefWatcherBuilder.java

/** Creates a {@link RefWatcher}. */
 public final RefWatcher build() {
   if (isDisabled()) {
     return RefWatcher.DISABLED;
   }

   ExcludedRefs excludedRefs = this.excludedRefs;
   if (excludedRefs == null) {
     excludedRefs = defaultExcludedRefs();
   }

   HeapDump.Listener heapDumpListener = this.heapDumpListener;
   if (heapDumpListener == null) {
     heapDumpListener = defaultHeapDumpListener();
   }

   DebuggerControl debuggerControl = this.debuggerControl;
   if (debuggerControl == null) {
     debuggerControl = defaultDebuggerControl();
   }

   HeapDumper heapDumper = this.heapDumper;
   if (heapDumper == null) {
     heapDumper = defaultHeapDumper();
   }

   WatchExecutor watchExecutor = this.watchExecutor;
   if (watchExecutor == null) {
     watchExecutor = defaultWatchExecutor();
   }

   GcTrigger gcTrigger = this.gcTrigger;
   if (gcTrigger == null) {
     gcTrigger = defaultGcTrigger();
   }

   return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
           excludedRefs);
 }

build()The method uses the builder pattern to build an RefWatcherinstance , and take a look at the main parameters:

  • watchExecutor: thread controller, performs memory leak detection onDestroy()after and when the main thread is idle
  • debuggerControl: Determine whether it is in debug mode, memory leak detection will not be performed in debug mode
  • gcTrigger: Used to GCdetect watchExecutora possible memory leak for the first time, it will take the initiative to detect it GC, and GCthen it will be detected again. If it is still leaking, it will be determined as a memory leak, and follow-up operations will be performed.
  • heapDumper: infodump on memory leak , write to fileheaphprof
  • heapDumpListener: After parsing the hproffile and notifying DisplayLeakServicea pop-up reminder
  • excludedRefs: exclude ignorable leak paths

LeakCanary.enableDisplayLeakActivity

The next step is the core install()method , and here we start to observe Activitythe reference. There is one more step before this, LeakCanary.enableDisplayLeakActivity(context);:

public static void enableDisplayLeakActivity(Context context) {
  setEnabled(context, DisplayLeakActivity.class, true);
}

Finally executed to LeakCanaryInternals#setEnabledBlocking:

public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
    boolean enabled) {
  ComponentName component = new ComponentName(appContext, componentClass);
  PackageManager packageManager = appContext.getPackageManager();
  int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
  // Blocks on IPC.
  packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}

Here is enabled DisplayLeakActivityand app icon is displayed. Note that this does not refer to your own application icon, but a LeakCanaryseparate application to display the history of memory leaks. The entry function is DisplayLeakActivity, you can see it in AndroidManifest.xml by default android:enabled="false":

<activity
    android:theme="@style/leak_canary_LeakCanary.Base"
    android:name=".internal.DisplayLeakActivity"
    android:process=":leakcanary"
    android:enabled="false"
    android:label="@string/leak_canary_display_activity_label"
    android:icon="@mipmap/leak_canary_icon"
    android:taskAffinity="com.squareup.leakcanary.${applicationId}"
    >
  <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
  </intent-filter>
</activity>

ActivityRefWatcher.install

ActivityRefWatcher.java

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

public void watchActivities() {
  // Make sure you don't get installed twice.
  stopWatchingActivities();
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

watchActivities()In the method, the lifecycle callback registration is unbound first lifecycleCallbacks, and then re-bound to avoid repeated binding. lifecycleCallbacksMonitored Activitythe various life cycles of , onDestroy()and began to detect Activitythe reference in .

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

The following focuses on the analysis RefWatcherof how to detect Activity.

RefWatcher.watch

Call RefWatcher#watchdetection Activity.RefWatcher.java

/**
 * Identical to {@link #watch(Object, String)} with an empty string reference name.
 *
 * @see #watch(Object, String)
 */
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);
}

watch()The parameters of the method are Object, LeakCanarynot just Androidfor , it can detect memory leaks of any object, the principle is the same.

Here are a few new faces, let's take a look at what each is:

  • retainedKeys: a Set<String>collection , each detected object corresponds to a unique key, stored retainedKeysin
  • KeyedWeakReference: A custom weak reference that holds the detection object and the keyvalue
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");
  }
}
  • queue: ReferenceQueueobject KeyedWeakReference, used with

Here is a little knowledge point. When weak references and reference queues are used in ReferenceQueuecombination , if the object held by the weak reference is garbage collected, the Java virtual machine will add the weak reference to the reference queue associated with it. That is, if the KeyedWeakReferenceheld Activityobject is garbage collected, the object will be added to the reference queuequeue .

Then look at the specific memory leak judgment process:

RefWatcher.ensureGoneAsync

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

watchExecutorPerform the detection operation by , where watchExecutoris the AndroidWatchExecutorobject .

@Override protected WatchExecutor defaultWatchExecutor() {
  return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}

DEFAULT_WATCH_DELAY_MILLISis 5 s.

public AndroidWatchExecutor(long initialDelayMillis) {
  mainHandler = new Handler(Looper.getMainLooper());
  HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
  handlerThread.start();
  backgroundHandler = new Handler(handlerThread.getLooper());
  this.initialDelayMillis = initialDelayMillis;
  maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}

Take a look at a few objects used in it:

  • mainHandler: Main thread message queue
  • handlerThread: background thread, HandlerThreadobject, thread nameLeakCanary-Heap-Dump
  • backgroundHandler: The message queue of the background thread above
  • initialDelayMillis: 5 s, i.e. beforeDEFAULT_WATCH_DELAY_MILLIS
@Override public void execute(Retryable retryable) {
  if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
    waitForIdle(retryable, 0);
  } else {
    postWaitForIdle(retryable, 0);
  }
}

void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
  mainHandler.post(new Runnable() {
    @Override public void run() {
      waitForIdle(retryable, failedAttempts);
    }
  });
}

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() {
      postToBackgroundWithDelay(retryable, failedAttempts);
      return false;
    }
  });
}

In the specific execute()process , whether it is waitForIdleor postWaitForIdle, it will eventually switch to the main thread for execution. It should be noted that when is the implementation IdleHandlerhere ?

We all know Handlerthat it processes messages in a loop MessageQueue. When there are no more messages to be processed in the message queue, and the IdleHandlerinterface , this is the operation that will be processed here. That is, specify some operations to process when the thread is idle. When the main thread is idle, it will notify the background thread to perform memory leak detection with a delay of 5 seconds.

void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
  long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
  long delayMillis = initialDelayMillis * exponentialBackoffFactor;
  backgroundHandler.postDelayed(new Runnable() {
    @Override public void run() {
      Retryable.Result result = retryable.run();
      if (result == RETRY) {
        postWaitForIdle(retryable, failedAttempts + 1);
      }
    }
  }, delayMillis);
}

Here's the actual detection process, AndroidWatchExecutorwhich calls the ensureGone()method :

RefWatcher.ensureGone

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

Repeat the meaning of several variables again, it retainedKeysis a Setcollection , which stores the unique keyvalue corresponding to the detection object, and it queueis a reference queue, which stores the objects that are garbage collected.

The main process has the following steps:

RefWatcher.emoveWeaklyReachableReferences()

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

Traverse the reference queue queueto determine whether there is a Activitycurrent weak reference in the queue, and delete retainedKeysthe keyvalue .

RefWatcher.gone()

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

retainedKeysDetermines whether contains Activitythe keyvalue .

If it is not included, it means that the value of the reference was retainedKeysremoved in the keyprevious operation, that is to say, the reference queuewas the reference was GCprocessed, no memory leak occurred, return DONE, and no further execution.

If it is included, it will not immediately determine that a memory leak occurs. There may be an object that is unreachable but has not yet entered the reference queue queue. At this time, it will take the initiative to perform an GCoperation and then make a judgment again.

gcTrigger.runGc()

/**
 * Called when a watched reference is expected to be weakly reachable, but hasn't been enqueued
 * in the reference queue yet. This gives the application a hook to run the GC before the {@link
 * RefWatcher} checks the reference queue again, to avoid taking a heap dump if possible.
 */
public interface GcTrigger {
  GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      Runtime.getRuntime().gc();
      enqueueReferences();
      System.runFinalization();
    }

    private void enqueueReferences() {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
      // references to the appropriate queues.
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        throw new AssertionError();
      }
    }
  };

  void runGc();
}

Note GCthe , not the use System.gc. System.gcJust notifying the system to perform a garbage collection operation at the right time does not actually guarantee that it will be executed.

GCAfter taking the initiative, it will be judged again, and the process is the same as above. First removeWeaklyReachableReferences()call retainedKeysthe keyvalue clear, and then judge whether to remove it. If it is still not removed, it is determined to be a memory leak.

Memory leak result processing

AndroidHeapDumper.dumpHeap

After determining the memory leak, call heapDumper.dumpHeap()to handle:

AndroidHeapDumper.java

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@Override 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;
  }

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

leakDirectoryProvider.newHeapDumpFile()Create a new hproffile , then call the Debug.dumpHprofData()method dumpcurrent heap memory and write to the file just created.

Back in the RefWatcher.ensureGone()method , heapDumpFileafter generating the file, pass the heapdumpListeneranalysis .

ServiceHeapDumpListener.analyze

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

Here heapdumpListeneris the ServiceHeapDumpListenerobject, then go to the ServiceHeapDumpListener.runAnalysis()method .

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

Here listenerServiceClassrefers to the configuration DisplayLeakService.classmentioned AndroidRefWatcherBuilderat .

@Override protected HeapDump.Listener defaultHeapDumpListener() {
  return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}

HeapAnalyzerService.runAnalysis

HeapAnalyzerService.runAnalysis()The method starts itself, passing two parameters, the DisplayLeakServiceclass name and the one to analyze heapDump. After launching itself, process it onHandleIntentin .

/**
 * This service runs in a separate process to avoid slowing down the app process or making it run
 * out of memory.
 */
public final class HeapAnalyzerService extends IntentService {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

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

  public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName());
  }

  @Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
}

heapAnalyzer.checkForLeak

checkForLeakThe method mainly Squareuses another library haha ​​of the company to analyze Android heap dump, and calls back after getting the result DisplayLeakService.

AbstractAnalysisResultService.sendResultToListener

public static void sendResultToListener(Context context, String listenerServiceClassName,
    HeapDump heapDump, AnalysisResult result) {
  Class<?> listenerServiceClass;
  try {
    listenerServiceClass = Class.forName(listenerServiceClassName);
  } catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
  }
  Intent intent = new Intent(context, listenerServiceClass);
  intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
  intent.putExtra(RESULT_EXTRA, result);
  context.startService(intent);
}

onHandleIntentAlso processed in .

DisplayLeakService.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();
  }
}

DisplayLeakService.onHeapAnalyzed

onHeapAnalyzed()After calling , the hproffile will be deleted.

DisplayLeakService.java

@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
  String leakInfo = leakInfo(this, heapDump, result, true);
  CanaryLog.d("%s", 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);
}

According to the analysis results, calling the showNotification()method constructs a Notificationnotification to the developer of the memory leak.

public static void showNotification(Context context, CharSequence contentTitle,
    CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
  NotificationManager notificationManager =
      (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

  Notification notification;
  Notification.Builder builder = new Notification.Builder(context) //
      .setSmallIcon(R.drawable.leak_canary_notification)
      .setWhen(System.currentTimeMillis())
      .setContentTitle(contentTitle)
      .setContentText(contentText)
      .setAutoCancel(true)
      .setContentIntent(pendingIntent);
  if (SDK_INT >= O) {
    String channelName = context.getString(R.string.leak_canary_notification_channel);
    setupNotificationChannel(channelName, notificationManager, builder);
  }
  if (SDK_INT < JELLY_BEAN) {
    notification = builder.getNotification();
  } else {
    notification = builder.build();
  }
  notificationManager.notify(notificationId, notification);
}

DisplayLeakService.afterDefaultHandling

Finally, a method with an empty implementation is also executed afterDefaultHandling:

/**
 * You can override this method and do a blocking call to a server to upload the leak trace and
 * the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link
 * AnalysisResult#excludedLeak} first.
 */
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
}

You can override this method to perform some custom operations, such as uploading leaked stack information to the server.

In this way, LeakCanarythe whole process of memory leak detection is completed. It can be seen that LeakCanarythe design idea of ​​​​is very ingenious and clear, and there are many interesting knowledge points, such as the use of weak references ReferenceQueueand , IdleHandlerthe use of , the opening and closing of the four major components, etc., which are all worth exploring. .

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325049111&siteId=291194637