LeakCanary source code parsing

preface

As for memory leak detection, MAT has a high starting point, so we generally use LeakCanary as our memory leak detection tool.

Basic knowledge of

Four types of references

LeakCanary is implemented primarily by monitoring the recovery of activities and fragments that have been destroyed based on weak references.

  • Strong references: Will not be recycled under any circumstances.

  • Soft reference: Enough memory not to reclaim. When there is not enough memory, it is reclaimed.

  • Weak references: When garbage is collected, it is directly collected.

  • Virtual references: Garbage collection is directly collected.

ReferenceQueue (ReferenceQueue)

Both soft and weak references can be associated with a reference queue. When the referenced object is reclaimed, the soft reference is added to the reference queue associated with it. The basic implementation of LeakCanary is to place the corresponding instances of destroyed activities and fragments into weak references and associate a reference queue. If the instance is reclaimed, the weak reference is placed in the ReferenceQueue, and if the monitored instance does not appear in the ReferenceQueue after a certain period of time, then a memory leak has occurred that has prevented the instance from being reclaimed.

Method of use

Configuration:

dependencies {

  debugImplementation 'com. Squareup. Leakcanary: leakcanary - android: 1.6.3'

  releaseImplementation 'com. Squareup. Leakcanary: leakcanary - android - no - op: 1.6.3'

  // Optional, if you use support library fragments:

  debugImplementation 'com. Squareup. Leakcanary: leakcanary - support - fragments: 1.6.3'

}

Copy the code

Use:

public class ExampleApplication extends Application {

  @Override public void onCreate(a) {

    super.onCreate();

    LeakCanary.install(this);

  }

}

Copy the code

Leakcanary Works correctly

Analyze from a unique entry point in the program. This article is based on the 1.6.3 version of the source code analysis. The corresponding source code address is leakcanary-source.

Register instance monitoring

    public static @NonNull RefWatcher install(@NonNull Application application) {

        return refWatcher(application)// Create a constructor for reference monitoring to be used on the Android side

                .listenerServiceClass(DisplayLeakService.class)

                // Sets class reference objects that are not monitored

                .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())

                // Create a monitor for references

                .buildAndInstall();

    }

Copy the code

This method is short, so let’s break it down one by one.

Construct an AndroidRefWatcherBuilder object
Create an AndroidRefWatcherBuilder object

public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {   

    return new AndroidRefWatcherBuilder(context);   

}

Copy the code

The AndroidRefWatcherBuilder object created here is a constructor for reference monitoring on the Android side.

Set the listener class in the background
  //AndroidRefWatcherBuilder.java

  // Set up a class to listen for the results of the analysis.

  public @NonNull AndroidRefWatcherBuilder listenerServiceClass(@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {

    enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);

    // Set a listener

    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));

  }



  //RefWatcherBuilder.java

  //HeapDump listener

  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {

    this.heapDumpListener = heapDumpListener;

    return self();

  }

Copy the code

Here, the DisplayLeakService class serves as our final memory leak analyst and is capable of notifying memory leak messages (typically Notification).

References not included in monitoring

The excludedRefs method removes references that we don’t care about from our monitoring scope. This is done primarily because of some system-level referencing issues. We can take a look at what’s in there that we don’t need to pay attention to.

    // Since the Android AOSP itself may have memory leaks, these things are not alerted by default.

    public static @NonNull ExcludedRefs.Builder createAppDefaults(a) {

        // All enumerated types of AndroidExcludedRefs are taken into account.

        return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));

    }



    public static @NonNull ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {

        ExcludedRefs.Builder excluded = ExcludedRefs.builder();

        // Iterate over all enumerated types

        for (AndroidExcludedRefs ref : refs) {

            // If the enumeration type performs the exclusion of references

            if (ref.applies) {

                // Call the enumeration's add method, which places all reference classes to be excluded in and out of the excluede

                ref.add(excluded);

                ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());

            }

        }

        return excluded;

    }

