Article 4 – Leakcanary source code analysis. (Source code to version 1.6.1 prevail)

A simple word, but also have more than two months didn’t write the article, although there is no continue to see the source code, among this some performance optimization but have learned knowledge, due to the basic it is through the study of video, blog, etc, but also own notes are following video and blog in the process of learning, so there is no written articles posted. Interested partners can take a look: github.com/Endless5F/J…

The use of LeakCanary

Private static void initLeakCanary(Application sApplication) {// LeakCanary Initialization (Memory leak detection)if (LeakCanary.isInAnalyzerProcess(sApplication)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(sApplication);
    }
Copy the code

Brief analysis of related knowledge points and related classes

  1. ReferenceQueue: The garbage collector adds the registered reference object to this queue when it detects a change in the object’s reachability at an appropriate time. The following code uses a weak reference to connect to the object you want to detect, and then uses ReferenceQueue to monitor changes in the reachability of this weak reference

  2. Four types of reference:

    • StrongReference StrongReference: strongly referenced objects are not collected by gc
    • SoftReference SoftReference: if the physical memory is sufficient, it will not be reclaimed by the gc; if the physical memory is insufficient, it will be reclaimed by the gc.
    • WeakReference WeakReference: once detected by gc, it will be reclaimed
    • PhantomReference Virtual reference: does not affect the life cycle of the object; it is virtually nothing and can be reclaimed by the GC at any time
    • FinalReference: Used for finalization

    When the GC thread scans the memory area under its jurisdiction, once it finds an object with only weak references, it reclaims its memory regardless of whether the current memory space is sufficient or not. Because the garbage collector is a low-priority thread, objects that have only weak references are not necessarily found quickly.

    Except for strong references, the other three references inherit from the Reference class. Reference provides two internal constructors, one with queue and one without queue. The whole point of a queue is that we can monitor it externally. If an object is to be reclaimed, the reference object will be placed in this queue. When we get reference, we can do some transactions and perform our own operations (depending on how you want to use them), so the queue acts as a notification when an object is reclaimed. If the ReferenceQueue is not attached, the only way to know whether the object held by Reference is reclaimed is to constantly rotate the Reference object and determine whether the get inside it returns null. Its GET always returns null, so it only has a constructor with queue). Both methods have their own usage scenarios, depending on the actual application. For example, weakHashMap chooses to query the data of queue to determine whether any object will be recovered. ThreadLocalMap, on the other hand, checks whether get() is null.

    Reference classifies memory into four states: Active, Pending, Enqueued, and Inactive.

    • Active Generally, memory is initially allocated in the Active state
    • Pending Objects that are about to be placed on the ReferenceQueue
    • The Enqueued object has been queued and has been reclaimed. This allows us to query whether an object is reclaimed
    • Inactive is the final state that cannot be changed to any other state.

    LeakCanary is a combination of WeakReference and ReferenceQueue. If the object referenced by a WeakReference is garbage collected, the Java virtual machine will add the WeakReference to the ReferenceQueue associated with it.

  3. ActivityRefWatcher: Used to monitor activities, But can only be used for Android 4.0 and above It will through watchActivities method of global Activity lifecycle callback interface Application. ActivityLifecycleCallbacks registered to the Application

  4. RefWatcher: Function LeakCanary Core in core. The RefWatcher’s job is to trigger the GC, and if the object is reclaimed, WeakReference will be put into the ReferenceQueue, otherwise a leak is suspected (just suspected), and then the memory is dumped for further analysis.

  5. ExcludedRef: LeakCanary provides ExcludedRefs to flexibly control whether objects need to be excluded from consideration, because in the Android Framework, the phone vendor ROM itself has some memory leaks that developers can’t do anything about. So AndroidExcludedRefs defines a number of classes that exclude consideration

  6. Listener and ServiceHeapDumpListener: ServiceHeapDumpListener implements the Heapdumplistener interface. When RefWatcher detects a suspicious reference, it passes the dumped Hprof file to the HeapAnalyzerService via the listener.

  7. HeapAnalyzerService: mainly through HeapAnalyzer checkForLeak analysis object references to calculate the shortest strong reference to the GC root path. The analysis results are then passed to DisplayLeakService.

  8. AbstractAnalysisResultService and DisplayLeakService: DisplayLeakService inherited AbstractAnalysisResultService. It is mainly used to process the results of the analysis, write the results to a file, and then alert the notification bar.

  9. Heap Dump: A Heap Dump, also called a Heap Dump, is a snapshot of the memory of a Java process at a point in time.

