Detailed explanation of Leak Canary principle

Posted by dudeddy on Mon, 01 Jul 2019 20:13:20 +0200

Memory leak is one aspect of performance optimization that must be paid attention to. Leak Canary is an excellent tool for discovering memory leak. Today we will analyze how it works inside.

Let's start with a few questions:

  1. When LeakCanary is integrated and installed on mobile phones, you will find that there is an additional LeakCanary icon on the desktop. How does this icon come from?
  2. How can memory leaks be detected?

Let's start with the first question, which is relatively simple. When you call LeakCanary.install(this), click in the source code and you will see a line of code like this.

enableDisplayLeakActivity(application);

What this line of code ultimately executes is

public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) {
    ComponentName component = new ComponentName(appContext, componentClass);
    PackageManager packageManager = appContext.getPackageManager();
    int newState = enabled?1:2;
    packageManager.setComponentEnabledSetting(component, newState, 1);
}

The component Class here is DisplayLeakActivity.class. When declaring this Activity in the Android Manifest. XML file, set android:enabled="false" and then set it dynamically in the code, and a new icon will be generated on the desktop as the entry to the Activity.

Next, we will answer the second question, how to detect memory leaks, that is to say, how to detect activity finishes that have not yet been recycled. LeakCanary here uses the method of recording all activities, registering LeakCanary in Application by registerActivity Lifecycle Callbacks. Listen to Activity's life cycle, and then see if Activity is recycled when Activity executes onDestroy. If there is no recycling, trigger gc, and see if there is no recycling. If there is no recycling, then there is a memory leak, and collect the log information related to memory leak.
This is the general process, the specific details, how to detect whether Activity is recycled, how to trigger gc, look at the main code of Leak Canary implementation:

public void watch(Object watchedReference, String referenceName) {
    //...
    if(!this.debuggerControl.isDebuggerAttached()) {
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        this.retainedKeys.add(key);
        // watchedReference executes a reference to Activeness of onDrstroy, key is a random number, queue is a ReferenceQueue object, which is used to record the recycled object in the reference.
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
        this.watchExecutor.execute(new Runnable() {
            public void run() {
                // The method is finally executed here.
                RefWatcher.this.ensureGone(reference, watchStartNanoTime);
            }
        });
    }
}
// Reference is a weak reference
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // The operation done by this method is recycled and reference key is removed from the collection.
    // This method uses Reference.queue, which is taken out of the queue to show that it is recycled.
    this.removeWeaklyReachableReferences();
    if(!this.gone(reference) && !this.debuggerControl.isDebuggerAttached()) {
        // Go in if and say it hasn't been recycled yet
        // Trigger gc, where the way to trigger GC is to call Runtime.getRuntime().gc();
        this.gcTrigger.runGc();
        // remove the recovered key from the collection
        this.removeWeaklyReachableReferences();
        if(!this.gone(reference)) {
            // if it goes in, it means that it hasn't been recycled, and after gc, it hasn't been recycled, which means that it can't be recycled, that is, there is a memory leak.
            long startDumpHeap = System.nanoTime();
            long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
            // Collect hprof files
            File heapDumpFile = this.heapDumper.dumpHeap();
            if(heapDumpFile == HeapDumper.NO_DUMP) {
                return;
            }

            long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
            // Resolve the leak log, notify of leaks, and save it locally
            this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
        }

    }
}

Topics: Android Mobile xml