package leakcanary import android.app.Application import android.os.SystemClock import java.util.concurrent.TimeUnit import leakcanary.AppWatcher.objectWatcher import leakcanary.internal.LeakCanaryDelegate import leakcanary.internal.friendly.checkMainThread import leakcanary.internal.friendly.mainHandler import leakcanary.internal.isDebuggableBuild /** * The entry point API for using [ObjectWatcher] in an Android app. [AppWatcher.objectWatcher] is * in charge of detecting retained objects, and [AppWatcher] is auto configured on app start to * pass it activity and fragment instances. Call [ObjectWatcher.watch] on [objectWatcher] to * watch any other object that you expect to be unreachable. */ object AppWatcher { private const val RETAINED_DELAY_NOT_SET = -1L @Volatile var retainedDelayMillis = RETAINED_DELAY_NOT_SET private set private var installCause: Exception? = null /** * The [ObjectWatcher] used by AppWatcher to detect retained objects. * Only set when [isInstalled] is true. */ val objectWatcher = ObjectWatcher( clock = { SystemClock.uptimeMillis() }, checkRetainedExecutor = { check(isInstalled) { "AppWatcher not installed" } mainHandler.postDelayed(it, retainedDelayMillis) }, isEnabled = { true } ) /** @see [manualInstall] */ val isInstalled: Boolean get() = installCause != null /** * Enables usage of [AppWatcher.objectWatcher] which will expect passed in objects to become * weakly reachable within [retainedDelayMillis] ms and if not will trigger LeakCanary (if * LeakCanary is in the classpath). * * In the main process, this method is automatically called with default parameter values on app * startup. You can call this method directly to customize the installation, however you must * first disable the automatic call by overriding the `leak_canary_watcher_auto_install` boolean * resource: * * ```xml * * * false * * ``` * * [watchersToInstall] can be customized to a subset of the default app watchers: * * ```kotlin * val watchersToInstall = AppWatcher.appDefaultWatchers(application) * .filter { it !is RootViewWatcher } * AppWatcher.manualInstall( * application = application, * watchersToInstall = watchersToInstall * ) * ``` * * [watchersToInstall] can also be customized to ignore specific instances (e.g. here ignoring * leaks of BadSdkLeakingFragment): * * ```kotlin * val watchersToInstall = AppWatcher.appDefaultWatchers(application, ReachabilityWatcher { watchedObject, description -> * if (watchedObject !is BadSdkLeakingFragment) { * AppWatcher.objectWatcher.expectWeaklyReachable(watchedObject, description) * } * }) * AppWatcher.manualInstall( * application = application, * watchersToInstall = watchersToInstall * ) * ``` */ @JvmOverloads fun manualInstall( application: Application, retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), watchersToInstall: List = appDefaultWatchers(application) ) { checkMainThread() if (isInstalled) { throw IllegalStateException( "AppWatcher already installed, see exception cause for prior install call", installCause ) } check(retainedDelayMillis >= 0) { "retainedDelayMillis $retainedDelayMillis must be at least 0 ms" } this.retainedDelayMillis = retainedDelayMillis if (application.isDebuggableBuild) { LogcatSharkLog.install() } // Requires AppWatcher.objectWatcher to be set LeakCanaryDelegate.loadLeakCanary(application) watchersToInstall.forEach { it.install() } // Only install after we're fully done with init. installCause = RuntimeException("manualInstall() first called here") } /** * Creates a new list of default app [InstallableWatcher], created with the passed in * [reachabilityWatcher] (which defaults to [objectWatcher]). Once installed, * these watchers will pass in to [reachabilityWatcher] objects that they expect to become * weakly reachable. * * The passed in [reachabilityWatcher] should probably delegate to [objectWatcher] but can * be used to filter out specific instances. */ fun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher = objectWatcher ): List { return listOf( ActivityWatcher(application, reachabilityWatcher), FragmentAndViewModelWatcher(application, reachabilityWatcher), RootViewWatcher(reachabilityWatcher), ServiceWatcher(reachabilityWatcher) ) } @Deprecated("Call AppWatcher.manualInstall() ") data class Config( @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") val watchActivities: Boolean = true, @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") val watchFragments: Boolean = true, @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") val watchFragmentViews: Boolean = true, @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list") val watchViewModels: Boolean = true, @Deprecated("Call AppWatcher.manualInstall() with a custom retainedDelayMillis value") val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5), @Deprecated("Call AppWatcher.appDefaultWatchers() with a custom ReachabilityWatcher") val enabled: Boolean = true ) { @Deprecated("Configuration moved to AppWatcher.manualInstall()", replaceWith = ReplaceWith("")) @Suppress("NEWER_VERSION_IN_SINCE_KOTLIN") @SinceKotlin("999.9") // Hide from Kotlin code, this method is only for Java code fun newBuilder(): Builder = Builder(this) @Deprecated("Configuration moved to XML resources") class Builder internal constructor(config: Config) { private var watchActivities = config.watchActivities private var watchFragments = config.watchFragments private var watchFragmentViews = config.watchFragmentViews private var watchViewModels = config.watchViewModels private var watchDurationMillis = config.watchDurationMillis /** Deprecated. @see [Config.enabled] */ @Deprecated("see [Config.enabled]", replaceWith = ReplaceWith("")) fun enabled(enabled: Boolean) = this /** @see [Config.watchActivities] */ @Deprecated("see [Config.watchActivities]", replaceWith = ReplaceWith("")) fun watchActivities(watchActivities: Boolean) = apply { this.watchActivities = watchActivities } @Deprecated("see [Config.watchFragments]", replaceWith = ReplaceWith("")) /** @see [Config.watchFragments] */ fun watchFragments(watchFragments: Boolean) = apply { this.watchFragments = watchFragments } @Deprecated("see [Config.watchFragmentViews]", replaceWith = ReplaceWith("")) /** @see [Config.watchFragmentViews] */ fun watchFragmentViews(watchFragmentViews: Boolean) = apply { this.watchFragmentViews = watchFragmentViews } @Deprecated("see [Config.watchViewModels]", replaceWith = ReplaceWith("")) /** @see [Config.watchViewModels] */ fun watchViewModels(watchViewModels: Boolean) = apply { this.watchViewModels = watchViewModels } @Deprecated("see [Config.watchDurationMillis]", replaceWith = ReplaceWith("")) /** @see [Config.watchDurationMillis] */ fun watchDurationMillis(watchDurationMillis: Long) = apply { this.watchDurationMillis = watchDurationMillis } @Deprecated("Configuration moved to AppWatcher.manualInstall()") fun build() = config.copy( watchActivities = watchActivities, watchFragments = watchFragments, watchFragmentViews = watchFragmentViews, watchViewModels = watchViewModels, watchDurationMillis = watchDurationMillis ) } } @Deprecated("Configuration moved to AppWatcher.manualInstall()") @JvmStatic @Volatile var config: Config = Config() }