Copy the code

This might be a little confusing, but let’s take a quick look at the AndroidExcludedRefs class.

public enum AndroidExcludedRefs {

    // parameter that identifies whether the add method needs to be executed

    final boolean applies;

    AndroidExcludedRefs() {

        this(true);

    }

    AndroidExcludedRefs(boolean applies) {

        this.applies = applies;

    }

    // Enumerate the methods the class needs to implement

    abstract void add(ExcludedRefs.Builder excluded);

}

Copy the code

AndroidExcludedRefs is an enumerated type. Contains member variables labelled as well as the add() method.

Let’s examine one more concrete enumeration type.

//AndroidExcludedRefs.java

    ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {

        @Override

        void add(ExcludedRefs.Builder excluded) {

            // Sets an attribute in the excluded class

            excluded.instanceField("android.app.ActivityThread$ActivityClientRecord"."nextIdle")

                    // Set the reason for exclusion

                    .reason("Android AOSP sometimes keeps a reference to a destroyed activity as a"

                            + " nextIdle client record in the android.app.ActivityThread.mActivities map."

                            + " Not sure what's going on there, input welcome.");

        }

    },

Copy the code

ACTIVITY_CLIENT_RECORD__NEXT_IDLE is a concrete enumeration type. Applies to SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP. There is also a concrete implementation of the Add method. The implementation adds the reference types that need to be excluded to Excluded.

So when SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP, we execute add.

AndroidExcludedRefs has different enumeration instances that are processed differently depending on the system version. The main point here is to ensure that some system-level memory leaks are no longer flagged.

Create a monitor for references

Let’s look directly at how an Activity that has performed onDestroy is monitored in buildAndInstall.

  // Return a RefWatcher object based on the Settings

  public @NonNull RefWatcher buildAndInstall(a) {

    if(LeakCanaryInternals.installedRefWatcher ! =null) {

      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");

    }

    // Create a RefWatcher object using the build() method in constructor mode, which has many default Settings

    RefWatcher refWatcher = build();

    if(refWatcher ! = DISABLED) {

      // If the memory leak Activity is allowed to be displayed, it is processed

      if (enableDisplayLeakActivity) {

        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);

      }

      // If the listener is set up, register a lifecycle listener for the Activity

      if (watchActivities) {

        ActivityRefWatcher.install(context, refWatcher);

      }

      // If a listening Fragment is set, register life cycle listening for the Fragment

      if (watchFragments) {

        FragmentRefWatcher.Helper.install(context, refWatcher);

      }

    }

    LeakCanaryInternals.installedRefWatcher = refWatcher;

    return refWatcher;

  }

Copy the code

Let’s look at how to monitor activities and fragments.

  1. Handling of activities
    //ActivityRefWatcher.java

    public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {

        Application application = (Application) context.getApplicationContext();

        // Create a weak reference listener class for the Activity

        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

        // Register an Activity lifecycle listener for the incoming Application

        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);

    }

Copy the code

Here to create a ActivityRefWatcher object, and then apply for, through registerActivityLifecycleCallbacks register a listener callback.

    //ActivityRefWatcher.java

    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =

            new ActivityLifecycleCallbacksAdapter() {

                // Only listen on the deStory method. Add the activity that calls destory to the listener watcher

                @Override

                public void onActivityDestroyed(Activity activity) {

                    refWatcher.watch(activity);

                }

            };

Copy the code

In this listener method, only the Activity’s onDestroy method is listened for. Use refWatcher to monitor instances when an Activity is destroyed.

  1. Handling of fragments
