LeakCanary.kt
TLDR
The file LeakCanary.kt
is the entry point API for LeakCanary, a memory leak detection library for Android. It provides a configuration data class and various methods for configuring and using LeakCanary.
Methods
No methods are defined in this file.
Classes
Config
The Config
class is a data class that represents the configuration options for LeakCanary. It includes various properties such as dumpHeap
, retainedVisibleThreshold
, and eventListeners
. Each property can be updated using the copy
function.
Builder
The Builder
class provides a convenient way to create a new Config
instance. It includes methods for setting the values of each property in the Config
class. The build
method returns a new Config
instance with the specified values.
LeakCanary
The LeakCanary
object is the main entry point for using LeakCanary. It includes a config
property that represents the current configuration for LeakCanary. This property can be updated at any time by replacing it with a new Config
instance.
The LeakCanary
object also includes various methods for interacting with LeakCanary. This includes creating a new Intent
to launch the leak display activity, showing or hiding the launcher icon for the leak display activity, and triggering a heap dump and analysis.
package leakcanary
import android.content.Intent
import leakcanary.LeakCanary.config
import leakcanary.internal.HeapDumpControl
import leakcanary.internal.InternalLeakCanary
import leakcanary.internal.InternalLeakCanary.FormFactor.TV
import leakcanary.internal.activity.LeakActivity
import shark.AndroidMetadataExtractor
import shark.AndroidObjectInspectors
import shark.AndroidReferenceMatchers
import shark.FilteringLeakingObjectFinder
import shark.HeapAnalysisSuccess
import shark.IgnoredReferenceMatcher
import shark.KeyedWeakReferenceFinder
import shark.LeakingObjectFinder
import shark.LibraryLeakReferenceMatcher
import shark.MetadataExtractor
import shark.ObjectInspector
import shark.ReferenceMatcher
import shark.SharkLog
/**
* The entry point API for LeakCanary. LeakCanary builds on top of [AppWatcher]. AppWatcher
* notifies LeakCanary of retained instances, which in turns dumps the heap, analyses it and
* publishes the results.
*
* LeakCanary can be configured by updating [config].
*/
object LeakCanary {
/**
* LeakCanary configuration data class. Properties can be updated via [copy].
*
* @see [config]
*/
data class Config(
/**
* Whether LeakCanary should dump the heap when enough retained instances are found. This needs
* to be true for LeakCanary to work, but sometimes you may want to temporarily disable
* LeakCanary (e.g. for a product demo).
*
* Defaults to true.
*/
val dumpHeap: Boolean = true,
/**
* If [dumpHeapWhenDebugging] is false then LeakCanary will not dump the heap
* when the debugger is attached. The debugger can create temporary memory leaks (for instance
* if a thread is blocked on a breakpoint).
*
* Defaults to false.
*/
val dumpHeapWhenDebugging: Boolean = false,
/**
* When the app is visible, LeakCanary will wait for at least
* [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
* freezes the UI and can be frustrating for developers who are trying to work. This is
* especially frustrating as the Android Framework has a number of leaks that cannot easily
* be fixed.
*
* When the app becomes invisible, LeakCanary dumps the heap after
* [AppWatcher.retainedDelayMillis] ms.
*
* The app is considered visible if it has at least one activity in started state.
*
* A higher threshold means LeakCanary will dump the heap less often, therefore it won't be
* bothering developers as much but it could miss some leaks.
*
* Defaults to 5.
*/
val retainedVisibleThreshold: Int = 5,
/**
* Known patterns of references in the heap, added here either to ignore them
* ([IgnoredReferenceMatcher]) or to mark them as library leaks ([LibraryLeakReferenceMatcher]).
*
* When adding your own custom [LibraryLeakReferenceMatcher] instances, you'll most
* likely want to set [LibraryLeakReferenceMatcher.patternApplies] with a filter that checks
* for the Android OS version and manufacturer. The build information can be obtained by calling
* [shark.AndroidBuildMirror.fromHeapGraph].
*
* Defaults to [AndroidReferenceMatchers.appDefaults]
*/
val referenceMatchers: List<ReferenceMatcher> = AndroidReferenceMatchers.appDefaults,
/**
* List of [ObjectInspector] that provide LeakCanary with insights about objects found in the
* heap. You can create your own [ObjectInspector] implementations, and also add
* a [shark.AppSingletonInspector] instance created with the list of internal singletons.
*
* Defaults to [AndroidObjectInspectors.appDefaults]
*/
val objectInspectors: List<ObjectInspector> = AndroidObjectInspectors.appDefaults,
/**
* Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata].
* Called on a background thread during heap analysis.
*
* Defaults to [AndroidMetadataExtractor]
*/
val metadataExtractor: MetadataExtractor = AndroidMetadataExtractor,
/**
* Whether to compute the retained heap size, which is the total number of bytes in memory that
* would be reclaimed if the detected leaks didn't happen. This includes native memory
* associated to Java objects (e.g. Android bitmaps).
*
* Computing the retained heap size can slow down the analysis because it requires navigating
* from GC roots through the entire object graph, whereas [shark.HeapAnalyzer] would otherwise
* stop as soon as all leaking instances are found.
*
* Defaults to true.
*/
val computeRetainedHeapSize: Boolean = true,
/**
* How many heap dumps are kept on the Android device for this app package. When this threshold
* is reached LeakCanary deletes the older heap dumps. As several heap dumps may be enqueued
* you should avoid going down to 1 or 2.
*
* Defaults to 7.
*/
val maxStoredHeapDumps: Int = 7,
/**
* LeakCanary always attempts to store heap dumps on the external storage if the
* WRITE_EXTERNAL_STORAGE is already granted, and otherwise uses the app storage.
* If the WRITE_EXTERNAL_STORAGE permission is not granted and
* [requestWriteExternalStoragePermission] is true, then LeakCanary will display a notification
* to ask for that permission.
*
* Defaults to false because that permission notification can be annoying.
*/
val requestWriteExternalStoragePermission: Boolean = false,
/**
* Finds the objects that are leaking, for which LeakCanary will compute leak traces.
*
* Defaults to [KeyedWeakReferenceFinder] which finds all objects tracked by a
* [KeyedWeakReference], ie all objects that were passed to
* [ObjectWatcher.expectWeaklyReachable].
*
* You could instead replace it with a [FilteringLeakingObjectFinder], which scans all objects
* in the heap dump and delegates the decision to a list of
* [FilteringLeakingObjectFinder.LeakingObjectFilter]. This can lead to finding more leaks
* than the default and shorter leak traces. This also means that every analysis during a
* given process life will bring up the same leaking objects over and over again, unlike
* when using [KeyedWeakReferenceFinder] (because [KeyedWeakReference] instances are cleared
* after each heap dump).
*
* The list of filters can be built from [AndroidObjectInspectors]:
*
* ```kotlin
* LeakCanary.config = LeakCanary.config.copy(
* leakingObjectFinder = FilteringLeakingObjectFinder(
* AndroidObjectInspectors.appLeakingObjectFilters
* )
* )
* ```
*/
val leakingObjectFinder: LeakingObjectFinder = KeyedWeakReferenceFinder,
/**
* Dumps the Java heap. You may replace this with your own implementation if you wish to
* change the core heap dumping implementation.
*/
val heapDumper: HeapDumper = AndroidDebugHeapDumper,
/**
* Listeners for LeakCanary events. See [EventListener.Event] for the list of events and
* which thread they're sent from. You most likely want to keep this list and add to it, or
* remove a few entries but not all entries. Each listener is independent and provides
* additional behavior which you can disable by not excluding it:
*
* ```kotlin
* // No cute canary toast (very sad!)
* LeakCanary.config = LeakCanary.config.run {
* copy(
* eventListeners = eventListeners.filter {
* it !is ToastEventListener
* }
* )
* }
* ```
*/
val eventListeners: List<EventListener> = listOf(
LogcatEventListener,
ToastEventListener,
LazyForwardingEventListener {
if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
},
when {
RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->
RemoteWorkManagerHeapAnalyzer
WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
else -> BackgroundThreadHeapAnalyzer
}
),
/**
* Whether to show LeakCanary notifications. When [showNotifications] is true, LeakCanary
* will only display notifications if the app is in foreground and is not an instant, TV or
* Wear app.
*
* Defaults to true.
*/
val showNotifications: Boolean = true,
) {
/**
* Construct a new Config via [LeakCanary.Config.Builder].
* Note: this method is intended to be used from Java code only. For idiomatic Kotlin use
* `copy()` to modify [LeakCanary.config].
*/
@Suppress("NEWER_VERSION_IN_SINCE_KOTLIN")
@SinceKotlin("999.9") // Hide from Kotlin code, this method is only for Java code
fun newBuilder() = Builder(this)
/**
* Builder for [LeakCanary.Config] intended to be used only from Java code.
*
* Usage:
* ```java
* LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
* .retainedVisibleThreshold(3)
* .build();
* LeakCanary.setConfig(config);
* ```
*
* For idiomatic Kotlin use `copy()` method instead:
* ```kotlin
* LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
* ```
*/
class Builder internal constructor(config: Config) {
private var dumpHeap = config.dumpHeap
private var dumpHeapWhenDebugging = config.dumpHeapWhenDebugging
private var retainedVisibleThreshold = config.retainedVisibleThreshold
private var referenceMatchers = config.referenceMatchers
private var objectInspectors = config.objectInspectors
private var metadataExtractor = config.metadataExtractor
private var computeRetainedHeapSize = config.computeRetainedHeapSize
private var maxStoredHeapDumps = config.maxStoredHeapDumps
private var requestWriteExternalStoragePermission =
config.requestWriteExternalStoragePermission
private var leakingObjectFinder = config.leakingObjectFinder
private var heapDumper = config.heapDumper
private var eventListeners = config.eventListeners
private var showNotifications = config.showNotifications
/** @see [LeakCanary.Config.dumpHeap] */
fun dumpHeap(dumpHeap: Boolean) =
apply { this.dumpHeap = dumpHeap }
/** @see [LeakCanary.Config.dumpHeapWhenDebugging] */
fun dumpHeapWhenDebugging(dumpHeapWhenDebugging: Boolean) =
apply { this.dumpHeapWhenDebugging = dumpHeapWhenDebugging }
/** @see [LeakCanary.Config.retainedVisibleThreshold] */
fun retainedVisibleThreshold(retainedVisibleThreshold: Int) =
apply { this.retainedVisibleThreshold = retainedVisibleThreshold }
/** @see [LeakCanary.Config.referenceMatchers] */
fun referenceMatchers(referenceMatchers: List<ReferenceMatcher>) =
apply { this.referenceMatchers = referenceMatchers }
/** @see [LeakCanary.Config.objectInspectors] */
fun objectInspectors(objectInspectors: List<ObjectInspector>) =
apply { this.objectInspectors = objectInspectors }
/** @see [LeakCanary.Config.metadataExtractor] */
fun metadataExtractor(metadataExtractor: MetadataExtractor) =
apply { this.metadataExtractor = metadataExtractor }
/** @see [LeakCanary.Config.computeRetainedHeapSize] */
fun computeRetainedHeapSize(computeRetainedHeapSize: Boolean) =
apply { this.computeRetainedHeapSize = computeRetainedHeapSize }
/** @see [LeakCanary.Config.maxStoredHeapDumps] */
fun maxStoredHeapDumps(maxStoredHeapDumps: Int) =
apply { this.maxStoredHeapDumps = maxStoredHeapDumps }
/** @see [LeakCanary.Config.requestWriteExternalStoragePermission] */
fun requestWriteExternalStoragePermission(requestWriteExternalStoragePermission: Boolean) =
apply { this.requestWriteExternalStoragePermission = requestWriteExternalStoragePermission }
/** @see [LeakCanary.Config.leakingObjectFinder] */
fun leakingObjectFinder(leakingObjectFinder: LeakingObjectFinder) =
apply { this.leakingObjectFinder = leakingObjectFinder }
/** @see [LeakCanary.Config.heapDumper] */
fun heapDumper(heapDumper: HeapDumper) =
apply { this.heapDumper = heapDumper }
/** @see [LeakCanary.Config.eventListeners] */
fun eventListeners(eventListeners: List<EventListener>) =
apply { this.eventListeners = eventListeners }
/** @see [LeakCanary.Config.showNotifications] */
fun showNotifications(showNotifications: Boolean) =
apply { this.showNotifications = showNotifications }
fun build() = config.copy(
dumpHeap = dumpHeap,
dumpHeapWhenDebugging = dumpHeapWhenDebugging,
retainedVisibleThreshold = retainedVisibleThreshold,
referenceMatchers = referenceMatchers,
objectInspectors = objectInspectors,
metadataExtractor = metadataExtractor,
computeRetainedHeapSize = computeRetainedHeapSize,
maxStoredHeapDumps = maxStoredHeapDumps,
requestWriteExternalStoragePermission = requestWriteExternalStoragePermission,
leakingObjectFinder = leakingObjectFinder,
heapDumper = heapDumper,
eventListeners = eventListeners,
showNotifications = showNotifications,
)
}
}
/**
* The current LeakCanary configuration. Can be updated at any time, usually by replacing it with
* a mutated copy, e.g.:
*
* ```kotlin
* LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 3)
* ```
*
* In Java, use [LeakCanary.Config.Builder] instead:
* ```java
* LeakCanary.Config config = LeakCanary.getConfig().newBuilder()
* .retainedVisibleThreshold(3)
* .build();
* LeakCanary.setConfig(config);
* ```
*/
@JvmStatic @Volatile
var config: Config = Config()
set(newConfig) {
val previousConfig = field
field = newConfig
logConfigChange(previousConfig, newConfig)
HeapDumpControl.updateICanHasHeapInBackground()
}
private fun logConfigChange(
previousConfig: Config,
newConfig: Config
) {
SharkLog.d {
val changedFields = mutableListOf<String>()
Config::class.java.declaredFields.forEach { field ->
field.isAccessible = true
val previousValue = field[previousConfig]
val newValue = field[newConfig]
if (previousValue != newValue) {
changedFields += "${field.name}=$newValue"
}
}
val changesInConfig =
if (changedFields.isNotEmpty()) changedFields.joinToString(", ") else "no changes"
"Updated LeakCanary.config: Config($changesInConfig)"
}
}
/**
* Returns a new [Intent] that can be used to programmatically launch the leak display activity.
*/
fun newLeakDisplayActivityIntent() = LeakActivity.createHomeIntent(InternalLeakCanary.application)
/**
* Dynamically shows / hides the launcher icon for the leak display activity.
* Note: you can change the default value by overriding the `leak_canary_add_launcher_icon`
* boolean resource:
*
* ```xml
* <?xml version="1.0" encoding="utf-8"?>
* <resources>
* <bool name="leak_canary_add_launcher_icon">false</bool>
* </resources>
* ```
*/
fun showLeakDisplayActivityLauncherIcon(showLauncherIcon: Boolean) {
InternalLeakCanary.setEnabledBlocking(
"leakcanary.internal.activity.LeakLauncherActivity", showLauncherIcon
)
}
/**
* Immediately triggers a heap dump and analysis, if there is at least one retained instance
* tracked by [AppWatcher.objectWatcher]. If there are no retained instances then the heap will not
* be dumped and a notification will be shown instead.
*/
fun dumpHeap() = InternalLeakCanary.onDumpHeapReceived(forceDump = true)
}