1 package leakcanary
2 
3 import android.app.Activity
4 import android.app.Application
5 import android.os.Build.VERSION.SDK_INT
6 import android.os.Build.VERSION_CODES.O
7 import android.os.Bundle
8 import leakcanary.internal.AndroidOFragmentDestroyWatcher
9 import leakcanary.internal.friendly.noOpDelegate
10 
11 /**
12  * Expects:
13  * - Fragments (Support Library, Android X and AOSP) to become weakly reachable soon after they
14  * receive the Fragment#onDestroy() callback.
15  * - Fragment views (Support Library, Android X and AOSP) to become weakly reachable soon after
16  * fragments receive the Fragment#onDestroyView() callback.
17  * - Android X view models (both activity and fragment view models) to become weakly reachable soon
18  * after they received the ViewModel#onCleared() callback.
19  */
20 class FragmentAndViewModelWatcher(
21   private val application: Application,
22   private val reachabilityWatcher: ReachabilityWatcher
23 ) : InstallableWatcher {
24 
<lambda>null25   private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
26     val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
27 
28     if (SDK_INT >= O) {
29       fragmentDestroyWatchers.add(
30         AndroidOFragmentDestroyWatcher(reachabilityWatcher)
31       )
32     }
33 
34     getWatcherIfAvailable(
35       ANDROIDX_FRAGMENT_CLASS_NAME,
36       ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
37       reachabilityWatcher
38     )?.let {
39       fragmentDestroyWatchers.add(it)
40     }
41 
42     getWatcherIfAvailable(
43       ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
44       ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
45       reachabilityWatcher
46     )?.let {
47       fragmentDestroyWatchers.add(it)
48     }
49     fragmentDestroyWatchers
50   }
51 
52   private val lifecycleCallbacks =
53     object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
onActivityCreatednull54       override fun onActivityCreated(
55         activity: Activity,
56         savedInstanceState: Bundle?
57       ) {
58         for (watcher in fragmentDestroyWatchers) {
59           watcher(activity)
60         }
61       }
62     }
63 
installnull64   override fun install() {
65     application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
66   }
67 
uninstallnull68   override fun uninstall() {
69     application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
70   }
71 
getWatcherIfAvailablenull72   private fun getWatcherIfAvailable(
73     fragmentClassName: String,
74     watcherClassName: String,
75     reachabilityWatcher: ReachabilityWatcher
76   ): ((Activity) -> Unit)? {
77 
78     return if (classAvailable(fragmentClassName) &&
79       classAvailable(watcherClassName)
80     ) {
81       val watcherConstructor =
82         Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
83       @Suppress("UNCHECKED_CAST")
84       watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit
85     } else {
86       null
87     }
88   }
89 
classAvailablenull90   private fun classAvailable(className: String): Boolean {
91     return try {
92       Class.forName(className)
93       true
94     } catch (e: Throwable) {
95       // e is typically expected to be a ClassNotFoundException
96       // Unfortunately, prior to version 25.0.2 of the support library the
97       // FragmentManager.FragmentLifecycleCallbacks class was a non static inner class.
98       // Our AndroidSupportFragmentDestroyWatcher class is compiled against the static version of
99       // the FragmentManager.FragmentLifecycleCallbacks class, leading to the
100       // AndroidSupportFragmentDestroyWatcher class being rejected and a NoClassDefFoundError being
101       // thrown here. So we're just covering our butts here and catching everything, and assuming
102       // any throwable means "can't use this". See https://github.com/square/leakcanary/issues/1662
103       false
104     }
105   }
106 
107   companion object {
108     private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"
109     private const val ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
110       "leakcanary.internal.AndroidXFragmentDestroyWatcher"
111 
112     // Using a string builder to prevent Jetifier from changing this string to Android X Fragment
113     @Suppress("VariableNaming", "PropertyName")
114     private val ANDROID_SUPPORT_FRAGMENT_CLASS_NAME =
115       StringBuilder("android.").append("support.v4.app.Fragment")
116         .toString()
117     private const val ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME =
118       "leakcanary.internal.AndroidSupportFragmentDestroyWatcher"
119   }
120 }
121