//FragmentRefWatcher.java

    public static void install(Context context, RefWatcher refWatcher) {

            List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

            // Add the two implementation classes that implement the FragmentRefWatcher interface to FragmentRefWatcher

            // Two implementation classes, one is for listening on the Fragment under V4 package, the other is for listening on the Fragment under V4 package

            if (SDK_INT >= O) {

                / / implementation class AndroidOFragmentRefWatcher

                fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));

            }



            try {

                / / implementation class SupportFragmentRefWatcher listens for V4 fragments under the package

                / / here using reflection, because SupportFragmentRefWatcher this class in support - fragments of this module.

                // So, if we hadn't introduced V4, we wouldn't have introduced this class.

Class<? > fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);

Constructor<? > constructor = fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);

                FragmentRefWatcher supportFragmentRefWatcher = (FragmentRefWatcher) constructor.newInstance(refWatcher);

                fragmentRefWatchers.add(supportFragmentRefWatcher);

            } catch (Exception ignored) {

            }

            // If there is no Fragment monitor, return directly

            if (fragmentRefWatchers.size() == 0) {

                return;

            }

            // Create a Helper instance

            Helper helper = new Helper(fragmentRefWatchers);

            Application application = (Application) context.getApplicationContext();

            // Register the Activity's lifecycle callback

            application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);

        }

Copy the code

Since we often use two types of fragments, one is the Fragment in the Support package and the other is the Fragment in the standard App package. So we’re dealing with both of these.

Let’s look at the lifecycle functions for registration

        private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =

                new ActivityLifecycleCallbacksAdapter() {

                    @Override

                    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

                        for (FragmentRefWatcher watcher : fragmentRefWatchers) {

                            // The watchFragments method of the concrete implementation class is called here. The concern here is the onCreate method of the bound Activity. At this point, the corresponding FragmentManager object has been created

                            / / and registerFragmentLifecycleCallbacks by FragmentManager object can be to create the Fragment lifecycle to monitor for its management

                            watcher.watchFragments(activity);

                        }

                    }

                };

Copy the code

Here we also register the Activity’s lifecycle callback. But what you’re monitoring here is the onActivityCreated method. Let’s look at the implementation of the watchFragments here.

Specific implementation, there are two classes, one is SupportFragmentRefWatcher, one is AndroidOFragmentRefWatcher. We’ll just analyze the first one here. The remaining Fragment is similar, but varies depending on the Fragment used.

    public void watchFragments(Activity activity) {

        // Fragments in V4 packages must be handled using FragmentActivity

        if (activity instanceof FragmentActivity) {

            FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();

            supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);

        }

    }



    private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =

            new FragmentManager.FragmentLifecycleCallbacks() {



                @Override

                public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {

                    View view = fragment.getView();

                    if(view ! =null) {

                        // Start monitoring when the fragment view is destroyed

                        refWatcher.watch(view);

                    }

                }



                @Override

                public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {

                    // Start monitoring when the fragment is destroyed

                    refWatcher.watch(fragment);

                }

            };

Copy the code

So here by getting the Activity of FragmentManager, for its management by registerFragmentLifecycleCallbacks fragments of life cycle for listening. When the Fragment performs destruction, its reference is added to the monitor queue.

So far, our activities and fragments have been monitored through refWatcher’s Watch.

Then, we next analyze how the watch method monitors instances and determines the existence of memory leaks.

monitoring

We will monitor the destroyed interface through the Watch method of refWatcher.

//RefWatcher.java

    public void watch(Object watchedReference) {

        // Override the method

        watch(watchedReference, "");

    }

    public void watch(Object watchedReference, String referenceName) {

        if (this == DISABLED) {

            return;

        }

        // Make sure the watch object is not empty

        checkNotNull(watchedReference, "watchedReference");

        checkNotNull(referenceName, "referenceName");

        final long watchStartNanoTime = System.nanoTime();

        // Create a UUID

        String key = UUID.randomUUID().toString();

        // Save the UUID to set

        retainedKeys.add(key);

        // Create a weak reference to the object to be detected.

        // If the weak reference is reclaimed, the reference will be added to the queue



        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);

        // Determine whether reference is reclaimed

        ensureGoneAsync(watchStartNanoTime, reference);

    }

