diff --git a/.jules/bolt.md b/.jules/bolt.md index 3f1dfa4..b7fdb2c 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -7,3 +7,6 @@ ## 2024-08-02 - Debouncing and offloading state derivation in Jetpack Compose **Learning:** In Jetpack Compose, computing expensive operations (like filtering large lists based on user input) directly within a synchronous `remember` block blocks the main UI thread. When this happens on every keystroke in a `TextField`, it causes significant UI stutter and input lag. **Action:** To prevent UI stutter during expensive state derivation, debounce the input using `LaunchedEffect` with `delay` and offload the computation to a background thread using `withContext(Dispatchers.Default)`. This keeps the main thread responsive for typing and animations. +## 2024-11-20 - Binder IPC call optimization in loops +**Learning:** Querying a dynamic property from a system service (like `MediaRouter.routeCount`) makes a Binder IPC call under the hood. Doing this repeatedly inside a loop condition (e.g., `0 until mediaRouter.routeCount`) introduces significant overhead because the IPC call is evaluated on every iteration. +**Action:** Extract dynamic properties that involve IPC calls to a local variable (e.g., `val count = mediaRouter.routeCount`) before looping, rather than querying them in the loop condition, especially if the count doesn't change during the loop iteration. This can yield massive performance gains (e.g., 6x faster). diff --git a/app/src/main/java/com/rockmusic/app/player/SystemAudioRouteController.kt b/app/src/main/java/com/rockmusic/app/player/SystemAudioRouteController.kt index 3995ebe..74868b9 100644 --- a/app/src/main/java/com/rockmusic/app/player/SystemAudioRouteController.kt +++ b/app/src/main/java/com/rockmusic/app/player/SystemAudioRouteController.kt @@ -22,7 +22,9 @@ class SystemAudioRouteController @Inject constructor( val mediaRouter = router ?: return emptyList() val selected = mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO) val currentRoutes = buildList { - for (routerIndex in 0 until mediaRouter.routeCount) { + // Extract routeCount to avoid repeated Binder IPC calls in the loop + val routeCount = mediaRouter.routeCount + for (routerIndex in 0 until routeCount) { val route = mediaRouter.getRouteAt(routerIndex) if (route.supportedTypes and MediaRouter.ROUTE_TYPE_LIVE_AUDIO != 0) add(route) } @@ -55,7 +57,9 @@ class SystemAudioRouteController @Inject constructor( val mediaRouter = router ?: error("Audio output routing is unavailable on this device") val route = routesById[index] ?: error("The selected audio route is no longer available") - val isStillAvailable = (0 until mediaRouter.routeCount) + // Extract routeCount to avoid repeated Binder IPC calls in the loop + val routeCount = mediaRouter.routeCount + val isStillAvailable = (0 until routeCount) .any { routerIndex -> mediaRouter.getRouteAt(routerIndex) === route } require(isStillAvailable) { "The selected audio route is no longer available" } require(route.isEnabled) { "The selected audio route is disabled" }