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 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-05 12:08:00 +01:00
commit 04b61fdd9e

View file

@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.Executors
/** /**
* Manages CameraX camera setup and controls. * Manages CameraX camera setup and controls.
@ -56,6 +57,9 @@ class CameraManager(private val context: Context) {
private val _isFrontCamera = MutableStateFlow(false) private val _isFrontCamera = MutableStateFlow(false)
val isFrontCamera: StateFlow<Boolean> = _isFrontCamera.asStateFlow() val isFrontCamera: StateFlow<Boolean> = _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 surfaceTextureProvider: (() -> SurfaceTexture?)? = null
private var surfaceSize: Size = Size(1920, 1080) private var surfaceSize: Size = Size(1920, 1080)
private var lifecycleOwnerRef: LifecycleOwner? = null 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 { fun getExecutor(): Executor {
return ContextCompat.getMainExecutor(context) return captureExecutor
} }
/** /**