Copy the code

There are three main operations performed in this

  • Create the UUID
  • Save the generated UUID to the retainedKeys queue.
  • Creates a weak reference, specifying the corresponding reference queue.

The retainedKeys queue here records the reference objects that we monitored. The queue holds the reclaimed references. So by comparing the two, we can find references to memory leaks.

Let’s take a look at how this process is performed in ensureGoneAsync.

    //RefWatcher.java

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

        watchExecutor.execute(new Retryable() {

            @Override

            public Retryable.Result run(a) {

                return ensureGone(reference, watchStartNanoTime);

            }

        });

    }

Copy the code

The watcheExecute here uses AndroidWatchExecutor

//AndroidRefWatcherBuilder.java

  @Override protected @NonNull WatchExecutor defaultWatchExecutor(a) {

    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);

  }

Copy the code

Let’s trace the execute method.

//AndroidWatchExecutor.java

  @Override public void execute(@NonNull Retryable retryable) {

    // If the current thread is the main thread, execute waitForIdl directly

    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

      waitForIdle(retryable, 0);

    } else {

      // If waitForIdle is not in the main thread, it is put into the main thread by the Handler mechanism

      postWaitForIdle(retryable, 0);

    }

  }



  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {

    // Use the Handler mechanism to send waitForIdle to the main thread for execution

    mainHandler.post(new Runnable() {

      @Override public void run(a) {

        waitForIdle(retryable, failedAttempts);

      }

    });

  }



  private void waitForIdle(final Retryable retryable, final int failedAttempts) {

    // Add a handler when messagequeue is idle. This approach is primarily for performance and does not affect our normal application fluency

    / / this method can be carried in the main thread, so postToBackgroundWithDelay are the main thread of execution

    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

      @Override public boolean queueIdle(a) {

        postToBackgroundWithDelay(retryable, failedAttempts);

        return false;

      }

    });

  }

Copy the code

So it can eventually performs postToBackgroundWithDelay method in the main thread.

  private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {

    // Calculate the compensation factor. If a retry is returned, the failedAttempts will increase, increasing the execution delay of the method.

    For example, if the first execution is RETRY in 5 seconds, the next execution will be delayed by 10 seconds

    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);

    // Calculate the delay time

    long delayMillis = initialDelayMillis * exponentialBackoffFactor;

    //backgroundHandler will execute the code in the run method in a new thread.

    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

The execution of this method delays the execution of the corresponding run method based on the number of times it executes.

Let’s look at the execution of the retryable. Run () method. Which brings us back to the ensureGoneAsync method in our RefWatcher.

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

  watchExecutor.execute(new Retryable() {

    @Override public Retryable.Result run(a) {

      return ensureGone(reference, watchStartNanoTime);

    }

  });

}

Copy the code

The ensureGone method here is really the core of our code.

    // Determine whether reference is reclaimed

    @SuppressWarnings("ReferenceEquality")

    // Explicitly checking for named null.

    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {

        long gcStartNanoTime = System.nanoTime();

        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

        // Remove reclaimed monitored objects

        removeWeaklyReachableReferences();

        // If the current state is debug, retry is returned

        if (debuggerControl.isDebuggerAttached()) {

            // The debugger can create false leaks.

            return RETRY;

        }

        // The monitoring object has been reclaimed

        if (gone(reference)) {

            return DONE;

        }

        // Perform a garbage collection

        gcTrigger.runGc();

        // Remove the reclaimed monitored object

        removeWeaklyReachableReferences();

        if(! gone(reference)) {

            // If there is still no collection, a memory leak has occurred

            long startDumpHeap = System.nanoTime();

            // The duration of gc execution

            long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

            //dump hprof file

            File heapDumpFile = heapDumper.dumpHeap();

            if (heapDumpFile == RETRY_LATER) {

                // Could not dump the heap.

                // If the snapshot file cannot be generated, try again

                return RETRY;

            }

            // The time spent generating the hprof file

            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 is ServiceHeapDumpListener by default

            heapdumpListener.analyze(heapDump);

        }

        return DONE;

    }

