Destroy dep tree on RecentsView detached. This prevents leaks.
Scenario 1 was already working, this CL fixes scenario 2 and leaves scenario 3 broken.
3 Scenarios:
Scenario 1 (occurs in real usage):
Launcher instance 1 init
Launcher instance 2 init
Launcher instance 1 destroy
Launcher instance 2 destroy
Scenario 2 (occurs in testStressPressHome):
Launcher instance 1 init
Launcher instance 1 destroy
Launcher instance 2 init
Launcher instance 2 destroy
Scenario 3 (does not currently happen):
Launcher instance 1 init
Launcher instance 2 init
Launcher instance 2 destroy
Launcher instance 1 destroy
Fix: 381160061
Flag: com.android.launcher3.enable_refactor_task_thumbnail
Test: TaplStartLauncherViaGestureTests#testStressPressHome
Change-Id: I7c82ce97e7a7f0698b9adad38f59e4555f0d1bdc
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index b78e214..53bacd0 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -253,13 +253,17 @@
private const val DEFAULT_SCOPE_ID = "RecentsDependencies::GlobalScope"
private const val TAG = "RecentsDependencies"
private const val DEBUG = false
+ private var activeRecentsCount = 0
@Volatile private lateinit var instance: RecentsDependencies
fun initialize(view: View): RecentsDependencies = initialize(view.context)
fun initialize(context: Context): RecentsDependencies {
- synchronized(this) { instance = RecentsDependencies(context.applicationContext) }
+ synchronized(this) {
+ activeRecentsCount++
+ instance = RecentsDependencies(context.applicationContext)
+ }
return instance
}
@@ -273,9 +277,29 @@
return instance
}
+ @JvmStatic
fun destroy() {
- instance.scopes.clear()
- instance.startDefaultScope(instance.appContext)
+ // When Launcher Activity restarts, the old view's RecentsView.onDetachedFromWindow
+ // happens after the new view's creation. This means that destroy can be called after a
+ // new initialisation. This check prevents a newly initialised tree from being
+ // destroyed. Ideally we would have 1 instance of the dependency tree for each
+ // RecentsView.
+ //
+ // This check is sufficient to avoid a leak of the dependency tree after the Activity is
+ // destroyed while also allowing Launcher auto-restarts (production behaviour) to easily
+ // reinitialise the dependency tree.
+ //
+ // TODO(b/353917593): Better lifecycle decisions will be implemented in this bug or when
+ // replacing with Dagger (b/371370483).
+ activeRecentsCount--
+ if (activeRecentsCount == 0) {
+ instance.scopes.clear()
+ } else {
+ instance.log(
+ "RecentsDependencies was not destroyed. " +
+ "There is still an active RecentsView instance."
+ )
+ }
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3b46367..b965d94 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1236,6 +1236,7 @@
reset();
if (enableRefactorTaskThumbnail()) {
mHelper.onDetachedFromWindow();
+ RecentsDependencies.destroy();
}
}