diff --git a/docs/changelog.md b/docs/changelog.md index 5a3a440f48..53c3954aaf 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -8,6 +8,7 @@ Please thank our [contributors](https://github.com/square/leakcanary/graphs/cont ### Heap Growth * Deleted the `shark-heap-growth` artifact, the code has been merged into the `shark*` and `leakcanary*` modules. +* Undo of breaking API changes that were introduced in alpha 1. The goal is to make the upgrade seamless. Please file an issue if you find an API breaking change from a 2.x release. * New `leakcanary-core` module that includes runtime leak detection utilities that aren't Android specific. * Optimization: for known data structures that don't reference the rest of the graph beyond the references we known about, we explore them locally at once and stop enqueuing their internals, which reduces the memory diff --git a/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api b/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api index 724b7e0c00..eefbbbf9fe 100644 --- a/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api +++ b/object-watcher/object-watcher-android-core/api/object-watcher-android-core.api @@ -1,5 +1,6 @@ public final class leakcanary/ActivityWatcher : leakcanary/InstallableWatcher { public fun (Landroid/app/Application;Lleakcanary/DeletableObjectReporter;)V + public fun (Landroid/app/Application;Lleakcanary/ReachabilityWatcher;)V public fun install ()V public fun uninstall ()V } @@ -33,15 +34,26 @@ public abstract interface class leakcanary/InstallableWatcher { } public final class leakcanary/RootViewWatcher : leakcanary/InstallableWatcher { - public fun (Lleakcanary/DeletableObjectReporter;Z)V - public synthetic fun (Lleakcanary/DeletableObjectReporter;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lleakcanary/DeletableObjectReporter;Lleakcanary/RootViewWatcher$Filter;)V + public synthetic fun (Lleakcanary/DeletableObjectReporter;Lleakcanary/RootViewWatcher$Filter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lleakcanary/ReachabilityWatcher;)V public fun install ()V public fun uninstall ()V } +public abstract interface class leakcanary/RootViewWatcher$Filter { + public abstract fun shouldExpectDeletionOnDetached (Landroid/view/View;)Z +} + +public final class leakcanary/RootViewWatcher$WindowTypeFilter : leakcanary/RootViewWatcher$Filter { + public fun (Z)V + public fun shouldExpectDeletionOnDetached (Landroid/view/View;)Z +} + public final class leakcanary/ServiceWatcher : leakcanary/InstallableWatcher { public static final field Companion Lleakcanary/ServiceWatcher$Companion; public fun (Lleakcanary/DeletableObjectReporter;)V + public fun (Lleakcanary/ReachabilityWatcher;)V public fun install ()V public fun uninstall ()V } diff --git a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ActivityWatcher.kt b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ActivityWatcher.kt index d867266e30..aac52b5d09 100644 --- a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ActivityWatcher.kt +++ b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ActivityWatcher.kt @@ -13,6 +13,12 @@ class ActivityWatcher( private val deletableObjectReporter: DeletableObjectReporter ) : InstallableWatcher { + // Kept for backward compatibility. + constructor( + application: Application, + reachabilityWatcher: ReachabilityWatcher + ) : this(application, reachabilityWatcher.asDeletableObjectReporter()) + private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { diff --git a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt index 110a378d2c..bd66a0baaf 100644 --- a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt +++ b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/AppWatcher.kt @@ -4,6 +4,7 @@ import android.app.Application import android.os.SystemClock import com.squareup.leakcanary.objectwatcher.core.R import java.util.concurrent.TimeUnit +import leakcanary.RootViewWatcher.WindowTypeFilter import leakcanary.internal.LeakCanaryDelegate import leakcanary.internal.friendly.checkMainThread import leakcanary.internal.friendly.mainHandler @@ -127,7 +128,7 @@ object AppWatcher { */ fun appDefaultWatchers( application: Application, - deletableObjectReporter: DeletableObjectReporter = objectWatcher + deletableObjectReporter: DeletableObjectReporter = objectWatcher.asDeletableObjectReporter() ): List { // Use app context resources to avoid NotFoundException // https://github.com/square/leakcanary/issues/2137 @@ -136,7 +137,7 @@ object AppWatcher { return listOf( ActivityWatcher(application, deletableObjectReporter), FragmentAndViewModelWatcher(application, deletableObjectReporter), - RootViewWatcher(deletableObjectReporter, watchDismissedDialogs), + RootViewWatcher(deletableObjectReporter, WindowTypeFilter(watchDismissedDialogs)), ServiceWatcher(deletableObjectReporter) ) } diff --git a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/RootViewWatcher.kt b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/RootViewWatcher.kt index f068a3bf17..aa0f350711 100644 --- a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/RootViewWatcher.kt +++ b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/RootViewWatcher.kt @@ -37,25 +37,39 @@ import leakcanary.internal.friendly.mainHandler */ class RootViewWatcher( private val deletableObjectReporter: DeletableObjectReporter, - private val watchDismissedDialogs: Boolean = false + private val rootViewFilter: Filter = WindowTypeFilter(watchDismissedDialogs = false) ) : InstallableWatcher { - private val listener = OnRootViewAddedListener { rootView -> - val trackDetached = when(rootView.windowType) { - PHONE_WINDOW -> { - when (rootView.phoneWindow?.callback?.wrappedCallback) { - // Activities are already tracked by ActivityWatcher - is Activity -> false - is Dialog -> watchDismissedDialogs - // Probably a DreamService - else -> true + fun interface Filter { + fun shouldExpectDeletionOnDetached(rootView: View): Boolean + } + + class WindowTypeFilter(private val watchDismissedDialogs: Boolean) : Filter { + override fun shouldExpectDeletionOnDetached(rootView: View): Boolean { + return when (rootView.windowType) { + PHONE_WINDOW -> { + when (rootView.phoneWindow?.callback?.wrappedCallback) { + // Activities are already tracked by ActivityWatcher + is Activity -> false + is Dialog -> watchDismissedDialogs + // Probably a DreamService + else -> true + } } + // Android widgets keep detached popup window instances around. + POPUP_WINDOW -> false + TOOLTIP, TOAST, UNKNOWN -> true } - // Android widgets keep detached popup window instances around. - POPUP_WINDOW -> false - TOOLTIP, TOAST, UNKNOWN -> true } - if (trackDetached) { + } + + // Kept for backward compatibility. + constructor(reachabilityWatcher: ReachabilityWatcher) : this( + deletableObjectReporter = reachabilityWatcher.asDeletableObjectReporter() + ) + + private val listener = OnRootViewAddedListener { rootView -> + if (rootViewFilter.shouldExpectDeletionOnDetached(rootView)) { rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener { val watchDetachedView = Runnable { diff --git a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ServiceWatcher.kt b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ServiceWatcher.kt index 48f28a3b26..c8db31706c 100644 --- a/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ServiceWatcher.kt +++ b/object-watcher/object-watcher-android-core/src/main/java/leakcanary/ServiceWatcher.kt @@ -5,19 +5,25 @@ import android.app.Service import android.os.Build import android.os.Handler import android.os.IBinder -import leakcanary.internal.friendly.checkMainThread -import shark.SharkLog import java.lang.ref.WeakReference import java.lang.reflect.InvocationTargetException import java.lang.reflect.Proxy import java.util.WeakHashMap +import leakcanary.internal.friendly.checkMainThread +import shark.SharkLog /** * Expects services to become weakly reachable soon after they receive the [Service.onDestroy] * callback. */ @SuppressLint("PrivateApi") -class ServiceWatcher(private val deletableObjectReporter: DeletableObjectReporter) : InstallableWatcher { +class ServiceWatcher(private val deletableObjectReporter: DeletableObjectReporter) : + InstallableWatcher { + + // Kept for backward compatibility. + constructor(reachabilityWatcher: ReachabilityWatcher) : this( + reachabilityWatcher.asDeletableObjectReporter() + ) private val servicesToBeDestroyed = WeakHashMap>() diff --git a/object-watcher/object-watcher/api/object-watcher.api b/object-watcher/object-watcher/api/object-watcher.api index 80a0281dff..3e6bd09a97 100644 --- a/object-watcher/object-watcher/api/object-watcher.api +++ b/object-watcher/object-watcher/api/object-watcher.api @@ -54,11 +54,11 @@ public final class leakcanary/ObjectWatcher : leakcanary/ReachabilityWatcher, le public fun (Lleakcanary/Clock;Ljava/util/concurrent/Executor;Lkotlin/jvm/functions/Function0;)V public synthetic fun (Lleakcanary/Clock;Ljava/util/concurrent/Executor;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addOnObjectRetainedListener (Lleakcanary/OnObjectRetainedListener;)V + public fun asDeletableObjectReporter ()Lleakcanary/DeletableObjectReporter; public fun clearAllObjectsTracked ()V public fun clearObjectsTrackedBefore-LRDsOJo (J)V public final fun clearObjectsWatchedBefore (J)V public final fun clearWatchedObjects ()V - public fun expectDeletionFor (Ljava/lang/Object;Ljava/lang/String;)Lleakcanary/TrackedObjectReachability; public fun expectWeaklyReachable (Ljava/lang/Object;Ljava/lang/String;)V public fun getHasRetainedObjects ()Z public fun getHasTrackedObjects ()Z @@ -75,13 +75,13 @@ public abstract interface class leakcanary/OnObjectRetainedListener { public abstract fun onObjectRetained ()V } -public abstract interface class leakcanary/ReachabilityWatcher : leakcanary/DeletableObjectReporter { - public abstract fun expectDeletionFor (Ljava/lang/Object;Ljava/lang/String;)Lleakcanary/TrackedObjectReachability; +public abstract interface class leakcanary/ReachabilityWatcher { + public abstract fun asDeletableObjectReporter ()Lleakcanary/DeletableObjectReporter; public abstract fun expectWeaklyReachable (Ljava/lang/Object;Ljava/lang/String;)V } public final class leakcanary/ReachabilityWatcher$DefaultImpls { - public static fun expectDeletionFor (Lleakcanary/ReachabilityWatcher;Ljava/lang/Object;Ljava/lang/String;)Lleakcanary/TrackedObjectReachability; + public static fun asDeletableObjectReporter (Lleakcanary/ReachabilityWatcher;)Lleakcanary/DeletableObjectReporter; } public final class leakcanary/ReferenceQueueRetainedObjectTracker : leakcanary/RetainedObjectTracker, leakcanary/TriggeredDeletableObjectReporter { diff --git a/object-watcher/object-watcher/src/main/java/leakcanary/DeletableObjectReporter.kt b/object-watcher/object-watcher/src/main/java/leakcanary/DeletableObjectReporter.kt index e694bf45b8..0b306f1de6 100644 --- a/object-watcher/object-watcher/src/main/java/leakcanary/DeletableObjectReporter.kt +++ b/object-watcher/object-watcher/src/main/java/leakcanary/DeletableObjectReporter.kt @@ -16,5 +16,26 @@ fun interface DeletableObjectReporter { fun expectDeletionFor( target: Any, reason: String - ) : TrackedObjectReachability + ): TrackedObjectReachability +} + +/** + * Creates a wrapper around [DeletableObjectReporter] that will run any instance of [T] by [apply] + * to decide whether to forward to [DeletableObjectReporter.expectDeletionFor] calls. Objects + * that do not extend [T] will always be forwarded. + */ +inline fun DeletableObjectReporter.filteringInstances( + crossinline apply: (T) -> Boolean +): DeletableObjectReporter { + val delegate = this + return DeletableObjectReporter { target, reason -> + if (target !is T || apply(target)) { + delegate.expectDeletionFor(target, reason) + } else object : TrackedObjectReachability { + override val isStronglyReachable: Boolean + get() = false + override val isRetained: Boolean + get() = false + } + } } diff --git a/object-watcher/object-watcher/src/main/java/leakcanary/ReachabilityWatcher.kt b/object-watcher/object-watcher/src/main/java/leakcanary/ReachabilityWatcher.kt index 12a2fb2ad2..d4bf4a2ba7 100644 --- a/object-watcher/object-watcher/src/main/java/leakcanary/ReachabilityWatcher.kt +++ b/object-watcher/object-watcher/src/main/java/leakcanary/ReachabilityWatcher.kt @@ -1,7 +1,7 @@ package leakcanary @Deprecated("Use DeletableObjectReporter instead", ReplaceWith("DeletableObjectReporter")) -fun interface ReachabilityWatcher : DeletableObjectReporter { +fun interface ReachabilityWatcher { /** * Expects the provided [watchedObject] to become weakly reachable soon. If not, @@ -12,20 +12,16 @@ fun interface ReachabilityWatcher : DeletableObjectReporter { description: String ) - /** - * This method exists for backward-compatibility purposes and as such is unable to return - * an accurate [TrackedObjectReachability] implementation. - */ - override fun expectDeletionFor( - target: Any, - reason: String - ): TrackedObjectReachability { - expectWeaklyReachable(target, reason) - return object : TrackedObjectReachability { - override val isStronglyReachable: Boolean - get() = error("Use a non deprecated DeletableObjectReporter implementation instead") - override val isRetained: Boolean - get() = error("Use a non deprecated DeletableObjectReporter implementation instead") + fun asDeletableObjectReporter(): DeletableObjectReporter = + DeletableObjectReporter { target, reason -> + expectWeaklyReachable(target, reason) + // This exists for backward-compatibility purposes and as such is unable to return + // an accurate [TrackedObjectReachability] implementation. + object : TrackedObjectReachability { + override val isStronglyReachable: Boolean + get() = error("Use a non deprecated DeletableObjectReporter implementation instead") + override val isRetained: Boolean + get() = error("Use a non deprecated DeletableObjectReporter implementation instead") + } } - } } diff --git a/samples/leakcanary-android-sample/src/debug/java/com/example/leakcanary/ExampleSetup.kt b/samples/leakcanary-android-sample/src/debug/java/com/example/leakcanary/ExampleSetup.kt index 41eae8b021..e1a55596a3 100644 --- a/samples/leakcanary-android-sample/src/debug/java/com/example/leakcanary/ExampleSetup.kt +++ b/samples/leakcanary-android-sample/src/debug/java/com/example/leakcanary/ExampleSetup.kt @@ -75,7 +75,7 @@ class ExampleSetup { // Or maybe just delete support for support lib. FragmentAndViewModelWatcher(application, deletableObjectReporter), // TODO should configure this - RootViewWatcher(deletableObjectReporter, false), + RootViewWatcher(deletableObjectReporter), ServiceWatcher(deletableObjectReporter) )