preface

LeakCanary is a small tool provided by Square for Android to detect memory. It can help us quickly locate code hidden bugs and reduce the chance of OOM.

Here is the git address link: github.com/square/leak…

Side note :Square is really a conscience company, offering a lot of well-known components. The following will sort out the well-known components on the market. Like Facebook’s open source components… Let’s take a look at what open source components Square has

OKHttp is an open source stable Http communication dependency library that feels better than HttpUrlConnection. OKHttp is now officially approved by Google. Okie OKHttp relies on the library dagger fast dependency injection framework. It is now maintained by Google and should be dagger2. Official website: HTTPS://google.github.io/dagger/Retrofit is an encapsulation of a RESTFUL Http web request framework based on OKHttp. Retrofit is designed to encapsulate interfaces. The otto Android EventBus is the same as the otto Android EventBus, which reduces the code coupling and can be compared to EventBus.Copy the code

Back to the main text, now to the use of LeakCanary

Using LeakCanary

In fact, you can refer to the Sample of Leakcanary

  1. The first reference is in build.gradle
dependencies { compile fileTree(dir: 'libs', include: [' *. Jar ']) androidTestCompile (' com. Android. Support. The test. The espresso: espresso - core: 2.2.2 ', {exclude group: 'com.android.support', module: 'the support - annotations'}) compile' com. Android. Support: appcompat - v7:25.2.0 'compile 'com. Android. Support. The constraint, the constraint - layout: 1.0.0 - alpha9' testCompile junit: junit: '4.12' / / LeakCanary DebugCompile 'com. Squareup. Leakcanary: leakcanary - android: 1.5' releaseCompile 'com. Squareup. Leakcanary: leakcanary - android - no - op: 1.5' testCompile 'com. Squareup. Leakcanary: leakcanary - android - no - op: 1.5'}Copy the code
  1. Add a method to Application onCreate
    public class ExampleApp extends Application{
    @Override
    public void onCreate(a) {
        super.onCreate();
        // LeakCanary is initialized
        LeakCanary.install(this); }}Copy the code
  1. Systemclock. sleep(20000);
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Button asynTaskBtn = (Button) this.findViewById(R.id.async_task);
        asynTaskBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) { startAsyncTask(); }}); }private void startAsyncTask(a) {
        // This async task is an anonymous class and therefore has a hidden reference to the outer
        // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
        // the activity instance will leak.
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                // Do some slow work in background
                SystemClock.sleep(20000);
                return null; } }.execute(); }}Copy the code
  1. Run and you will see an icon for LeakCanary. We’ll explain how this icon appears later. When a memory leak occurs, a memory leak notification is displayed in the notification bar. Clicking on the notification will take you to the specific problem.





    LeakCanary icon





The notification bar shows a memory leak





Memory Leak Details

According to the icon, we can see the memory leak in the AsyncTask, according to the AsyncTask memory modification

Having explained how to use this, let’s start with LeakCanary.

LeakCanary,

Code directory structure

. ├ ─ ─ AbstractAnalysisResultService. Java ├ ─ ─ ActivityRefWatcher. Java - Activity monitoring, Monitoring its lifecycle ├ ─ ─ AndroidDebuggerControl. Java, Android Debug control switch, Is to determine the Debug. IsDebuggerConnected () ├ ─ ─ AndroidExcludedRefs. Java memory leak - base class ├ ─ ─ AndroidHeapDumper. Java - generated. Hrpof class ├ ─ ─ AndroidWatchExecutor. Java -- -- Android monitoring thread, delay5S executive ├ ─ ─ DisplayLeakService. Java -- -- show notification bar memory leaks, implements the AbstractAnalysisResultService. Java ├ ─ ─ LeakCanary. Java - foreign class, provide the install (this) method ├ ─ ─ ServiceHeapDumpListener. Java └ ─ ─ internal - this folder is used to display the memory leak (interface) related to the condition of ├ ─ ─ DisplayLeakActivity. Java - memory leak show Activity ├ ─ ─ DisplayLeakAdapter. Java ├ ─ ─ DisplayLeakConnectorView. Java ├ ─ ─ FutureResult. Java ├ ─ ─ Java A Service started in another process to receive data and send it to the interface ├─ LeakCanaryInternals. Java ├─ leakcanaryui.java ├─ MoreDetailsView.javaCopy the code

External method LeakCanary. Install (this)

In fact, LeakCanary provides only one method

LeakCanary.install(this);Copy the code

Start from here, corresponding source code

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

/**
 * Creates a {@link RefWatcher} that reports results to the provided service, and starts watching
 * activity references (on ICS+).
 */
public static RefWatcher install(Application application,
                                 Class<? extends AbstractAnalysisResultService> listenerServiceClass.ExcludedRefs excludedRefs) {
    // Determine whether the Analyzer is in process
    if (isInAnalyzerProcess(application)) {
        return RefWatcher.DISABLED;
    }
    // Allows memory leaks to be displayed
    enableDisplayLeakActivity(application);

    HeapDump.Listener heapDumpListener =
            new ServiceHeapDumpListener(application, listenerServiceClass);

    RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
    ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
    return refWatcher;
}Copy the code

Why does LeakCanary require above 4.0

<mark> You can see from the comments that this LeakCanary is used for methods above 4.0

references (on ICS+).Copy the code

Why use above 4.0?

ActivityRefWatcher.installOnIcsPlus(application, refWatcher);Copy the code

The ActivityRefWatcher class is used for Ics+(version 4.0 and above)

@TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher {

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

  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); }};private final Application application;
  private final RefWatcher refWatcher;

  /**
   * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
   * after they have been destroyed.
   */
  public ActivityRefWatcher(Application application, final RefWatcher refWatcher) {
    this.application = checkNotNull(application, "application");
    this.refWatcher = checkNotNull(refWatcher, "refWatcher");
  }

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

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

  public void stopWatchingActivities(a) { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); }}Copy the code

