Fix several issues that retained WatchFaceService
It appears the WakeLock can sometimes retain the WatchFaceService,
adding a release() call fixes this. Also the StateFlows can
retain WatchFaceService, cancelling the underlying CoroutineScopes
fixes that.
Test: Manual testing with AndroidStudio profiler
Bug: 237294238
Change-Id: I9296f53be46a96c30a76a094f94ad216ba03dbeb
(cherry picked from commit 403c9bf65a3866e99e4d26d12b9c0364c92aa05c)
Merged-In: I9296f53be46a96c30a76a094f94ad216ba03dbeb
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index c4ade27..07e5020 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -617,6 +617,8 @@
}
}
+ internal open fun cancelCoroutineScopesInOnDestroy() = true
+
/**
* This is open for use by tests, it allows them to inject a custom [SurfaceHolder].
* @hide
@@ -1518,8 +1520,8 @@
}
// NB user code could throw an exception so do this last.
- runBlocking {
- try {
+ try {
+ runBlocking {
// The WatchFaceImpl is created on the UiThread so if we get here and it's not
// created we can be sure it'll never be created hence we don't need to destroy
// it.
@@ -1530,17 +1532,34 @@
watchFaceInitDetails
.await().watchFace.renderer.onDestroy()
}
- } catch (e: Exception) {
- // Throwing an exception here leads to a cascade of errors, log instead.
- Log.e(
- TAG,
- "WatchFace exception observed in onDestroy (may have occurred during init)",
- e
- )
+ }
+ } catch (e: Exception) {
+ // Throwing an exception here leads to a cascade of errors, log instead.
+ Log.e(
+ TAG,
+ "WatchFace exception observed in onDestroy (may have occurred during init)",
+ e
+ )
+ } finally {
+ if (this@EngineWrapper::ambientUpdateWakelock.isInitialized) {
+ // Make sure the WakeLock doesn't retain the WatchFaceService.
+ ambientUpdateWakelock.release()
+ }
+
+ // StateFlows may retain WatchFaceService via the coroutineScope. Call cancel to
+ // ensure resources are released. Headless watch faces call cancelCoroutineScopes
+ // themselves since they call onDestroy from a coroutine context.
+ if (cancelCoroutineScopesInOnDestroy() && !mutableWatchState.isHeadless) {
+ cancelCoroutineScopes()
}
}
}
+ internal fun cancelCoroutineScopes() {
+ uiThreadCoroutineScope.cancel()
+ backgroundThreadCoroutineScope.cancel()
+ }
+
override fun onSurfaceDestroyed(holder: SurfaceHolder) {
surfaceDestroyed = true
}
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
index 40dfec1..461887f 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
@@ -133,7 +133,8 @@
engine = null
}
}
- }
+ engineCopy
+ }.cancelCoroutineScopes()
}
}
}
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index 7a01898..8646fdb 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -77,6 +77,12 @@
override fun forceIsVisibleForTesting() = forceIsVisible
+ /**
+ * [WatchFaceService.EngineWrapper.onDestroy] is called more than once in some tests which is a
+ * problem due to using a CoroutineScope after it's been cancelled leading to exceptions.
+ */
+ override fun cancelCoroutineScopesInOnDestroy() = false
+
fun reset() {
clearTappedState()
complicationSelected = null