Copy the code

This code performs several procedures

  1. Example Remove reclaimed monitored objects
  2. If the currently monitored object has been reclaimed, return DONE.
  3. If there is no collection, a GC operation is forced.
  4. Remove reclaimed monitored objects again.
  5. If the current monitored object is still not reclaimed, the hprof file is dumped and the memory leak is analyzed based on the snapshot file.

Here we analyze each method one by one

Removes a reclaimed weak reference object
    private void removeWeaklyReachableReferences(a) {

        KeyedWeakReference ref;

        / / circular queue

        while((ref = (KeyedWeakReference) queue.poll()) ! =null) {

            // The ref in the queue has been reclaimed, so remove the corresponding key from the retainedKeys.

            retainedKeys.remove(ref.key);

        }

    }

Copy the code

The queue here is the reference queue we mentioned, and the retainedKeys hold the objects we want to monitor. When objects are reclaimed, weak references are stored in the queue, so we remove weak references from the queue from the retainedKeys. All that’s left is objects that we’re listening for or that have memory leaks.

Check whether the monitored object is reclaimed
    // Check whether the monitored object is reclaimed true: The monitored object is reclaimed

    private boolean gone(KeyedWeakReference reference) {

        return! retainedKeys.contains(reference.key);

    }

Copy the code

In the previous step, we removed the reclaimed reference information from the retainedKeys, so we can simply determine if the class we are monitoring is present in the set.

Export the. Hprof file
  public File dumpHeap(a) {

    // Create an.hrof file

    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

    if (heapDumpFile == RETRY_LATER) {

      // Failed to create, try again later

      return RETRY_LATER;

    }

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

    // Toasts are displayed on the main thread through the Handler mechanism, using CountDownLatch. [Fixed] Toasts are displayed with a value of 0,

    showToast(waitingForToast);

    // Wait for the main thread to display a Toast (CountDownLatch becomes 0). Then you can continue

    if(! waitingForToast.wait(5, SECONDS)) {

      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");

      return RETRY_LATER;

    }

    // Create a Notification Notification

    Notification.Builder builder = new Notification.Builder(context)

        .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));

    Notification notification = LeakCanaryInternals.buildNotification(context, builder);

    NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

    int notificationId = (int) SystemClock.uptimeMillis();

    notificationManager.notify(notificationId, notification);



    Toast toast = waitingForToast.get();

    try {

      // Create a snapshot of the heap to see which parts of the program are using most of the memory

      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());

      // Turn off Toask and Notification notifications

      cancelToast(toast);

      notificationManager.cancel(notificationId);

      return heapDumpFile;

    } catch (Exception e) {

      CanaryLog.d(e, "Could not dump heap");

      // Abort heap dump

      return RETRY_LATER;

    }

  }

Copy the code

A.hprof file will be created, and a Toast and Notification Notification will be displayed, and the snapshot information of the heap during the memory leak will be saved in the.hprof file, and the Toast and Notification notifications will be closed. So after doing this, the.hprof file we generated contains information about the heap at the time of the memory leak.

Snapshot File Analysis

After a snapshot file is generated, heapdumpListener is used to analyze the generated snapshot file. The default listener is the ServiceHeapDumpListener class

  //AndroidRefWatcherBuilder.java

  @Override protected @NonNull HeapDump.Listener defaultHeapDumpListener(a) {

    return new ServiceHeapDumpListener(context, DisplayLeakService.class);

  }

Copy the code

Let’s look at its analyze method

  //ServiceHeapDumpListener.java

   public void analyze(@NonNull HeapDump heapDump) {

    checkNotNull(heapDump, "heapDump");

    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);

  }

  //HeapAnalyzerService.java

  public static void runAnalysis(Context context, HeapDump heapDump,

      Class<? extends AbstractAnalysisResultService> listenerServiceClass)
 
