0

I started using LeakCanary in my app. Based on various sources, I found that with this dependency:

 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'

LeakCanary watches the following components by default: Activities, Fragments (both androidx.fragment.app.Fragment and android.app.Fragment), ViewModels, Services, Broadcast Receivers (especially dynamically registered ones), and View Roots.

Now, I want to log memory leaks detected in any of these components. So I wrote the following code:

  private void setLeakCanaryData() {
                if (BuildConfig.DEBUG) {
                    LeakCanary.Config oldConfig = LeakCanary.getConfig();
                    List<EventListener> newEventListeners = getEventListeners(oldConfig);
                    LeakCanary.Config newConfig = new LeakCanary.Config(
                            oldConfig.getDumpHeap(),
                            oldConfig.getDumpHeapWhenDebugging(),
                            oldConfig.getRetainedVisibleThreshold(),
                            oldConfig.getReferenceMatchers(),
                            oldConfig.getObjectInspectors(),
                            oldConfig.getOnHeapAnalyzedListener(),
                            oldConfig.getMetadataExtractor(),
                            oldConfig.getComputeRetainedHeapSize(),
                            oldConfig.getMaxStoredHeapDumps(),
                            oldConfig.getRequestWriteExternalStoragePermission(),
                            oldConfig.getLeakingObjectFinder(),
                            oldConfig.getHeapDumper(),
                            newEventListeners,
                            oldConfig.getShowNotifications(),
                            oldConfig.getUseExperimentalLeakFinders()
                    );
                    LeakCanary.setConfig(newConfig);
                }
            }
        
            private static @NonNull List<EventListener> getEventListeners(LeakCanary.Config oldConfig) {
                List<EventListener> newEventListeners = new ArrayList<>(oldConfig.getEventListeners());
                newEventListeners.add(event -> {
                    if (event instanceof EventListener.Event.HeapAnalysisDone) {
                        HeapAnalysis analysis = ((EventListener.Event.HeapAnalysisDone<?>) event).getHeapAnalysis();
        
                        if (analysis instanceof HeapAnalysisSuccess) {
                            HeapAnalysisSuccess success = (HeapAnalysisSuccess) analysis;
        
                            for (ApplicationLeak leak : success.getApplicationLeaks()) {
                               sendLogInfo("Application Leak: " + leak.toString());
                            }
        
                            for (LibraryLeak leak : success.getLibraryLeaks()) {
                               sendLogInfo("Library Leak: " + leak.toString());
                            }
                        } else if (analysis instanceof HeapAnalysisFailure) {
                            HeapAnalysisFailure failure = (HeapAnalysisFailure) analysis;
                            sendLogInfo("Heap analysis failed: " + failure.getException().getMessage());
                        }
                    }
                });
                return newEventListeners;
            }

My questions are:

  1. Is this the correct and recommended way to log memory leaks using
    LeakCanary?

  2. Is there any cleaner or more efficient method to attach event listeners in Java?

Any help or suggestions are appreciated. Thank you!

1 Answer 1

1

Your approach is perfectly fine, but you can write the code in a cleaner way:-

  1. Create an Interface for Logging

    public interface LeakLogger {
        void logApplicationLeak(String message);
        void logLibraryLeak(String message);
        void logAnalysisFailure(String message);
    }
    
  2. Implement the Logger (e.g., ConsoleLogger)

    public class ConsoleLeakLogger implements LeakLogger {
        @Override
        public void logApplicationLeak(String message) {
            Log.d("LeakCanary", "Application Leak: " + message);
        }
    
        @Override
        public void logLibraryLeak(String message) {
            Log.d("LeakCanary", "Library Leak: " + message);
        }
    
        @Override
        public void logAnalysisFailure(String message) {
            Log.e("LeakCanary", "Heap analysis failed: " + message);
        }
    }
    
  3. Create an EventListener Provider

    public class LeakCanaryEventListener implements EventListener {
        private final LeakLogger logger;
    
        public LeakCanaryEventListener(LeakLogger logger) {
            this.logger = logger;
        }
    
        @Override
        public void onEvent(Event event) {
            if (event instanceof Event.HeapAnalysisDone) {
                HeapAnalysis analysis = ((Event.HeapAnalysisDone<?>) event).getHeapAnalysis();
    
                if (analysis instanceof HeapAnalysisSuccess) {
                    HeapAnalysisSuccess success = (HeapAnalysisSuccess) analysis;
    
                    for (ApplicationLeak leak : success.getApplicationLeaks()) {
                        logger.logApplicationLeak(leak.toString());
                    }
    
                    for (LibraryLeak leak : success.getLibraryLeaks()) {
                        logger.logLibraryLeak(leak.toString());
                    }
    
                } else if (analysis instanceof HeapAnalysisFailure) {
                    HeapAnalysisFailure failure = (HeapAnalysisFailure) analysis;
                    logger.logAnalysisFailure(failure.getException().getMessage());
                }
            }
        }
    }
    
  4. Apply Configuration in a Setup Class

    public class LeakCanaryInitializer {
    
        public static void setupLeakCanary() {
            if (!BuildConfig.DEBUG) return;
    
            LeakLogger logger = new ConsoleLeakLogger(); // or use DI
            EventListener customListener = new LeakCanaryEventListener(logger);
    
            LeakCanary.Config oldConfig = LeakCanary.getConfig();
            List<EventListener> newListeners = new ArrayList<>(oldConfig.getEventListeners());
            newListeners.add(customListener);
    
            LeakCanary.Config newConfig = new LeakCanary.Config(
                oldConfig.getDumpHeap(),
                oldConfig.getDumpHeapWhenDebugging(),
                oldConfig.getRetainedVisibleThreshold(),
                oldConfig.getReferenceMatchers(),
                oldConfig.getObjectInspectors(),
                oldConfig.getOnHeapAnalyzedListener(),
                oldConfig.getMetadataExtractor(),
                oldConfig.getComputeRetainedHeapSize(),
                oldConfig.getMaxStoredHeapDumps(),
                oldConfig.getRequestWriteExternalStoragePermission(),
                oldConfig.getLeakingObjectFinder(),
                oldConfig.getHeapDumper(),
                newListeners,
                oldConfig.getShowNotifications(),
                oldConfig.getUseExperimentalLeakFinders()
            );
    
            LeakCanary.setConfig(newConfig);
        }
    }
    
    
  5. And finally in Application class

@Override
public void onCreate() {
    super.onCreate();
    LeakCanaryInitializer.setupLeakCanary();
}
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.