1 package leakcanary
2 
3 import android.app.Application
4 import android.os.SystemClock
5 import java.util.concurrent.TimeUnit
6 import leakcanary.AppWatcher.objectWatcher
7 import leakcanary.internal.LeakCanaryDelegate
8 import leakcanary.internal.friendly.checkMainThread
9 import leakcanary.internal.friendly.mainHandler
10 import leakcanary.internal.isDebuggableBuild
11 
12 /**
13  * The entry point API for using [ObjectWatcher] in an Android app. [AppWatcher.objectWatcher] is
14  * in charge of detecting retained objects, and [AppWatcher] is auto configured on app start to
15  * pass it activity and fragment instances. Call [ObjectWatcher.watch] on [objectWatcher] to
16  * watch any other object that you expect to be unreachable.
17  */
18 object AppWatcher {
19 
20   private const val RETAINED_DELAY_NOT_SET = -1L
21 
22   @Volatile
23   var retainedDelayMillis = RETAINED_DELAY_NOT_SET
24     private set
25 
26   private var installCause: Exception? = null
27 
28   /**
29    * The [ObjectWatcher] used by AppWatcher to detect retained objects.
30    * Only set when [isInstalled] is true.
31    */
32   val objectWatcher = ObjectWatcher(
<lambda>null33     clock = { SystemClock.uptimeMillis() },
<lambda>null34     checkRetainedExecutor = {
35       check(isInstalled) {
36         "AppWatcher not installed"
37       }
38       mainHandler.postDelayed(it, retainedDelayMillis)
39     },
<lambda>null40     isEnabled = { true }
41   )
42 
43   /** @see [manualInstall] */
44   val isInstalled: Boolean
45     get() = installCause != null
46 
47   /**
48    * Enables usage of [AppWatcher.objectWatcher] which will expect passed in objects to become
49    * weakly reachable within [retainedDelayMillis] ms and if not will trigger LeakCanary (if
50    * LeakCanary is in the classpath).
51    *
52    * In the main process, this method is automatically called with default parameter values  on app
53    * startup. You can call this method directly to customize the installation, however you must
54    * first disable the automatic call by overriding the `leak_canary_watcher_auto_install` boolean
55    * resource:
56    *
57    * ```xml
58    * <?xml version="1.0" encoding="utf-8"?>
59    * <resources>
60    *   <bool name="leak_canary_watcher_auto_install">false</bool>
61    * </resources>
62    * ```
63    *
64    * [watchersToInstall] can be customized to a subset of the default app watchers:
65    *
66    * ```kotlin
67    * val watchersToInstall = AppWatcher.appDefaultWatchers(application)
68    *   .filter { it !is RootViewWatcher }
69    * AppWatcher.manualInstall(
70    *   application = application,
71    *   watchersToInstall = watchersToInstall
72    * )
73    * ```
74    *
75    * [watchersToInstall] can also be customized to ignore specific instances (e.g. here ignoring
76    * leaks of BadSdkLeakingFragment):
77    *
78    * ```kotlin
79    * val watchersToInstall = AppWatcher.appDefaultWatchers(application, ReachabilityWatcher { watchedObject, description ->
80    *   if (watchedObject !is BadSdkLeakingFragment) {
81    *     AppWatcher.objectWatcher.expectWeaklyReachable(watchedObject, description)
82    *   }
83    * })
84    * AppWatcher.manualInstall(
85    *   application = application,
86    *   watchersToInstall = watchersToInstall
87    * )
88    * ```
89    */
90   @JvmOverloads
manualInstallnull91   fun manualInstall(
92     application: Application,
93     retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
94     watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
95   ) {
96     checkMainThread()
97     if (isInstalled) {
98       throw IllegalStateException(
99         "AppWatcher already installed, see exception cause for prior install call", installCause
100       )
101     }
102     check(retainedDelayMillis >= 0) {
103       "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
104     }
105     this.retainedDelayMillis = retainedDelayMillis
106     if (application.isDebuggableBuild) {
107       LogcatSharkLog.install()
108     }
109     // Requires AppWatcher.objectWatcher to be set
110     LeakCanaryDelegate.loadLeakCanary(application)
111 
112     watchersToInstall.forEach {
113       it.install()
114     }
115     // Only install after we're fully done with init.
116     installCause = RuntimeException("manualInstall() first called here")
117   }
118 
119   /**
120    * Creates a new list of default app [InstallableWatcher], created with the passed in
121    * [reachabilityWatcher] (which defaults to [objectWatcher]). Once installed,
122    * these watchers will pass in to [reachabilityWatcher] objects that they expect to become
123    * weakly reachable.
124    *
125    * The passed in [reachabilityWatcher] should probably delegate to [objectWatcher] but can
126    * be used to filter out specific instances.
127    */
appDefaultWatchersnull128   fun appDefaultWatchers(
129     application: Application,
130     reachabilityWatcher: ReachabilityWatcher = objectWatcher
131   ): List<InstallableWatcher> {
132     return listOf(
133       ActivityWatcher(application, reachabilityWatcher),
134       FragmentAndViewModelWatcher(application, reachabilityWatcher),
135       RootViewWatcher(reachabilityWatcher),
136       ServiceWatcher(reachabilityWatcher)
137     )
138   }
139 
140   @Deprecated("Call AppWatcher.manualInstall() ")
141   data class Config(
142     @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
143     val watchActivities: Boolean = true,
144 
145     @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
146     val watchFragments: Boolean = true,
147 
148     @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
149     val watchFragmentViews: Boolean = true,
150 
151     @Deprecated("Call AppWatcher.manualInstall() with a custom watcher list")
152     val watchViewModels: Boolean = true,
153 
154     @Deprecated("Call AppWatcher.manualInstall() with a custom retainedDelayMillis value")
155     val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),
156 
157     @Deprecated("Call AppWatcher.appDefaultWatchers() with a custom ReachabilityWatcher")
158     val enabled: Boolean = true
159   ) {
160 
161     @Deprecated("Configuration moved to AppWatcher.manualInstall()", replaceWith = ReplaceWith(""))
162     @Suppress("NEWER_VERSION_IN_SINCE_KOTLIN")
163     @SinceKotlin("999.9") // Hide from Kotlin code, this method is only for Java code
newBuildernull164     fun newBuilder(): Builder = Builder(this)
165 
166     @Deprecated("Configuration moved to XML resources")
167     class Builder internal constructor(config: Config) {
168       private var watchActivities = config.watchActivities
169       private var watchFragments = config.watchFragments
170       private var watchFragmentViews = config.watchFragmentViews
171       private var watchViewModels = config.watchViewModels
172       private var watchDurationMillis = config.watchDurationMillis
173 
174       /** Deprecated. @see [Config.enabled] */
175       @Deprecated("see [Config.enabled]", replaceWith = ReplaceWith(""))
176       fun enabled(enabled: Boolean) = this
177 
178       /** @see [Config.watchActivities] */
179       @Deprecated("see [Config.watchActivities]", replaceWith = ReplaceWith(""))
180       fun watchActivities(watchActivities: Boolean) =
181         apply { this.watchActivities = watchActivities }
182 
183       @Deprecated("see [Config.watchFragments]", replaceWith = ReplaceWith(""))
184         /** @see [Config.watchFragments] */
185       fun watchFragments(watchFragments: Boolean) =
186         apply { this.watchFragments = watchFragments }
187 
188       @Deprecated("see [Config.watchFragmentViews]", replaceWith = ReplaceWith(""))
189         /** @see [Config.watchFragmentViews] */
190       fun watchFragmentViews(watchFragmentViews: Boolean) =
191         apply { this.watchFragmentViews = watchFragmentViews }
192 
193       @Deprecated("see [Config.watchViewModels]", replaceWith = ReplaceWith(""))
194         /** @see [Config.watchViewModels] */
195       fun watchViewModels(watchViewModels: Boolean) =
196         apply { this.watchViewModels = watchViewModels }
197 
198       @Deprecated("see [Config.watchDurationMillis]", replaceWith = ReplaceWith(""))
199         /** @see [Config.watchDurationMillis] */
200       fun watchDurationMillis(watchDurationMillis: Long) =
201         apply { this.watchDurationMillis = watchDurationMillis }
202 
203       @Deprecated("Configuration moved to AppWatcher.manualInstall()")
204       fun build() = config.copy(
205         watchActivities = watchActivities,
206         watchFragments = watchFragments,
207         watchFragmentViews = watchFragmentViews,
208         watchViewModels = watchViewModels,
209         watchDurationMillis = watchDurationMillis
210       )
211     }
212   }
213 
214   @Deprecated("Configuration moved to AppWatcher.manualInstall()")
215   @JvmStatic @Volatile
216   var config: Config = Config()
217 
218 }
219