LeakCanary source code is in depth

LeakCanary is very convenient to use, but everything is actually done in the source code.

1. LeakCanary.install(sApplication)

    public static RefWatcher install(Application application) {
    returnRefWatcher (application) // Get AndroidRefWatcherBuilder // set the Class of the listening service (note: ListenerServiceClass (displayLeakService.class) // LeakCanary provides ExcludedRefs to flexibly control whether some objects need to be excluded from consideration, // Because there are some memory leaks in the Android Framework, there is nothing we can do about these leaks. So many rule out class defined in AndroidExcludedRefs. ExcludedRefs (AndroidExcludedRefs. CreateAppDefaults (). The build ()) / / build and loading .buildAndInstall(); }Copy the code

Let’s briefly mention a few points in the system method:

  1. The RefWatcher returned by this static method is configured using the AndroidRefWatcherBuilder constructor class
  2. The listenerServiceClass method is bound to the leak analysis related service binding to the DisplayLeakService.class class, which analyzes and notifies the developer of leak messages
  3. The excludedRefs method eliminates leaks (typically system-level bugs) that can be ignored by development. These enumerations are defined in the AndroidExcludedRefs class
  4. The buildAndInstall method is the focus of the real build

2. buildAndInstall()

// AndroidRefWatcherBuilder class: // Whether to observe the Activity memory leak private Boolean watchActivities =true; // Whether to observe fragments for memory leaks private Boolean watchFragments =true;
    
    public RefWatcher buildAndInstall() {// LeakCanaryInternals class is LeakCanary some similar to the logic of the utility class etc. (all static methods)if(LeakCanaryInternals.installedRefWatcher ! = null) {// installedRefWatcher is used to save whether the LeakCanary has been built and installed // If the LeakCanary has been built and installed repeatedly, Will put the following abnormal throw new UnsupportedOperationException ("buildAndInstall() should only be called once" +"."); RefWatcher = build(); RefWatcher = build();if(refWatcher ! = DISABLED) {// Defaults totrue
            if(watchActivities) { ActivityRefWatcher.install(context, refWatcher); } // The default istrue
            if (watchFragments) {
                FragmentRefWatcher.Helper.install(context, refWatcher);
            }
        }
        LeakCanaryInternals.installedRefWatcher = refWatcher;
        return refWatcher;
    }
Copy the code

Can be seen from the code buildAndInstall method will build part to build () method, install part use ActivityRefWatcher and FragmentRefWatcher Helper.

1). The build section

// RefWatcherBuilder class, this class is the parent of AndroidRefWatcherBuilder class // Most of this method is to get the default value public final RefWatcherbuild() {
        if (isDisabled()) {
            returnRefWatcher.DISABLED; } // Not null hereif(heapDumpBuilder.excludedRefs == null) { heapDumpBuilder.excludedRefs(defaultExcludedRefs()); } heapDumpListener = this.heapDumpListener; heapDumpListener = this.heapDumpListener;if(heapDumpListener == null) { heapDumpListener = defaultHeapDumpListener(); } // The default value is null. // This parameter is used to check whether memory leak detection is not performed during debugging. DebuggerControl debuggerControl = this.debuggerControl;if(debuggerControl == null) { debuggerControl = defaultDebuggerControl(); } // Dump files for memory leak analysis. Dump the memory head. HeapDumper heapDumper = this.heapDumper;if(heapDumper == null) { heapDumper = defaultHeapDumper(); } // Executor WatchExecutor WatchExecutor = this. WatchExecutor;if(watchExecutor == null) { watchExecutor = defaultWatchExecutor(); } // GC switch, call system GC. GcTrigger gcTrigger = this.gcTrigger;if (gcTrigger == null) {
            gcTrigger = defaultGcTrigger();
        }

        if(heapDumpBuilder.reachabilityInspectorClasses == null) { heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses()); } // Initialize the RefWatcher objectreturn new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper,
                heapDumpListener,
                heapDumpBuilder);
    }
Copy the code

