### Basic Test Rule Chain Setup Source: https://github.com/square/leakcanary/blob/main/docs/ui-tests.md An example of a basic test rule chain setup without LeakCanary's specific rules. ```kotlin import org.junit.Rule import org.junit.rules.RuleChain // Example test rule chain @get:Rule val rule = RuleChain.outerRule(LoginRule()) .around(ActivityScenarioRule(CartActivity::class.java)) .around(LoadingScreenRule()) ``` -------------------------------- ### Install Shark CLI with Homebrew Source: https://github.com/square/leakcanary/blob/main/docs/shark.md Install the Shark CLI using Homebrew. This is the recommended method for installation. ```bash brew install leakcanary-shark ``` -------------------------------- ### Manually Install AppWatchers Source: https://github.com/square/leakcanary/blob/main/docs/recipes.md Specify which watchers to install via AppWatcher.manualInstall() to customize runtime detection. This example excludes FragmentAndViewModelWatcher. ```kotlin val watchersToInstall = AppWatcher.appDefaultWatchers(this) .filter { it !is FragmentAndViewModelWatcher } AppWatcher.manualInstall( application = this, watchersToInstall = watchersToInstall ) ``` -------------------------------- ### Install Docs Dependencies Source: https://github.com/square/leakcanary/blob/main/docs/dev-env.md Install or update the documentation dependencies using pip. ```bash pip install --requirement docs/requirements.txt ``` -------------------------------- ### Set up Python virtual environment and install dependencies Source: https://github.com/square/leakcanary/blob/main/docs/releasing.md Sets up a Python virtual environment and installs the necessary Python packages for documentation generation and deployment. This ensures a consistent build environment. ```bash python3 -m venv venv source venv/bin/activate pip3 install --requirement docs/requirements.txt ``` -------------------------------- ### Install GitHub CLI and jq Source: https://github.com/square/leakcanary/blob/main/docs/releasing.md Installs the GitHub CLI and jq, which are required for interacting with GitHub and processing JSON data, respectively. Use this command on macOS with Homebrew. ```bash brew install gh jq ``` -------------------------------- ### Default Setup Code (Before LeakCanary 2.0) Source: https://github.com/square/leakcanary/blob/main/docs/upgrading-to-leakcanary-2.0.md Illustrates the default LeakCanary setup code in an Application class before version 2.0. This includes a check for the analyzer process. ```java public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); // Normal app init code... } } ``` -------------------------------- ### Install LeakCanary and Explore Heap Dump with Neo4j Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Install LeakCanary using Homebrew. Then, use the shark-cli command to convert a heap dump into a Neo4j database and explore it. ```bash brew install leakcanary-shark ``` ```bash shark-cli --process com.example.app.debug neo4j ``` -------------------------------- ### Accessing Installed Ref Watcher Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Retrieves the singleton instance of the installed ref watcher. ```APIDOC ## Accessing Installed Ref Watcher ### Description The installed ref watcher singleton is now available for direct access. ### Method `LeakCanary.installedRefWatcher()` ### Parameters None ### Response - **RefWatcher** - The singleton instance of the installed ref watcher. ``` -------------------------------- ### Replace LeakCanary Dependency with Startup Variant Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Use leakcanary-android-startup instead of leakcanary-android to integrate with the AndroidX App Startup library. This is an alternative setup for automatic LeakCanary installation. ```groovy dependencies { // Remove the normal leakcanary-android dependency // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8' debugImplementation 'com.squareup.leakcanary:leakcanary-android-startup:2.8' } ``` -------------------------------- ### Print Thread Names from Heap Graph Source: https://github.com/square/leakcanary/blob/main/docs/shark.md This Kotlin example demonstrates how to index an Hprof file into a heap graph and then extract and print the names of all threads. ```kotlin import java.io.File import shark.Hprof import shark.HprofHeapGraph fun main(args: Array) { val heapDumpFile = File(args[0]) // Prints all thread names Hprof.open(heapDumpFile).use { hprof -> val heapGraph = HprofHeapGraph.indexHprof(hprof) val threadClass = heapGraph.findClassByName("java.lang.Thread")!! val threadNames: Sequence = threadClass.instances.map { instance -> val nameField = instance["java.lang.Thread", "name"]!! nameField.value.readAsJavaString()!! } threadNames.forEach { println(it) } } } ``` -------------------------------- ### Configure LeakCanary: Before 2.0 Source: https://github.com/square/leakcanary/blob/main/docs/upgrading-to-leakcanary-2.0.md In LeakCanary versions prior to 2.0, LeakCanary was installed directly within the application class. This example shows the older method of installing LeakCanary with specific watch settings. ```java public class DebugExampleApplication extends ExampleApplication { @Override protected void installLeakCanary() { RefWatcher refWatcher = LeakCanary.refWatcher(this) .watchActivities(false) .buildAndInstall(); } } ``` -------------------------------- ### Heap Growth Detector Setup for Espresso Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md This Kotlin code sets up a `LiveHeapGrowthDetector` for in-process Espresso UI tests. It configures heap dumping, reference matching, and the detection logic. Ensure the heap dump directory exists and is accessible. ```kotlin import leakcanary.AndroidDebugHeapDumper import shark.AndroidReferenceMatchers import shark.AndroidReferenceReaderFactory import shark.CloseableHeapGraph import shark.DiffingHeapGrowthDetector import shark.HeapGraphProvider import shark.HprofHeapGraph.Companion.openHeapGraph import shark.IgnoredReferenceMatcher import shark.LiveHeapGrowthDetector import shark.LoopingHeapGrowthDetector import shark.MatchingGcRootProvider import shark.ReferencePattern.InstanceFieldPattern import java.io.File import java.text.SimpleDateFormat import java.util.Date import java.util.Locale /** * Heap growth detector for in process Espresso UI tests. * * Call [LiveHeapGrowthDetector.detectRepeatedHeapGrowth] with a scenario to repeat, then assert that the resulting [shark.HeapTraversalWithDiff.growingNodes] is empty. */ val HeapGrowthDetector by lazy { val referenceMatchers = AndroidReferenceMatchers.appDefaults + HeapTraversal.ignoredReferences + // https://cs.android.com/android/_/android/platform/frameworks/base/+/6985fb39f07294fb979b14ba0ebabfd2fea06d34 IgnoredReferenceMatcher(InstanceFieldPattern("android.os.StrictMode", "sLastVmViolationTime")) val dateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'-heap-growth.hprof'", Locale.US) val uploadedTracesDirectory = File("/sdcard/traces/") uploadedTracesDirectory.mkdirs() check(uploadedTracesDirectory.exists()) { "Expected heap dump folder to exist: ${uploadedTracesDirectory.absolutePath}" } val heapGraphProvider = HeapGraphProvider { val fileName = dateFormat.format(Date()) val heapDumpFile = File(uploadedTracesDirectory, fileName) AndroidDebugHeapDumper.dumpHeap(heapDumpFile) check(heapDumpFile.exists()) { "Expected file to exist after heap dump: ${heapDumpFile.absolutePath}" } val realGraph = heapDumpFile.openHeapGraph() object : CloseableHeapGraph by realGraph { override fun close() { realGraph.close() heapDumpFile.delete() } } } LiveHeapGrowthDetector( maxHeapDumps = 5, heapGraphProvider = heapGraphProvider, scenarioLoopsPerDump = 5, detector = LoopingHeapGrowthDetector( DiffingHeapGrowthDetector( referenceReaderFactory = AndroidReferenceReaderFactory(referenceMatchers), gcRootProvider = MatchingGcRootProvider(referenceMatchers) ) ) ) } ``` -------------------------------- ### Manually Install LeakCanary Watchers (Excluding ServiceWatcher) Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Manually install LeakCanary watchers by filtering out the ServiceWatcher from the default list. This is useful when you need fine-grained control over which watchers are active. ```kotlin class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() val watchersToInstall = AppWatcher.appDefaultWatchers(application) .filter { it !is ServiceWatcher } AppWatcher.manualInstall( application = application, watchersToInstall = watchersToInstall ) } } ``` -------------------------------- ### Configure LeakCanary Analysis (Java) Source: https://github.com/square/leakcanary/blob/main/docs/recipes.md Customize heap dumping and analysis settings in Java using LeakCanary.Config.Builder. This example sets the retained visible threshold. ```java LeakCanary.Config config = LeakCanary.getConfig().newBuilder() .retainedVisibleThreshold(3) .build(); LeakCanary.setConfig(config); ``` -------------------------------- ### Example Library Leak Pattern Source: https://github.com/square/leakcanary/blob/main/docs/fundamentals-how-leakcanary-works.md This example shows how LeakCanary identifies a Library Leak by pattern matching on reference names, highlighting a specific instance field and its reference chain. ```text Leak pattern: instance field android.app.Activity$1#this$0 Description: Android Q added a new IRequestFinishCallback$Stub class [...] ┬─── │ GC Root: Global variable in native code │ ├─ android.app.Activity$1 instance │ Leaking: UNKNOWN │ Anonymous subclass of android.app.IRequestFinishCallback$Stub │ ↓ Activity$1.this$0 │ ~~~~~~ ╰→ com.example.MainActivity instance ``` -------------------------------- ### Generate Android Heap Analysis Report Source: https://github.com/square/leakcanary/blob/main/docs/shark.md This Kotlin example extends the general heap analysis by incorporating Android-specific reference matchers and object inspectors for more accurate Android heap analysis. ```kotlin import java.io.File import shark.AndroidObjectInspectors import shark.AndroidReferenceMatchers import shark.FilteringLeakingObjectFinder import shark.FilteringLeakingObjectFinder.LeakingObjectFilter import shark.HeapAnalyzer import shark.HeapObject import shark.HeapObject.HeapInstance import shark.Hprof import shark.HprofHeapGraph // Marks any instance of com.example.ThingWithLifecycle with // ThingWithLifecycle.destroyed=true as leaking val leakingObjectFilter = object : LeakingObjectFilter { override fun isLeakingObject(heapObject: HeapObject): Boolean { return if ( heapObject is HeapInstance && heapObject instanceOf "com.example.ThingWithLifecycle" ) { val instance = heapObject as HeapInstance val destroyedField = instance["com.example.ThingWithLifecycle", "destroyed"]!! destroyedField.value.asBoolean!! } else false } } val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(leakingObjectFilter)) fun main(args: Array) { val heapDumpFile = File(args[0]) val heapAnalysis = Hprof.open(heapDumpFile).use { hprof -> val heapGraph = HprofHeapGraph.indexHprof(hprof) val heapAnalyzer = HeapAnalyzer({}) heapAnalyzer.analyze( heapDumpFile = heapDumpFile, graph = heapGraph, leakingObjectFinder = leakingObjectFinder, referenceMatchers = AndroidReferenceMatchers.appDefaults, objectInspectors = AndroidObjectInspectors.appDefaults, ) } println(heapAnalysis) } ``` -------------------------------- ### Initial Leak Trace Highlighting All Suspect References Source: https://github.com/square/leakcanary/blob/main/docs/fundamentals-fixing-a-memory-leak.md This is an example of an initial leak trace produced by LeakCanary, where all references in the path are initially suspected and highlighted. ```text ┬─── │ GC Root: System class │ ├─ android.provider.FontsContract class │ ↓ static FontsContract.sContext ├─ com.example.leakcanary.ExampleApplication instance │ ↓ ExampleApplication.leakedViews ├─ java.util.ArrayList instance │ ↓ ArrayList.elementData ├─ java.lang.Object[] array │ ↓ Object[].[0] ├─ android.widget.TextView instance │ ↓ TextView.mContext ╰→ com.example.leakcanary.MainActivity instance ``` -------------------------------- ### HashMap Leak Trace Example Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Demonstrates a typical leak trace for a HashMap before improved data structure support. This shows the internal structure of the HashMap, which can be less intuitive for developers. ```kotlin class CheckoutController { val tabs = HashMap() fun addItemsTab(tab: Tab) { tabs["ItemsTab"] = tab } } ``` ```text │ ... ├─ com.example.CheckoutController instance │ ↓ CheckoutController.tabs ├─ java.util.HashMap instance │ ↓ HashMap.table ├─ java.util.HashMap$Node[] array │ ↓ HashMap$Node[42] ├─ java.util.HashMap$Node instance │ ↓ HashMap$Node.next ├─ java.util.HashMap$Node instance │ ↓ HashMap$Node.value ├─ com.example.Tab instance │ ... ``` -------------------------------- ### Configure LeakCanary Analysis Source: https://github.com/square/leakcanary/blob/main/docs/recipes.md Customize heap dumping and analysis settings by updating LeakCanary.config. This example sets the retained visible threshold. ```kotlin LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3) ``` -------------------------------- ### Manually Install LeakCanary Watchers with Custom ReachabilityWatcher Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Install LeakCanary watchers with a custom ReachabilityWatcher that filters out specific objects, such as BadSdkLeakingFragment, before delegating to the default object watcher. ```kotlin class DebugExampleApplication : ExampleApplication() { override fun onCreate() { super.onCreate() val delegate = ReachabilityWatcher { watchedObject, description -> if (watchedObject !is BadSdkLeakingFragment) { AppWatcher.objectWatcher.expectWeaklyReachable(watchedObject, description) } } val watchersToInstall = AppWatcher.appDefaultWatchers(application, delegate) AppWatcher.manualInstall( application = application, watchersToInstall = watchersToInstall ) } } ``` -------------------------------- ### Display View Resource ID in LeakTrace Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md This example shows how a View instance's resource ID is displayed in the LeakTrace. This information is useful for debugging UI-related leaks. ```text ├─ android.widget.TextView instance │ View.mID = R.id.helper_text ``` -------------------------------- ### Example Leak Trace Source: https://github.com/square/leakcanary/blob/main/docs/fundamentals-fixing-a-memory-leak.md This is an example of a leak trace provided by LeakCanary, illustrating the strong reference path from a garbage collection root to a retained object. It helps in diagnosing memory leaks. ```text ┬─── │ GC Root: Local variable in native code │ ├─ dalvik.system.PathClassLoader instance │ ↓ PathClassLoader.runtimeInternalObjects ├─ java.lang.Object[] array │ ↓ Object[].[43] ├─ com.example.Utils class │ ↓ static Utils.helper ╰→ java.example.Helper ``` -------------------------------- ### Espresso Test for Heap Growth Detection Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Example of an Espresso test using `ObjectGrowthDetector` to find repeatedly growing objects during UI interactions. Asserts that no objects are found to be growing. ```kotlin class MyEspressoTest { val detector = ObjectGrowthDetector .forAndroidHeap() .repeatingAndroidInProcessScenario() @Test fun greeter_says_hello_does_not_leak() { // Runs repeatedly until the heap stops growing or we reach max heap dumps. val heapGrowth = detector.findRepeatedlyGrowingObjects { onView(withId(R.id.name_field)).perform(typeText("Steve")) onView(withId(R.id.greet_button)).perform(click()) onView(withText("Hello Steve!")).check(matches(isDisplayed())) } assertThat(heapGrowth.growingObjects).isEmpty() } } ``` -------------------------------- ### Disable LeakCanary Auto Install Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md To disable the automatic installation of LeakCanary watchers, set this boolean resource to false in your project's values. ```xml false ``` -------------------------------- ### Configure LeakCanary from Java Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Use LeakCanary.Config.Builder for idiomatic Java configuration. This example sets the retained visible threshold and disables computing retained heap size. ```java LeakCanary.Config config = LeakCanary.getConfig().newBuilder() .retainedVisibleThreshold(3) .computeRetainedHeapSize(false) .build(); LeakCanary.setConfig(config); ``` -------------------------------- ### UI Automator Test for Heap Growth Detection Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Example of a UI Automator test using `ObjectGrowthDetector` to monitor heap growth during UI interactions. It checks that no growing objects are detected. ```kotlin class MyUiAutomatorTest { val detector = ObjectGrowthDetector .forAndroidHeap() .repeatingUiAutomatorScenario() @Test fun clicking_welcome_does_not_leak() { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) // Runs repeatedly until the heap stops growing or we reach max heap dumps. val heapGrowth = detector.findRepeatedlyGrowingObjects { device.findObject(By.text("Welcome!")).click() } assertThat(heapGrowth.growingObjects).isEmpty() } } ``` -------------------------------- ### Leak Trace with Suspect References Source: https://github.com/square/leakcanary/blob/main/docs/fundamentals-how-leakcanary-works.md This example shows a leak trace where suspect references are underlined with '~' characters. This format is used when sharing leak traces as text. ```text │ ├─ com.example.leakcanary.LeakingSingleton class │ Leaking: NO (a class is never leaking) │ ↓ static LeakingSingleton.leakedViews │ ~~~~~~~~~~~ ├─ java.util.ArrayList instance │ Leaking: UNKNOWN │ ↓ ArrayList.elementData │ ~~~~~~~~~~~ ├─ java.lang.Object[] array │ Leaking: UNKNOWN │ ↓ Object[].[0] │ ~~~ ├─ android.widget.TextView instance │ Leaking: YES (View.mContext references a destroyed activity) ... ``` -------------------------------- ### Espresso Test for Heap Growth Detection Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md An example of an Espresso test that uses HeapDiff to detect heap growth during UI interactions. The test asserts that no objects are growing in the heap. ```kotlin class MyEspressoTest { val detector = HeapDiff.repeatingAndroidInProcessScenario() @Test fun greeter_says_hello_does_not_grow_heap() { // Runs repeatedly until the heap stops growing or we reach max heap dumps. val heapDiff = detector.findRepeatedlyGrowingObjects { onView(withId(R.id.name_field)).perform(typeText("Steve")) onView(withId(R.id.greet_button)).perform(click()) onView(withText("Hello Steve!")).check(matches(isDisplayed())) } assertThat(heapDiff.growingObjects).isEmpty() } } ``` -------------------------------- ### Improved HashMap Leak Trace Example Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Illustrates the clearer leak trace provided by LeakCanary after improved data structure support. It surfaces the key used in the HashMap and simplifies the representation of internal nodes. ```text │ ... ├─ com.example.CheckoutController instance │ ↓ CheckoutController.tabs ├─ java.util.HashMap instance │ ↓ HashMap[ItemsTab] ├─ com.example.Tab instance │ ... ``` -------------------------------- ### UI Automator Test for Heap Growth Detection Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md An example of a UI Automator test that uses HeapDiff to detect heap growth during UI interactions. The test clicks a UI element and asserts that no objects are growing in the heap. ```kotlin class MyUiAutomatorTest { val detector = HeapDiff.repeatingUiAutomatorScenario() @Test fun clicking_welcome_does_not_grow_heap() { val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) // Runs repeatedly until the heap stops growing or we reach max heap dumps. val heapDiff = detector.findRepeatedlyGrowingObjects { device.findObject(By.text("Welcome!")).click() } assertThat(heapDiff.growingObjects).isEmpty() } } ``` -------------------------------- ### Detect Leaks Before Activity Destroyed Source: https://github.com/square/leakcanary/blob/main/docs/ui-tests.md Configure `DetectLeaksAfterTestSuccess` to run before the activity is destroyed. This setup might detect more fragment leaks but could miss leaks that are resolved when the activity is destroyed. ```kotlin import org.junit.Rule import org.junit.rules.RuleChain @get:Rule val rule = RuleChain.outerRule(LoginRule()) .around(ActivityScenarioRule(CartActivity::class.java)) // Detect leaks BEFORE activity is destroyed .around(DetectLeaksAfterTestSuccess(tag = "BeforeActivityDestroyed")) .around(LoadingScreenRule()) ``` -------------------------------- ### Check LeakCanary Running Status Source: https://github.com/square/leakcanary/blob/main/docs/faq.md Verify LeakCanary is active by filtering Logcat for the 'LeakCanary' tag. Look for the 'Installing AppWatcher' message to confirm successful initialization. ```bash $ adb logcat | grep LeakCanary D/LeakCanary: Installing AppWatcher ``` -------------------------------- ### Add Plumber-Android Dependency Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Example of how to include plumber-android in your project dependencies. Leakcanary-android includes plumber-android by default for debug builds. ```gradle dependencies { // leakcanary-android adds plumber-android to debug builds debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6' // This adds plumber-android to all build types implementation 'com.squareup.leakcanary:plumber-android:2.6' } ``` -------------------------------- ### Skip LeakCanary Initialization in Analyzer Process Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md Prevent LeakCanary initialization in the analyzer process to avoid conflicts. This setup is for multi-process configurations. ```kotlin class ExampleApplication : Application() { override fun onCreate() { if (LeakCanaryProcess.isInAnalyzerProcess(this)) { return } super.onCreate() // normal init goes here, skipped in :leakcanary process. } } ``` -------------------------------- ### Deobfuscate Hprof Files with Shark CLI Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md The Shark CLI can now deobfuscate heap dumps. Ensure you have installed the leakcanary-shark package via Homebrew. ```bash brew install leakcanary-shark shark-cli --hprof heapdump.hprof -m mapping.txt deobfuscate-hprof ``` -------------------------------- ### Detect ObjectAnimator Leaks in Kotlin Source: https://github.com/square/leakcanary/blob/main/docs/changelog.md This code demonstrates how to start an infinite ObjectAnimator without canceling it, which LeakCanary can now detect. Ensure ObjectAnimator is properly canceled to prevent leaks. ```kotlin class ExampleActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) findViewById