<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