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