You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose中navigation-compose与CameraX搭配使用崩溃问题咨询

问题根因
  • 直接在Composable顶层执行cameraProviderFuture.get():该方法为阻塞方法,会直接阻塞主线程,Future未完成时调用会抛出ExecutionException/InterruptedException,导航操作会触发NavHost页面重组,此时调用该方法大概率直接触发崩溃
  • 相机核心实例(cameraProvider、imageCapture、camera、previewView)未使用remember包裹:Composable触发重组时这些实例会被重新初始化,导致点击拍照时持有的imageCapture为无效实例,甚至直接为空
  • previewView直接在Composable顶层初始化:未和AndroidView的factory生命周期绑定,多次重组会创建多个PreviewView实例,和相机绑定的实例不一致
  • 所用依赖均为早期alpha版本:navigation-compose 2.4.0-alpha09和camerax 1.1.0-alpha08本身存在大量已知生命周期适配、状态同步类bug
修复方案

1. 相机核心实例加remember存储,避免重组丢失

@ExperimentalCoilApi
@Composable
fun CameraPreviewScreen(
    modifier: Modifier = Modifier,
    context: Context,
    lifecycleOwner: LifecycleOwner,
    outPutDirectory: File,
    onMediaCaptured: (Uri?) -> Unit
) {
    val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
    val executor = remember { ContextCompat.getMainExecutor(context) }
    var imageCapture by remember { mutableStateOf<ImageCapture?>(null) }
    var camera by remember { mutableStateOf<Camera?>(null) }
    val previewView = remember { PreviewView(context) }
    val preview = remember { 
        Preview.Builder().build().also {
            it.setSurfaceProvider(previewView.surfaceProvider)
        } 
    }

    var lensFacing by remember { mutableStateOf(CameraSelector.LENS_FACING_BACK) }
    var flashEnabled by remember { mutableStateOf(false) }
    var flashRes by remember { mutableStateOf(R.drawable.ic_outlined_flash_on) }

2. 移除顶层阻塞的get调用,所有相机操作放到异步回调中

Box {
        AndroidView(
            factory = { ctx ->
                cameraProviderFuture.addListener(
                    {
                        // 仅在Future回调中调用get,避免阻塞主线程
                        val cameraProvider = cameraProviderFuture.get()
                        val cameraSelector = CameraSelector.Builder()
                            .requireLensFacing(lensFacing)
                            .build()
                        val newImageCapture = ImageCapture.Builder()
                            .setTargetRotation(previewView.display.rotation)
                            .build()
                        imageCapture = newImageCapture

                        try {
                            cameraProvider.unbindAll()
                            camera = cameraProvider.bindToLifecycle(
                                lifecycleOwner,
                                cameraSelector,
                                newImageCapture,
                                preview
                            )
                        } catch (e: Exception) {
                            Log.e("CameraX", "相机绑定失败", e)
                        }
                    }, executor
                )
                previewView
            },
            modifier = modifier
        )

3. 调整切换摄像头逻辑,避免操作未初始化的实例

// 底部切换摄像头按钮逻辑修改
        IconButton(
            onClick = {
                val newLensFacing = if (lensFacing == CameraSelector.LENS_FACING_BACK) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK
                lensFacing = newLensFacing
                cameraProviderFuture.addListener({
                    val cameraProvider = cameraProviderFuture.get()
                    val cameraSelector = CameraSelector.Builder()
                        .requireLensFacing(newLensFacing)
                        .build()
                    val currentImageCapture = imageCapture ?: return@addListener
                    try {
                        cameraProvider.unbindAll()
                        camera = cameraProvider.bindToLifecycle(
                            lifecycleOwner,
                            cameraSelector,
                            currentImageCapture,
                            preview
                        )
                    } catch (e: Exception) {
                        Log.e("CameraX", "切换摄像头失败", e)
                    }
                }, executor)
            }) {
            Icon(
                modifier = Modifier.size(34.dp),
                painter = painterResource(id = R.drawable.ic_outlined_rotate),
                contentDescription = null,
                tint = MaterialTheme.colors.surface
            )
        }

4. 拍照回调增加生命周期判断,避免页面销毁后触发导航

override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
    Log.d(TAG, "image saved: ${photoFile.path}")
    // 仅页面处于活跃状态时触发回调导航
    if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
        onMediaCaptured(Uri.fromFile(photoFile))
    }
}

5. 依赖版本升级

建议将依赖至少升级到正式稳定版,避免alpha版本自带bug:

  • navigation-compose升级到2.4.1及以上稳定版
  • camerax相关库升级到1.2.0及以上稳定版

内容的提问来源于stack exchange,提问作者Annon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 19:15:02