From 04a7f082b02bd4fbc6d81abb8c77765fc8c3a3a9 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Thu, 11 May 2023 14:23:35 -0700 Subject: [PATCH] Update LeakCanary to 2.10 Also added the now required "description" parameter when watching instances. Fixes #580 --- .../java/com/uber/rib/SampleApplication.java | 17 ++++--------- android/gradle/libs.versions.toml | 2 +- .../kotlin/com/uber/rib/core/RibRefWatcher.kt | 19 ++++++++++++--- .../main/kotlin/com/uber/rib/core/Router.kt | 5 +++- .../uber/rib/core/InteractorAndRouterTest.kt | 8 +++---- .../com/uber/rib/core/RibRefWatcherTest.kt | 24 +++++++++---------- 6 files changed, 41 insertions(+), 34 deletions(-) diff --git a/android/demos/memory-leaks/src/main/java/com/uber/rib/SampleApplication.java b/android/demos/memory-leaks/src/main/java/com/uber/rib/SampleApplication.java index 712589905..39c25026a 100644 --- a/android/demos/memory-leaks/src/main/java/com/uber/rib/SampleApplication.java +++ b/android/demos/memory-leaks/src/main/java/com/uber/rib/SampleApplication.java @@ -16,12 +16,10 @@ package com.uber.rib; import android.app.Application; -import com.squareup.leakcanary.LeakCanary; -import com.squareup.leakcanary.RefWatcher; import com.uber.rib.core.ActivityDelegate; import com.uber.rib.core.HasActivityDelegate; import com.uber.rib.core.RibRefWatcher; -import java.util.concurrent.TimeUnit; +import leakcanary.AppWatcher; public class SampleApplication extends Application implements HasActivityDelegate { @@ -31,24 +29,17 @@ public class SampleApplication extends Application implements HasActivityDelegat public void onCreate() { activityDelegate = new SampleActivityDelegate(); super.onCreate(); - if (!LeakCanary.isInAnalyzerProcess(this)) { - // This process is dedicated to LeakCanary for heap analysis. You should not init your app in - // this process. - installLeakCanary(); - } + installLeakCanary(); } /** Install leak canary for both activities and RIBs. */ private void installLeakCanary() { - final RefWatcher refWatcher = - LeakCanary.refWatcher(this).watchDelay(2, TimeUnit.SECONDS).buildAndInstall(); - LeakCanary.install(this); RibRefWatcher.getInstance() .setReferenceWatcher( new RibRefWatcher.ReferenceWatcher() { @Override - public void watch(Object object) { - refWatcher.watch(object); + public void watch(Object object, String description) { + AppWatcher.INSTANCE.getObjectWatcher().expectWeaklyReachable(object, description); } @Override diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index db0fa935e..54b4c6753 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -36,7 +36,7 @@ kotlin = "1.8.20" kotlinx-coroutines = "1.7.3" ktfmt = "0.43" ktlint = "0.48.2" -leakcanary = "1.5.4" +leakcanary = "2.10" mockito = "4.6.1" mockito-kotlin = "4.0.0" motif = "0.3.4" diff --git a/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/RibRefWatcher.kt b/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/RibRefWatcher.kt index 8ed91004a..6605a1849 100644 --- a/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/RibRefWatcher.kt +++ b/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/RibRefWatcher.kt @@ -34,17 +34,30 @@ public open class RibRefWatcher { referenceWatcher = watcher } + @Deprecated( + "Add the description parameter", + replaceWith = ReplaceWith("watchDeletedObject(objectToWatch, description)"), + ) + public open fun watchDeletedObject( + objectToWatch: Any?, + ) { + watchDeletedObject(objectToWatch, "missing description") + } + /** * Watch this object to verify it has no inbound references. * * @param objectToWatch the object to watch. */ - public open fun watchDeletedObject(objectToWatch: Any?) { + public open fun watchDeletedObject( + objectToWatch: Any?, + description: String, + ) { if (objectToWatch == null) { return } if (isLeakCanaryEnabled || uLeakEnabled) { - referenceWatcher?.watch(objectToWatch) + referenceWatcher?.watch(objectToWatch, description) } } @@ -97,7 +110,7 @@ public open class RibRefWatcher { * * @param objectToWatch the object to watch. */ - public fun watch(objectToWatch: Any) + public fun watch(objectToWatch: Any, description: String) /** * Method to pipe breadcrumbs into the Breadcrumb logger. diff --git a/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/Router.kt b/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/Router.kt index 0e381bbc6..86f5e44a9 100644 --- a/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/Router.kt +++ b/android/libraries/rib-base/src/main/kotlin/com/uber/rib/core/Router.kt @@ -150,7 +150,10 @@ protected constructor( public open fun detachChild(childRouter: Router<*>) { val isChildRemoved = children.remove(childRouter) val interactor = childRouter.interactor - ribRefWatcher.watchDeletedObject(interactor) + ribRefWatcher.watchDeletedObject( + interactor, + "detached child router ${childRouter.javaClass.name}", + ) ribRefWatcher.logBreadcrumb( "DETACHED", childRouter.javaClass.simpleName, diff --git a/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/InteractorAndRouterTest.kt b/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/InteractorAndRouterTest.kt index a23c365d2..cb03e9c59 100644 --- a/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/InteractorAndRouterTest.kt +++ b/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/InteractorAndRouterTest.kt @@ -187,13 +187,13 @@ class InteractorAndRouterTest { val childInteractor = TestInteractorB() val childRouter = TestRouterB(childInteractor, component) router.attachChild(childRouter) - verify(ribRefWatcher, never()).watchDeletedObject(any()) + verify(ribRefWatcher, never()).watchDeletedObject(any(), "") // Action: Detach the child interactor. router.detachChild(childRouter) // Verify: the reference watcher observes the detached interactor and child. - verify(ribRefWatcher).watchDeletedObject(eq(childInteractor)) + verify(ribRefWatcher).watchDeletedObject(eq(childInteractor), "") } @Test @@ -207,13 +207,13 @@ class InteractorAndRouterTest { } val rootRouter = TestRouterB(component, TestInteractorB(), ribRefWatcher) val child = addTwoNestedChildInteractors() - verify(ribRefWatcher, never()).watchDeletedObject(any()) + verify(ribRefWatcher, never()).watchDeletedObject(any(), "") // Action: Detach all child interactors. rootRouter.detachChild(child) // Verify: called four times. Twice for each interactor. - verify(ribRefWatcher, times(2)).watchDeletedObject(any()) + verify(ribRefWatcher, times(2)).watchDeletedObject(any(), "") } private fun addTwoNestedChildInteractors(): Router { diff --git a/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/RibRefWatcherTest.kt b/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/RibRefWatcherTest.kt index b3c58fffc..fb4d8c70c 100644 --- a/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/RibRefWatcherTest.kt +++ b/android/libraries/rib-base/src/test/kotlin/com/uber/rib/core/RibRefWatcherTest.kt @@ -36,14 +36,14 @@ class RibRefWatcherTest { fun watchDeletedObject_whenObjectIsNull_shouldDoNothing() { ribRefWatcher.enableLeakCanary() ribRefWatcher.setReferenceWatcher(referenceWatcher) - ribRefWatcher.watchDeletedObject(null) + ribRefWatcher.watchDeletedObject(null, "") verifyNoInteractions(referenceWatcher) } @Test fun watchDeletedObject_whenReferenceWatcherIsNull_shouldDoNothing() { ribRefWatcher.enableLeakCanary() - ribRefWatcher.watchDeletedObject(Any()) + ribRefWatcher.watchDeletedObject(Any(), "") verifyNoInteractions(referenceWatcher) } @@ -52,30 +52,30 @@ class RibRefWatcherTest { ribRefWatcher.enableLeakCanary() val obj = Any() ribRefWatcher.setReferenceWatcher(referenceWatcher) - ribRefWatcher.watchDeletedObject(obj) - verify(referenceWatcher).watch(obj) + ribRefWatcher.watchDeletedObject(obj, "") + verify(referenceWatcher).watch(obj, "") } @Test fun watchDeletedObject_whenNonNullRefWithDisabledLeakCanary_shouldDoNothing() { val obj = Any() ribRefWatcher.setReferenceWatcher(referenceWatcher) - ribRefWatcher.watchDeletedObject(obj) - verify(referenceWatcher, never()).watch(obj) + ribRefWatcher.watchDeletedObject(obj, "") + verify(referenceWatcher, never()).watch(obj, "") } @Test fun watchDeletedObject_whenObjectIsNullWithULeak_shouldDoNothing() { ribRefWatcher.enableULeakLifecycleTracking() ribRefWatcher.setReferenceWatcher(referenceWatcher) - ribRefWatcher.watchDeletedObject(null) + ribRefWatcher.watchDeletedObject(null, "") verifyNoInteractions(referenceWatcher) } @Test fun watchDeletedObject_whenReferenceWatcherIsNullULeakEnabled_shouldDoNothing() { ribRefWatcher.enableULeakLifecycleTracking() - ribRefWatcher.watchDeletedObject(Any()) + ribRefWatcher.watchDeletedObject(Any(), "") verifyNoInteractions(referenceWatcher) } @@ -84,15 +84,15 @@ class RibRefWatcherTest { ribRefWatcher.enableULeakLifecycleTracking() val obj = Any() ribRefWatcher.setReferenceWatcher(referenceWatcher) - ribRefWatcher.watchDeletedObject(obj) - verify(referenceWatcher).watch(obj) + ribRefWatcher.watchDeletedObject(obj, "") + verify(referenceWatcher).watch(obj, "") } @Test fun watchDeletedObject_whenNonNullRefULeakDisabled_shouldDoNothing() { val obj = Any() ribRefWatcher.setReferenceWatcher(referenceWatcher) - ribRefWatcher.watchDeletedObject(obj) - verify(referenceWatcher, never()).watch(obj) + ribRefWatcher.watchDeletedObject(obj, "") + verify(referenceWatcher, never()).watch(obj, "") } }