Let’s take a look at a few variables of the build() method:

  1. HeapDumpBuilder. ExcludedRefs: this variable is actually LeakCanary. Install method through excludedRefs Settings, used in the filtration system of some memory leaks.

  2. Enclosing heapDumpListener: this variable is actually with heapDumpBuilder excludedRefs, is set up through listenerServiceClass method. Let’s take a quick look at the source code:

    AndroidRefWatcherBuilder Class: public AndroidRefWatcherBuilder listenerServiceClass(Class<? extends AbstractAnalysisResultService> listenerServiceClass) { return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass)); } // 2. ServiceHeapDumpListener Class: public ServiceHeapDumpListener(final Context Context, final Class<? extends AbstractAnalysisResultService> listenerServiceClass) { this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass"); this.context = checkNotNull(context, "context").getApplicationContext(); } // 3. RefWatcherBuilder: Public final T heapDumpListener(heapDumpListener) {public final T heapDumpListener(heapDumpListener) {public final T heapDumpListener(heapDumpListener) this.heapDumpListener = heapDumpListener; return self(); }Copy the code

    So a heapDumpListener is actually a ServiceHeapDumpListener object.

  3. Others: All other variables are null by default, so the default values are set.

Part 2). Install

We have illustrated ActivityRefWatcher as an example:

/ / ActivityRefWatcher class: // Initialize ActivityRefWatcher via the provided static method and register the callback. RefWatcher refWatcher) { Application application = (Application) context.getApplicationContext(); // Initialize ActivityRefWatcher, ActivityRefWatcher = new ActivityRefWatcher(Application, RefWatcher); / / register the Activity's lifecycle callback application. RegisterActivityLifecycleCallbacks (activityRefWatcher. LifecycleCallbacks); } / / lifecycle callback private final Application. ActivityLifecycleCallbacks lifecycleCallbacks = newActivityLifecycleCallbacksAdapter() {@override public void onActivityDestroyed(Activity Activity) {// The Activity starts monitoring its reference changes when it enters the Destroy state refWatcher.watch(activity); }}; private final Application application; private final RefWatcher refWatcher; private ActivityRefWatcher(Application application, RefWatcher refWatcher) { this.application = application; This. RefWatcher = refWatcher; }Copy the code

The install static method initializes the ActivityRefWatcher instance object and registers the Activity’s lifecycle callback. RefWatcher. Watch is finally used in the Activity’s onDestroy state to monitor the current Activity for memory leaks.

3. RefWatcher.watch

1. RefWatcher profile

    private final WatchExecutor watchExecutor;
    private final DebuggerControl debuggerControl;
    private final GcTrigger gcTrigger;
    private final HeapDumper heapDumper;
    private final HeapDump.Listener heapdumpListener;
    private final HeapDump.Builder heapDumpBuilder;
    private final Set<String> retainedKeys;
    private final ReferenceQueue<Object> queue;
Copy the code
  • WatchExecutor (AndroidWatchExecutor, internal Handler) : Executor that performs memory leak detection.
  • DebuggerControl: Used to check whether memory leak detection is not performed during debugging.
  • GcTrigger: GC switch to invoke system GC.
  • HeapDumper (actually AndroidHeapDumper object) : Dump file used to generate memory leak analysis.
  • RetainedKeys: Keys that hold references to be detected and memory leaks generated.
  • Queue (initialized in the RefWatcher constructor) : Used to hold weak references to be gc.
  • HeapdumpListener: Used to analyze dump files and generate a memory leak analysis report.
  • HeapDumpBuilder: Builds HeapDump objects to analyze memory leaks using dump files generated by heapDumper and other information.

2. watch(activity)

/ / RefWatcher class: Public void watch(Object watchedReference) {// watchedReference is an Activity or Fragment watch(watchedReference,"");
    }

    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(); String key = uuid.randomuuid (). retainedKeys.add(key); // Create a weak reference, And passed key and queue(reference queue) // KeyedWeakReference this weak reference object is used with ReferenceQueue constructor final KeyedWeakReference Reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); EnsureGoneAsync (watchStartNanoTime, reference); } private void ensureGoneAsync(final long watchStartNanoTime, Final KeyedWeakReference Reference) {// Actually use the Handler's post method // // on the asynchronous thread to start parsing the weak reference watchExecutor.execute(new)Retryable() {
            @Override
            public Retryable.Result run() {
                returnensureGone(reference, watchStartNanoTime); }}); } Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); / / remove weak references removeWeaklyReachableReferences ();if(debuggerControl isDebuggerAttached ()) {/ / if the VM is connected to the Debuger, ignore the test, because the Debugger might hold some invisible object in the current context, lead to miscalculationreturn RETRY;
        }
        if(gone(reference)) {// Return if the reference does not already existreturnDONE; } gcTrigger.runGc(); / / trigger GC removeWeaklyReachableReferences (); ; // Remove the weak reference again, double-check // If the reference still exists after GC, then analyze furtherif(! gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); // Dump memory snapshots to *.hprof File heapDumpFile = heapDumper.dumpheap ();if (heapDumpFile == RETRY_LATER) {
                // Could not dump the heap.
                returnRETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); / / build heapdumps object heapdumps heapdumps = heapDumpBuilder. HeapDumpFile (heapDumpFile). ReferenceKey (reference. Key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); HeapdumpListener. Analyze (heapDump); }return DONE;
    }