{

    setEnabledBlocking(context, HeapAnalyzerService.class, true);

    setEnabledBlocking(context, listenerServiceClass, true);

    Intent intent = new Intent(context, HeapAnalyzerService.class);

    // listenerServiceClass is DisplayLeakService

    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());

    intent.putExtra(HEAPDUMP_EXTRA, heapDump);

    // Start a foreground service. When started, the onHandleIntent method is called, which is implemented in the parent class. Implementation will call onHandleIntentInForeground () method

    ContextCompat.startForegroundService(context, intent);

  }

Copy the code

A service is started to analyze files. The onHandleIntent method is called when the service is started. The onHandleIntent of HeapAnalyzerService is implemented in its parent class.

//ForegroundService.java

@Override protected void onHandleIntent(@Nullable Intent intent) {

  onHandleIntentInForeground(intent);

}

Copy the code

So will call onHandleIntentInForeground this method.

    protected void onHandleIntentInForeground(@Nullable Intent intent) {

        String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);

        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

        // Create a heap parser

        HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

        //** focus analysis method *** analysis of memory leak results

        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);

        // Call the interface and call the result back to the corresponding class of listenerClassName (in this case, DisplayLeakService) for processing

        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);

    }

Copy the code

Here will create a heap profiler, for us a snapshot of the file is analyzed, then the result by the method of AbstractAnalysisResultService results to DisplayLeakService class for processing.

Detection of leak results

The main purpose of the HeapAnalyzer class is to detect memory leaks in the objects we monitor by analyzing.hprof files

//HeapAnalyzer.java

// Parse the hprof file into the corresponding AnalysisResult object

public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, @NonNull String referenceKey, boolean computeRetainedSize) {

    long analysisStartNanoTime = System.nanoTime();



    if(! heapDumpFile.exists()) {

        Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);

        return failure(exception, since(analysisStartNanoTime));

    }



    try {

        // Start reading Dump file

        listener.onProgressUpdate(READING_HEAP_DUMP_FILE);

        HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);

        // The.hprof parser, which is the haha library class

        HprofParser parser = new HprofParser(buffer);

        listener.onProgressUpdate(PARSING_HEAP_DUMP);

        // Parse to generate a snapshot, which contains information about all referenced objects

        Snapshot snapshot = parser.parse();

        listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);

        deduplicateGcRoots(snapshot);

        listener.onProgressUpdate(FINDING_LEAKING_REF);

        // According to the key value, check whether the snapshot has the required object

        Instance leakingRef = findLeakingReference(referenceKey, snapshot);

        if (leakingRef == null) {

            // Indicates that the object does not exist and was collected during gc. Indicates that there are no memory leaks

            String className = leakingRef.getClassObj().getClassName();

            return noLeak(className, since(analysisStartNanoTime));

        }

        // Detect the path of the leak and return the detected result

        return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);

    } catch (Throwable e) {

        return failure(e, since(analysisStartNanoTime));

    }

}

Copy the code

This method uses the HAHA tripartite library to parse and process.hprof files. The main process is as follows:

  1. Create a buffer from the.hprof file to read the file
  2. The HprofParser is used to parse the hprof file and generate the Snapshot object. In this step, we build a reference relationship tree of the Object. We can query the information of each Object in this tree, including the Class information, memory address, held references, and the relationship of held references.
  3. Obtain the corresponding reference leakingRef in the Snapshot based on the key value of the incoming monitored object.
  4. Analysis leakingRef to obtain the path of the memory leak. A shortest reference path to the leak object will be found. This is accomplished by findLeakTrace, in fact, to find the shortest reference path logic is encapsulated in PathsFromGCRootsComputerImpl getNextShortestPath and processCurrentReferrefs method
Notification of a leak