application.registerActivityLifecycleCallbacks(lifecycleCallbacks); This method is used on Android4.0 to observe the Activity lifecycle. As you can see from the code above, LeakCanary listens for the Activity’s destruction operation

  ActivityRefWatcher.this.onActivityDestroyed(activity);Copy the code

How does LeakCanary show the LeakCanry icon

public static void setEnabled(Context context, finalClass<? > componentClass,final boolean enabled) {
        final Context appContext = context.getApplicationContext();
        // Time consuming operation
        executeOnFileIoThread(new Runnable() {
            @Override
            public void run(a) {
                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); }}); }Copy the code

Called when install’s method executes

 // Allows memory leaks to be displayed
enableDisplayLeakActivity(application);Copy the code

This method performs the setEnable shown above method. The core of the method is packageManager setComponentEnabledSetting. This method can be used to hide/show application icon Specific can consult android setComponentEnabledSetting disable or open four components

[Key point] LeakCanary How do I catch a memory leak

Use the debug.dumphprofData () method to generate a. Hprof file, parse the. Hprof file using the open-source library HAHA(open source address :github.com/square/haha), and send it to DisplayLeakActivity for display

public final class AndroidHeapDumper implements HeapDumper {

  private static final String TAG = "AndroidHeapDumper";

  private final Context context;
  private final Handler mainHandler;

  public AndroidHeapDumper(Context context) {
    this.context = context.getApplicationContext();
    mainHandler = new Handler(Looper.getMainLooper());
  }

  @Override public File dumpHeap(a) {
    if(! isExternalStorageWritable()) { Log.d(TAG,"Could not dump heap, external storage not mounted.");
    }
    File heapDumpFile = getHeapDumpFile();
    if (heapDumpFile.exists()) {
      Log.d(TAG, "Could not dump heap, previous analysis still is in progress.");
      // Heap analysis in progress, let's not put too much pressure on the device.
      return NO_DUMP;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);

    if(! waitingForToast.wait(5, SECONDS)) {
      Log.d(TAG, "Did not dump heap, too much time waiting for Toast.");
      return NO_DUMP;
    }

    Toast toast = waitingForToast.get();
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      cancelToast(toast);
      return heapDumpFile;
    } catch (IOException e) {
      cleanup();
      Log.e(TAG, "Could not perform heap dump", e);
      // Abort heap dump
      returnNO_DUMP; }}/** * Call this on app startup to clean up all heap dump files that had not been handled yet when * the app process was killed. */
  public void cleanup(a) {
    LeakCanaryInternals.executeOnFileIoThread(new Runnable() {
      @Override public void run(a) {
        if (isExternalStorageWritable()) {
          Log.d(TAG, "Could not attempt cleanup, external storage not mounted.");
        }
        File heapDumpFile = getHeapDumpFile();
        if (heapDumpFile.exists()) {
          Log.d(TAG, "Previous analysis did not complete correctly, cleaning: "+ heapDumpFile); heapDumpFile.delete(); }}}); }private File getHeapDumpFile(a) {
    return new File(storageDirectory(), "suspected_leak_heapdump.hprof");
  }

  private void showToast(final FutureResult<Toast> waitingForToast) {
    mainHandler.post(new Runnable() {
      @Override public void run(a) {
        final Toast toast = new Toast(context);
        toast.setGravity(Gravity.CENTER_VERTICAL, 0.0);
        toast.setDuration(Toast.LENGTH_LONG);
        LayoutInflater inflater = LayoutInflater.from(context);
        toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
        toast.show();
        // Waiting for Idle to make sure Toast gets rendered.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle(a) {
            waitingForToast.set(toast);
            return false; }}); }}); }private void cancelToast(final Toast toast) {
    mainHandler.post(new Runnable() {
      @Override public void run(a) { toast.cancel(); }}); }}Copy the code

Testing time

The refwatch. watch method is executed when the Activity is destroyed, and then memory checks are performed

The IdleHandler principle is to hook the user when the messageQueue is idle for a message. AndroidWatchExecutor will send out a background task when the main thread is idle, which will be executed after DELAY_MILLIS time. LeakCanary is set to 5 seconds.

public void watch(Object watchedReference, String referenceName) {
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    if (debuggerControl.isDebuggerAttached()) {
      return;
    }
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    watchExecutor.execute(new Runnable() {
      @Override public void run(a) { ensureGone(reference, watchStartNanoTime); }}); }Copy the code
public final class AndroidWatchExecutor implements Executor {

  static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
  private static final int DELAY_MILLIS = 5000;

  private final Handler mainHandler;
  private final Handler backgroundHandler;

  public AndroidWatchExecutor(a) {
    mainHandler = new Handler(Looper.getMainLooper());
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper());
  }

  @Override public void execute(final Runnable command) {
    if (isOnMainThread()) {
      executeDelayedAfterIdleUnsafe(command);
    } else {
      mainHandler.post(new Runnable() {
        @Override public void run(a) { executeDelayedAfterIdleUnsafe(command); }}); }}private boolean isOnMainThread(a) {
    return Looper.getMainLooper().getThread() == Thread.currentThread();
  }

  private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle(a) {
        backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
        return false; }}); }}Copy the code

How does Fragment use LeakCanary

If we want to detect the Fragment’s memory, we can save the returned RefWatcher in the Application. We can watch it in the Fragment’s onDestroy.

public abstract class BaseFragment extends Fragment {

  @Override public void onDestroy(a) {
    super.onDestroy();
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(this); }}Copy the code

See the LeakCanary open source project

Other references

LeakCanary Memory leak monitoring principle

Android memory leak check tool LeakCanary