Copy the code

Let’s analyze this large section of code separately:

  1. Analysis of action process of triggering Watch: When the Activity onDestroy call notification will be sent to the Application, and then call lifecycleCallback. OnActivityDestroyed () method, finally RefWatcher watch method is triggered, This enables the Activity to automatically analyze memory leaks.

  2. A KeyedWeakReference is created, and when the JVM triggers GC, the weak reference is reclaimed and added to the ReferenceQueue. (When the WeakReference constructor is passed into the ReferenceQueue queue, if the referenced object is reclaimed, it will be added to the queue.)

    For details, see: Java Reference analysis and detailed description of the implementation of Reference in Java and the corresponding execution process

  3. By removing weak references twice, the GC operation is manually invoked before the second time. If the reference still exists, the HeapDump file is generated and the HeapDump object is initialized. Finally, heapdumpListener. Analyze is called and the memory leak is notified.

Let’s look at the source code for the two methods in the previous section:

Private Boolean Gone (KeyedWeakReference Reference) {// Check whether there is a key in the retainedKeys container. // If there is no key, then there is no leak, otherwise there is suspected leak.return! retainedKeys.contains(reference.key); } private voidremoveWeaklyReachableReferences() { KeyedWeakReference ref; // This object is a weak reference, and if it is returned, it is added to the ReferenceQueue. // retainedKeys this is a Set container that is marked with a unique generated key. If the object is reclaimed, the key value is removed.while ((ref = (KeyedWeakReference) queue.poll()) != null) {
            retainedKeys.remove(ref.key);
        }
    }
Copy the code

HeapdumpListener. Analyze Memory snapshot analysis

Note: The heapdumpListener here is ServiceHeapDumpListener from the [1].build section above

Another library is used for this part of the core content: HaHa

ServiceHeapDumpListener: @override public void analyze(HeapDump HeapDump) {checkNotNull(HeapDump,"heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); } // HeapAnalyzerService Class: public static void runAnalysis(Context Context, HeapDump,Class<? Extends AbstractAnalysisResultService > listenerServiceClass) {/ / Android disable and enable APP or four components / / please refer to: https://blog.csdn.net/ShawnXiaFei/article/details/82020386setEnabledBlocking(context, HeapAnalyzerService.class, true);
        setEnabledBlocking(context, listenerServiceClass, true); Intent intent = new Intent(context, HeapAnalyzerService.class); // listenerServiceClass, // The displayLeakService.class Intent. PutExtra (LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); intent.putExtra(HEAPDUMP_EXTRA, heapDump); / / open at the front desk service ContextCompat. StartForegroundService (context, intent); } @Override protected void onHandleIntentInForeground(@Nullable Intent intent) {if (intent == null) {
            CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
            return; } // Get displayLeakService. class String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA); HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA); HeapAnalyzer HeapAnalyzer = new HeapAnalyzer(heapdump.excludedrefs, this, heapDump.reachabilityInspectorClasses); / / checkForLeak method is the most critical AnalysisResult result. = heapAnalyzer checkForLeak (heapdumps heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize); / / send the result to the listener AbstractAnalysisResultService. SendResultToListener (this, listenerClassName, heapdumps, result); } @override public void onProgressUpdate(Step Step) {// Update the progress status during memory leak analysis int percent = (int) ((100f * step.ordinal()) /  Step.values().length); CanaryLog.d("Analysis in progress, working on: %s", step.name());
        String lowercase = step.name().replace("_"."").toLowerCase(); String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1); / / open notice shows showForegroundNotification (100, percent,false, message);
    }
Copy the code

The Analyze method enables the HeapAnalyzerService heap analysis service. HeapAnalyzerService inherits from ForegroundService, and ForegroundService inherits from IntentService. ForegroundService class overrides the onHandleIntent method, and in the method call his statement onHandleIntentInForeground abstract methods. The HeapAnalyzerService class implements the AnalyzerProgressListener interface, in which there is only one method onProgressUpdate(analysis progress status update) and the enumeration of states in the analysis process. Therefore HeapAnalyzerService will direct execution after service open onHandleIntentInForeground method, the final executive at one of the most important method checkForLeak:

/ / HeapAnalyzer class:  public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey, boolean computeRetainedSize) { long analysisStartNanoTime = System.nanoTime();if(! heapDumpFile.exists()) { Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
            returnfailure(exception, since(analysisStartNanoTime)); } to try {/ / update the progress state listener. OnProgressUpdate (READING_HEAP_DUMP_FILE); // Use HAHA (Mat-based stack parsing library) to parse previously dumped memory files into Snapshot objects // Generate HprofBuffer from heap dump files MemoryMappedFileBuffer(heapDumpFile); // HprofParser parser = new HprofParser(buffer); listener.onProgressUpdate(PARSING_HEAP_DUMP); Snapshot = parser.parse(); Snapshot = parser.parse(); listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS); DeduplicateGcRoots (snapshot); listener.onProgressUpdate(FINDING_LEAKING_REF); // This method is based on the key of the class we need to detect, query the result of parsing whether there is our object, Instance leakingRef = findLeakingReference(referenceKey, snapshot); // If this object does not exist, it has been cleaned up by GC. There is no leak and therefore no leak is returnedif (leakingRef == null) {
                returnnoLeak(since(analysisStartNanoTime)); } // This object exists and cannot be confirmed as a memory leak. Check for gc root for this objectreturn findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
        } catch (Throwable e) {
            returnfailure(e, since(analysisStartNanoTime)); }} private Instance findLeakingReference(String key, Snapshot Snapshot) { KeyedWeakReference is constructed, so first find KeyedWeakReference, You can find our object ClassObj refClass = the snapshot. FindClass (KeyedWeakReference. Class. GetName ());if (refClass == null) {
            throw new IllegalStateException(
                    "Could not find the " + KeyedWeakReference.class.getName() + " class in the " +
                            "heap dump."); } List<String> keysFound = new ArrayList<>(); // Loop through all KeyedWeakReference instancesfor(Instance instance : refClass.getInstancesList()) { List<ClassInstance.FieldValue> values = classInstanceValues(instance); // Find the key value in the KeyedWeakReference, which uniquely identifies Object keyFieldValue = fieldValue(values,"key");
            if (keyFieldValue == null) {
                keysFound.add(null);
                continue; } String keyCandidate = asString(keyFieldValue); // When the key value is equal, it indicates that it is our detection objectif (keyCandidate.equals(key)) {
                return fieldValue(values, "referent");
            }
            keysFound.add(keyCandidate);
        }
        throw new IllegalStateException(
                "Could not find weak reference with key " + key + " in "+ keysFound); } private AnalysisResult findLeakTrace(long analysisStartNanoTime , Snapshot snapshot,Instance leakingRef, boolean computeRetainedSize) { listener.onProgressUpdate(FINDING_SHORTEST_PATH); /** * This is the first line of code that can detect a memory leak in the hprof file. The first line of code that can detect a memory leak in the HPROF file is to expand the call to the gc root. Let's just focus on two types: * 1. This object is static. 2. This object is used by another thread, and another thread is running. ShortestPathFinder = new ShortestPathFinder(excludedRefs); ShortestPathFinder = new ShortestPathFinder(excludedRefs); ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // If the gc root causing the memory leak is not found, the object is not leakingif (result.leakingNode == null) {
            returnnoLeak(since(analysisStartNanoTime)); } listener.onProgressUpdate(BUILDING_LEAK_TRACE); LeakTrace LeakTrace = buildLeakTrace(result.leakingNode); String className = leakingRef.getClassObj().getClassName(); long retainedSize;if(computeRetainedSize) { listener.onProgressUpdate(COMPUTING_DOMINATORS); // Side effect: Calculate the size of the reservation. snapshot.computeDominators(); Instance leakingInstance = result.leakingNode.instance; / / calculate leakage of space size retainedSize = leakingInstance. GetTotalRetainedSize (); / / check Android O above, and view the Android. Graphics. Bitmap. MBuffer what happenedif(SDK_INT <= N_MR1) { listener.onProgressUpdate(COMPUTING_BITMAP_SIZE); / / calculation ignore the size of the bitmap to retain retainedSize + = computeIgnoredBitmapRetainedSize (the snapshot, leakingInstance); }}else{ retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED; } // a leak is detected, and an AnalysisResult object is constructed to returnreturn leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
                since(analysisStartNanoTime));
    }
Copy the code

The third analysis step, parsing the hprof file, is to encapsulate the file as snapshot, then determine the leaking object according to the weak reference and the key value defined above, and finally find the shortest leaking path, as the result of feedback, so if the suspected leaking object is not found in the snapshot, Then it is assumed that this object is not actually leaking.

Finally, when the memory leak analysis completed, call the AbstractAnalysisResultService. SendResultToListener:

/ / AbstractAnalysisResultService class:  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); } // Enable DisplayLeakService Intent Intent = new Intent(context, listenerServiceClass); intent.putExtra(HEAP_DUMP_EXTRA, heapDump); intent.putExtra(RESULT_EXTRA, result); ContextCompat.startForegroundService(context, intent); }Copy the code

Memory leak analysis is completed, will open DisplayLeakService service, the service inherited from AbstractAnalysisResultService, And AbstractAnalysisResultService service inherited from ForegroundService, And AbstractAnalysisResultService rewrite the ForegroundService service onHandleIntentInForeground (this method is invoked in onHandleIntent), And onHandleIntentInForeground method invokes the onHeapAnalyzed method, finally delete heapDumpFile file.

/ / ForegroundService class:  @Override protected void onHandleIntent(@Nullable Intent intent) { onHandleIntentInForeground(intent); } / / AbstractAnalysisResultService class:  @Override protected final void onHandleIntentInForeground(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 class:  @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) {
                if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
                    String className = classSimpleName(result.className);
                    if (result.excludedLeak) {
                        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);
                    String className = classSimpleName(result.className);
                    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 {
                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); // LeakCanaryInternals class:  public static void showNotification(Context context , CharSequence contentTitle, CharSequence contentText, PendingIntent pendingIntent, int notificationId) { Notification.Builder builder = new Notification.Builder(context) .setContentText(contentText) .setContentTitle(contentTitle) .setAutoCancel(true)
                .setContentIntent(pendingIntent);

        Notification notification = buildNotification(context, builder);
        NotificationManager notificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(notificationId, notification);
    }
Copy the code

After the memory leak analysis is complete, the DisplayLeakService service is enabled and the onHeapAnalyzed method is called, which finally pops up a notification informing the developer of the memory leak reference and heap reference path.

The custom of LeakCanary saves leak information

Leakcanary is a quick and convenient way for Android developers to detect memory leaks. However, if LeakCanary cannot meet the requirements, you can customize to save the memory leak results locally.

There is an empty method in the DisplayLeakService.java class in LeakCanary as follows:

/** * You can override this method and make blocking calls to the server to pass leak traces and heap dumps. * Don't forget to check {@link AnalysisResult first#leakFound and AnalysisResult#excludedLeak }
     */
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    }
Copy the code
  1. Inherit the DisplayLeakService class and override the afterDefaultHandling() method to implement its own leak handling

    public class LeadCanaryService extends DisplayLeakService { @Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) { super.afterDefaultHandling(heapDump, result, leakInfo); // Upload leak information to the cloud or save it locally saveLocal(result, leakInfo); } private void saveLocal(AnalysisResult result, String leakInfo) { if (result ! = null) { String leakPath = getApplication().getCacheDir().getAbsolutePath() + "/LeakCanary" + "/LeakCanary.log"; File file = new File(leakPath); FileUtils.createFileDir(file); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String leadMessage = "Time" + simpleDateFormat.toString() + "\\n AnalysisResult{" + "leakFound=" + result.leakFound + ",  excludedLeak=" + result.excludedLeak + ", className='" + result.className + '\'' + ", leakTrace=" + result.leakTrace + ", failure=" + result.failure + ", retainedHeapSize=" + result.retainedHeapSize + ", analysisDurationMs=" + result.analysisDurationMs + "} \\r\\n"; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(leadMessage.getBytes()); try { FileUtils.writeFile(byteArrayInputStream, file); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code
  2. Register the custom service LeadCanaryService in androidmanifest.xml

     <service android:name=".service.LeadCanaryService"/>
    Copy the code
  3. Application references the custom service LeadCanaryService

     LeakCanary.refWatcher(this).listenerServiceClass(LeadCanaryService.class)
             .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
             .buildAndInstall();
    Copy the code

At this point, LeakCanary analysis is over. For performance optimizations, you can check out my Github: github.com/Endless5F/J… , with detailed documentation and code reference.

Refer to the link

www.cnblogs.com/cord/p/1154…

www.cnblogs.com/huanyi0723/…

www.jianshu.com/p/9a7c0e6e6…

Note: if there is anything wrong, please correct it. Looking forward to your thumbs up!!