When find our memory leak paths, invoked AbstractAnalysisResultService. SendResultToListener results to DisplayLeakService class for processing.

//AbstractAnalysisResultService.java

public static void sendResultToListener(@NonNull Context context,

    @NonNull String listenerServiceClassName,

    @NonNull HeapDump heapDump,

    @NonNull AnalysisResult result)
 
{

Class<? > listenerServiceClass;

  try {

    // Get a class information by reflection

    listenerServiceClass = Class.forName(listenerServiceClassName);

  } catch (ClassNotFoundException e) {

    throw new RuntimeException(e);

  }

  Intent intent = new Intent(context, listenerServiceClass);

  // Save the result to a file and pass the file path to the service

  File analyzedHeapFile = AnalyzedHeap.save(heapDump, result);

  if(analyzedHeapFile ! =null) {

    intent.putExtra(ANALYZED_HEAP_PATH_EXTRA, analyzedHeapFile.getAbsolutePath());

  }

  // Start the service, and then pass the location of the result file of the memory leak analysis

  ContextCompat.startForegroundService(context, intent);

}

Copy the code

A DisplayLeakService is started, passing along the file path information for the memory leak analysis results.

Then through onHandleIntent () – > onHandleIntentInForeground () – > onHeapAnalyzed (). The onHeapAnalyzed method of DisplayLeakService was eventually called

protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {

    HeapDump heapDump = analyzedHeap.heapDump;

    AnalysisResult result = analyzedHeap.result;

    // Generate a prompt String based on the leaked information

    String leakInfo = leakInfo(this, heapDump, result, true);

    CanaryLog.d("%s", leakInfo);

    // Rename the.hprof file

    heapDump = renameHeapdump(heapDump);

    // Save the analysis results

    boolean resultSaved = saveResult(heapDump, result);

    // Result header

    String contentTitle;

    if (resultSaved) {

        PendingIntent pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

        if(result.failure ! =null) {

            // Analysis failed

            contentTitle = getString(R.string.leak_canary_analysis_failed);

        } else {

            String className = classSimpleName(result.className);

            if (result.leakFound) {// Memory leak detected

                if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {

                    if (result.excludedLeak) {// Excluded test results

                        contentTitle = getString(R.string.leak_canary_leak_excluded, className);

                    } else {

                        contentTitle = getString(R.string.leak_canary_class_has_leaked, className);

                    }

                } else {

                    String size = formatShortFileSize(this, result.retainedHeapSize);

                    if (result.excludedLeak) {

                        contentTitle = getString(R.string.leak_canary_leak_excluded_retaining, className, size);

                    } else {

                        contentTitle = getString(R.string.leak_canary_class_has_leaked_retaining, className, size);

                    }

                }

            } else {

                // No memory leak detected

                contentTitle = getString(R.string.leak_canary_class_no_leak, className);

            }

        }

        String contentText = getString(R.string.leak_canary_notification_message);

        //*** * key method *** display a Notification

        showNotification(pendingIntent, contentTitle, contentText);

    } else {

        onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));

    }

    afterDefaultHandling(heapDump, result, leakInfo);

}

Copy the code

The function of this service is to inform users of the specific memory leak situation by notifying them of the information related to the leak path after analysis.

At this point, the entire implementation process parsing for LeakCanary is complete.

New knowledge learned

The whole study, or learned some things that did not realize before.

  1. Mainly through registerActivityLifecycleCallbacks to register the destruction of the Activity of listening in to us.
  2. Weak reference queues are used to monitor the Activity references that we have destroyed to see if they are recycled.
  3. For garbage collection, runtime.getruntime ().gc() is used.
  4. You can use CountDownLatch for synchronization between threads. For example, this set of source code for showToast processing.
  5. Different versions of Android may have some memory leaks of their own.

Source code: Leakcanary -source

This article is published by Kaiken!

Synchronous public number [open Ken]

image-20200404120045271