From 04b61fdd9eeb5281d61c409a22efa3e9553b37e8 Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Thu, 5 Mar 2026 12:08:00 +0100 Subject: [PATCH] Move capture callbacks to background executor Replace ContextCompat.getMainExecutor with a dedicated single-thread executor for image capture callbacks. This prevents bitmap decode, rotation, and tilt-shift effect processing from blocking the UI thread and causing jank or ANR on slower devices. Co-Authored-By: Claude Opus 4.6 --- .../java/no/naiv/tiltshift/camera/CameraManager.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt b/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt index 3717693..20104bd 100644 --- a/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt +++ b/app/src/main/java/no/naiv/tiltshift/camera/CameraManager.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import java.util.concurrent.Executor +import java.util.concurrent.Executors /** * Manages CameraX camera setup and controls. @@ -56,6 +57,9 @@ class CameraManager(private val context: Context) { private val _isFrontCamera = MutableStateFlow(false) val isFrontCamera: StateFlow = _isFrontCamera.asStateFlow() + /** Background executor for image capture callbacks to avoid blocking the main thread. */ + private val captureExecutor: Executor = Executors.newSingleThreadExecutor() + private var surfaceTextureProvider: (() -> SurfaceTexture?)? = null private var surfaceSize: Size = Size(1920, 1080) private var lifecycleOwnerRef: LifecycleOwner? = null @@ -194,10 +198,12 @@ class CameraManager(private val context: Context) { } /** - * Gets the executor for image capture callbacks. + * Gets the background executor for image capture callbacks. + * Uses a dedicated thread to avoid blocking the main/UI thread during heavy + * bitmap processing (decode, rotate, tilt-shift effect). */ fun getExecutor(): Executor { - return ContextCompat.getMainExecutor(context) + return captureExecutor } /**