xref: /aosp_15_r20/external/leakcanary2/leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)

<lambda>null1 package leakcanary
2 
3 import android.content.Intent
4 import leakcanary.LeakCanary.config
5 import leakcanary.internal.HeapDumpControl
6 import leakcanary.internal.InternalLeakCanary
7 import leakcanary.internal.InternalLeakCanary.FormFactor.TV
8 import leakcanary.internal.activity.LeakActivity
9 import shark.AndroidMetadataExtractor
10 import shark.AndroidObjectInspectors
11 import shark.AndroidReferenceMatchers
12 import shark.FilteringLeakingObjectFinder
13 import shark.HeapAnalysisSuccess
14 import shark.IgnoredReferenceMatcher
15 import shark.KeyedWeakReferenceFinder
16 import shark.LeakingObjectFinder
17 import shark.LibraryLeakReferenceMatcher
18 import shark.MetadataExtractor
19 import shark.ObjectInspector
20 import shark.ReferenceMatcher
21 import shark.SharkLog
22 
23 /**
24  * The entry point API for LeakCanary. LeakCanary builds on top of [AppWatcher]. AppWatcher
25  * notifies LeakCanary of retained instances, which in turns dumps the heap, analyses it and
26  * publishes the results.
27  *
28  * LeakCanary can be configured by updating [config].
29  */
30 object LeakCanary {
31 
32   /**
33    * LeakCanary configuration data class. Properties can be updated via [copy].
34    *
35    * @see [config]
36    */
37   data class Config(
38     /**
39      * Whether LeakCanary should dump the heap when enough retained instances are found. This needs
40      * to be true for LeakCanary to work, but sometimes you may want to temporarily disable
41      * LeakCanary (e.g. for a product demo).
42      *
43      * Defaults to true.
44      */
45     val dumpHeap: Boolean = true,
46     /**
47      * If [dumpHeapWhenDebugging] is false then LeakCanary will not dump the heap
48      * when the debugger is attached. The debugger can create temporary memory leaks (for instance
49      * if a thread is blocked on a breakpoint).
50      *
51      * Defaults to false.
52      */
53     val dumpHeapWhenDebugging: Boolean = false,
54     /**
55      * When the app is visible, LeakCanary will wait for at least
56      * [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
57      * freezes the UI and can be frustrating for developers who are trying to work. This is
58      * especially frustrating as the Android Framework has a number of leaks that cannot easily
59      * be fixed.
60      *
61      * When the app becomes invisible, LeakCanary dumps the heap after
62      * [AppWatcher.retainedDelayMillis] ms.
63      *
64      * The app is considered visible if it has at least one activity in started state.
65      *
66      * A higher threshold means LeakCanary will dump the heap less often, therefore it won't be
67      * bothering developers as much but it could miss some leaks.
68      *
69      * Defaults to 5.
70      */
71     val retainedVisibleThreshold: Int = 5,
72 
73     /**
74      * Known patterns of references in the heap, added here either to ignore them
75      * ([IgnoredReferenceMatcher]) or to mark them as library leaks ([LibraryLeakReferenceMatcher]).
76      *
77      * When adding your own custom [LibraryLeakReferenceMatcher] instances, you'll most
78      * likely want to set [LibraryLeakReferenceMatcher.patternApplies] with a filter that checks
79      * for the Android OS version and manufacturer. The build information can be obtained by calling
80      * [shark.AndroidBuildMirror.fromHeapGraph].
81      *
82      * Defaults to [AndroidReferenceMatchers.appDefaults]
83      */
84     val referenceMatchers: List<ReferenceMatcher> = AndroidReferenceMatchers.appDefaults,
85 
86     /**
87      * List of [ObjectInspector] that provide LeakCanary with insights about objects found in the
88      * heap. You can create your own [ObjectInspector] implementations, and also add
89      * a [shark.AppSingletonInspector] instance created with the list of internal singletons.
90      *
91      * Defaults to [AndroidObjectInspectors.appDefaults]
92      */
93     val objectInspectors: List<ObjectInspector> = AndroidObjectInspectors.appDefaults,
94 
95     /**
96      * Deprecated, add to LeakCanary.config.eventListeners instead.
97      * Called on a background thread when the heap analysis is complete.
98      * If you want leaks to be added to the activity that lists leaks, make sure to delegate
99      * calls to a [DefaultOnHeapAnalyzedListener].
100      *
101      * Defaults to [DefaultOnHeapAnalyzedListener]
102      */
103     @Deprecated(message = "Add to LeakCanary.config.eventListeners instead")
104     val onHeapAnalyzedListener: OnHeapAnalyzedListener = DefaultOnHeapAnalyzedListener.create(),
105 
106     /**
107      * Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata].
108      * Called on a background thread during heap analysis.
109      *
110      * Defaults to [AndroidMetadataExtractor]
111      */
112     val metadataExtractor: MetadataExtractor = AndroidMetadataExtractor,
113 
114     /**
115      * Whether to compute the retained heap size, which is the total number of bytes in memory that
116      * would be reclaimed if the detected leaks didn't happen. This includes native memory
117      * associated to Java objects (e.g. Android bitmaps).
118      *
119      * Computing the retained heap size can slow down the analysis because it requires navigating
120      * from GC roots through the entire object graph, whereas [shark.HeapAnalyzer] would otherwise
121      * stop as soon as all leaking instances are found.
122      *
123      * Defaults to true.
124      */
125     val computeRetainedHeapSize: Boolean = true,
126 
127     /**
128      * How many heap dumps are kept on the Android device for this app package. When this threshold
129      * is reached LeakCanary deletes the older heap dumps. As several heap dumps may be enqueued
130      * you should avoid going down to 1 or 2.
131      *
132      * Defaults to 7.
133      */
134     val maxStoredHeapDumps: Int = 7,
135 
136     /**
137      * LeakCanary always attempts to store heap dumps on the external storage if the
138      * WRITE_EXTERNAL_STORAGE is already granted, and otherwise uses the app storage.
139      * If the WRITE_EXTERNAL_STORAGE permission is not granted and
140      * [requestWriteExternalStoragePermission] is true, then LeakCanary will display a notification
141      * to ask for that permission.
142      *
143      * Defaults to false because that permission notification can be annoying.
144      */
145     val requestWriteExternalStoragePermission: Boolean = false,
146 
147     /**
148      * Finds the objects that are leaking, for which LeakCanary will compute leak traces.
149      *
150      * Defaults to [KeyedWeakReferenceFinder] which finds all objects tracked by a
151      * [KeyedWeakReference], ie all objects that were passed to
152      * [ObjectWatcher.expectWeaklyReachable].
153      *
154      * You could instead replace it with a [FilteringLeakingObjectFinder], which scans all objects
155      * in the heap dump and delegates the decision to a list of
156      * [FilteringLeakingObjectFinder.LeakingObjectFilter]. This can lead to finding more leaks
157      * than the default and shorter leak traces. This also means that every analysis during a
158      * given process life will bring up the same leaking objects over and over again, unlike
159      * when using [KeyedWeakReferenceFinder] (because [KeyedWeakReference] instances are cleared
160      * after each heap dump).
161      *
162      * The list of filters can be built from [AndroidObjectInspectors]:
163      *
164      * ```kotlin
165      * LeakCanary.config = LeakCanary.config.copy(
166      *     leakingObjectFinder = FilteringLeakingObjectFinder(
167      *         AndroidObjectInspectors.appLeakingObjectFilters
168      *     )
169      * )
170      * ```
171      */
172     val leakingObjectFinder: LeakingObjectFinder = KeyedWeakReferenceFinder,
173 
174     /**
175      * Dumps the Java heap. You may replace this with your own implementation if you wish to
176      * change the core heap dumping implementation.
177      */
178     val heapDumper: HeapDumper = AndroidDebugHeapDumper,
179 
180     /**
181      * Listeners for LeakCanary events. See [EventListener.Event] for the list of events and
182      * which thread they're sent from. You most likely want to keep this list and add to it, or
183      * remove a few entries but not all entries. Each listener is independent and provides
184      * additional behavior which you can disable by not excluding it:
185      *
186      * ```kotlin
187      * // No cute canary toast (very sad!)
188      * LeakCanary.config = LeakCanary.config.run {
189      *   copy(
190      *     eventListeners = eventListeners.filter {
191      *       it !is ToastEventListener
192      *     }
193      *   )
194      * }
195      * ```
196      */
197     val eventListeners: List<EventListener> = listOf(
198       LogcatEventListener,
199       ToastEventListener,
200       LazyForwardingEventListener {
201         if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
202       },
203       when {
204           RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
205             RemoteWorkManagerHeapAnalyzer
206           WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
207           else -> BackgroundThreadHeapAnalyzer
208       }
209     ),
210 
211     /**
212      * Whether to show LeakCanary notifications. When [showNotifications] is true, LeakCanary
213      * will only display notifications if the app is in foreground and is not an instant, TV or
214      * Wear app.
215      *
216      * Defaults to true.
217      */
218     val showNotifications: Boolean = true,
219 
220     /**
221      * Deprecated: This is a no-op, set a custom [leakingObjectFinder] instead.
222      */
223     @Deprecated("This is a no-op, set a custom leakingObjectFinder instead")
224     val useExperimentalLeakFinders: Boolean = false
225   ) {
226 
227     /**
228      * Construct a new Config via [LeakCanary.Config.Builder].
229      * Note: this method is intended to be used from Java code only. For idiomatic Kotlin use
230      * `copy()` to modify [LeakCanary.config].
231      */
232     @Suppress("NEWER_VERSION_IN_SINCE_KOTLIN")
233     @SinceKotlin("999.9") // Hide from Kotlin code, this method is only for Java code
234     fun newBuilder() = Builder(this)
235 
236     /**
237      * Builder for [LeakCanary.Config] intended to be used only from Java code.
238      *
239      * Usage:
240      * ```java
241      * LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
242      *    .retainedVisibleThreshold(3)
243      *    .build();
244      * LeakCanary.setConfig(config);
245      * ```
246      *
247      * For idiomatic Kotlin use `copy()` method instead:
248      * ```kotlin
249      * LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
250      * ```
251      */
252     class Builder internal constructor(config: Config) {
253       private var dumpHeap = config.dumpHeap
254       private var dumpHeapWhenDebugging = config.dumpHeapWhenDebugging
255       private var retainedVisibleThreshold = config.retainedVisibleThreshold
256       private var referenceMatchers = config.referenceMatchers
257       private var objectInspectors = config.objectInspectors
258       private var onHeapAnalyzedListener = config.onHeapAnalyzedListener
259       private var metadataExtractor = config.metadataExtractor
260       private var computeRetainedHeapSize = config.computeRetainedHeapSize
261       private var maxStoredHeapDumps = config.maxStoredHeapDumps
262       private var requestWriteExternalStoragePermission =
263         config.requestWriteExternalStoragePermission
264       private var leakingObjectFinder = config.leakingObjectFinder
265       private var heapDumper = config.heapDumper
266       private var eventListeners = config.eventListeners
267       private var useExperimentalLeakFinders = config.useExperimentalLeakFinders
268       private var showNotifications = config.showNotifications
269 
270       /** @see [LeakCanary.Config.dumpHeap] */
271       fun dumpHeap(dumpHeap: Boolean) =
272         apply { this.dumpHeap = dumpHeap }
273 
274       /** @see [LeakCanary.Config.dumpHeapWhenDebugging] */
275       fun dumpHeapWhenDebugging(dumpHeapWhenDebugging: Boolean) =
276         apply { this.dumpHeapWhenDebugging = dumpHeapWhenDebugging }
277 
278       /** @see [LeakCanary.Config.retainedVisibleThreshold] */
279       fun retainedVisibleThreshold(retainedVisibleThreshold: Int) =
280         apply { this.retainedVisibleThreshold = retainedVisibleThreshold }
281 
282       /** @see [LeakCanary.Config.referenceMatchers] */
283       fun referenceMatchers(referenceMatchers: List<ReferenceMatcher>) =
284         apply { this.referenceMatchers = referenceMatchers }
285 
286       /** @see [LeakCanary.Config.objectInspectors] */
287       fun objectInspectors(objectInspectors: List<ObjectInspector>) =
288         apply { this.objectInspectors = objectInspectors }
289 
290       /** @see [LeakCanary.Config.onHeapAnalyzedListener] */
291       @Deprecated(message = "Add to LeakCanary.config.eventListeners instead")
292       fun onHeapAnalyzedListener(onHeapAnalyzedListener: OnHeapAnalyzedListener) =
293         apply { this.onHeapAnalyzedListener = onHeapAnalyzedListener }
294 
295       /** @see [LeakCanary.Config.metadataExtractor] */
296       fun metadataExtractor(metadataExtractor: MetadataExtractor) =
297         apply { this.metadataExtractor = metadataExtractor }
298 
299       /** @see [LeakCanary.Config.computeRetainedHeapSize] */
300       fun computeRetainedHeapSize(computeRetainedHeapSize: Boolean) =
301         apply { this.computeRetainedHeapSize = computeRetainedHeapSize }
302 
303       /** @see [LeakCanary.Config.maxStoredHeapDumps] */
304       fun maxStoredHeapDumps(maxStoredHeapDumps: Int) =
305         apply { this.maxStoredHeapDumps = maxStoredHeapDumps }
306 
307       /** @see [LeakCanary.Config.requestWriteExternalStoragePermission] */
308       fun requestWriteExternalStoragePermission(requestWriteExternalStoragePermission: Boolean) =
309         apply { this.requestWriteExternalStoragePermission = requestWriteExternalStoragePermission }
310 
311       /** @see [LeakCanary.Config.leakingObjectFinder] */
312       fun leakingObjectFinder(leakingObjectFinder: LeakingObjectFinder) =
313         apply { this.leakingObjectFinder = leakingObjectFinder }
314 
315       /** @see [LeakCanary.Config.heapDumper] */
316       fun heapDumper(heapDumper: HeapDumper) =
317         apply { this.heapDumper = heapDumper }
318 
319       /** @see [LeakCanary.Config.eventListeners] */
320       fun eventListeners(eventListeners: List<EventListener>) =
321         apply { this.eventListeners = eventListeners }
322 
323       /** @see [LeakCanary.Config.useExperimentalLeakFinders] */
324       @Deprecated("Set a custom leakingObjectFinder instead")
325       fun useExperimentalLeakFinders(useExperimentalLeakFinders: Boolean) =
326         apply { this.useExperimentalLeakFinders = useExperimentalLeakFinders }
327 
328       /** @see [LeakCanary.Config.showNotifications] */
329       fun showNotifications(showNotifications: Boolean) =
330         apply { this.showNotifications = showNotifications }
331 
332 
333       fun build() = config.copy(
334         dumpHeap = dumpHeap,
335         dumpHeapWhenDebugging = dumpHeapWhenDebugging,
336         retainedVisibleThreshold = retainedVisibleThreshold,
337         referenceMatchers = referenceMatchers,
338         objectInspectors = objectInspectors,
339         onHeapAnalyzedListener = onHeapAnalyzedListener,
340         metadataExtractor = metadataExtractor,
341         computeRetainedHeapSize = computeRetainedHeapSize,
342         maxStoredHeapDumps = maxStoredHeapDumps,
343         requestWriteExternalStoragePermission = requestWriteExternalStoragePermission,
344         leakingObjectFinder = leakingObjectFinder,
345         heapDumper = heapDumper,
346         eventListeners = eventListeners,
347         useExperimentalLeakFinders = useExperimentalLeakFinders,
348         showNotifications = showNotifications,
349       )
350     }
351   }
352 
353   /**
354    * The current LeakCanary configuration. Can be updated at any time, usually by replacing it with
355    * a mutated copy, e.g.:
356    *
357    * ```kotlin
358    * LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
359    * ```
360    *
361    * In Java, use [LeakCanary.Config.Builder] instead:
362    * ```java
363    * LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
364    *    .retainedVisibleThreshold(3)
365    *    .build();
366    * LeakCanary.setConfig(config);
367    * ```
368    */
369   @JvmStatic @Volatile
370   var config: Config = Config()
371     set(newConfig) {
372       val previousConfig = field
373       field = newConfig
374       logConfigChange(previousConfig, newConfig)
375       HeapDumpControl.updateICanHasHeapInBackground()
376     }
377 
378   private fun logConfigChange(
379     previousConfig: Config,
380     newConfig: Config
381   ) {
382     SharkLog.d {
383       val changedFields = mutableListOf<String>()
384       Config::class.java.declaredFields.forEach { field ->
385         field.isAccessible = true
386         val previousValue = field[previousConfig]
387         val newValue = field[newConfig]
388         if (previousValue != newValue) {
389           changedFields += "${field.name}=$newValue"
390         }
391       }
392       val changesInConfig =
393         if (changedFields.isNotEmpty()) changedFields.joinToString(", ") else "no changes"
394 
395       "Updated LeakCanary.config: Config($changesInConfig)"
396     }
397   }
398 
399   /**
400    * Returns a new [Intent] that can be used to programmatically launch the leak display activity.
401    */
402   fun newLeakDisplayActivityIntent() = LeakActivity.createHomeIntent(InternalLeakCanary.application)
403 
404   /**
405    * Dynamically shows / hides the launcher icon for the leak display activity.
406    * Note: you can change the default value by overriding the `leak_canary_add_launcher_icon`
407    * boolean resource:
408    *
409    * ```xml
410    * <?xml version="1.0" encoding="utf-8"?>
411    * <resources>
412    *   <bool name="leak_canary_add_launcher_icon">false</bool>
413    * </resources>
414    * ```
415    */
416   fun showLeakDisplayActivityLauncherIcon(showLauncherIcon: Boolean) {
417     InternalLeakCanary.setEnabledBlocking(
418       "leakcanary.internal.activity.LeakLauncherActivity", showLauncherIcon
419     )
420   }
421 
422   /**
423    * Immediately triggers a heap dump and analysis, if there is at least one retained instance
424    * tracked by [AppWatcher.objectWatcher]. If there are no retained instances then the heap will not
425    * be dumped and a notification will be shown instead.
426    */
427   fun dumpHeap() = InternalLeakCanary.onDumpHeapReceived(forceDump = true)
428 }
429