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

Android 11下导航含相机Composable时应用崩溃问题求助

问题分析与解决方案

核心原因

从日志中的sp<> assignment detected data race以及Android 11专属崩溃的表现来看,问题根源在于CameraX 1.1.0与Navigation Compose在Android 11下的生命周期资源竞争:

  • Navigation Compose切换页面时,旧相机Composable可能未完全销毁,CameraX的bindToLifecycle与unbindAll()操作在多线程下产生数据竞争;
  • Android 12+系统对相机服务的资源管理做了兼容性优化,规避了该问题;
  • 无NavController时相机功能正常,说明问题直接关联Navigation对Composable生命周期的调度逻辑。

修复步骤

1. 升级CameraX依赖到稳定新版本

CameraX 1.1.0存在Android 11下的生命周期绑定已知问题,升级到1.3.0+可修复大部分底层兼容性问题:

dependencies {
    implementation "androidx.camera:camera-camera2:1.3.0"
    implementation "androidx.camera:camera-lifecycle:1.3.0"
    implementation "androidx.camera:camera-view:1.3.0"
    // 其他依赖保持不变
}

2. 用DisposableEffect严格管理相机生命周期

原代码中相机绑定逻辑写在AndroidView的factory里,无法感知Composable的销毁时机,改用DisposableEffect确保相机资源在Composable销毁时同步解绑:

@Composable
fun QRCodeScanner(onQRCodeScanned: (String) -> Unit) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val previewView = remember { PreviewView(context) }
    var cameraProvider: ProcessCameraProvider? by remember { mutableStateOf(null) }

    // 初始化CameraProvider
    LaunchedEffect(Unit) {
        ProcessCameraProvider.getInstance(context).also { future ->
            future.addListener({
                cameraProvider = future.get()
            }, ContextCompat.getMainExecutor(context))
        }
    }

    // 绑定/解绑相机,与Composable生命周期同步
    DisposableEffect(lifecycleOwner, cameraProvider) {
        val provider = cameraProvider ?: return@DisposableEffect onDispose {}

        val preview = Preview.Builder().build().apply {
            setSurfaceProvider(previewView.surfaceProvider)
        }
        val imageAnalysis = ImageAnalysis.Builder()
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .build().apply {
                setAnalyzer(ContextCompat.getMainExecutor(context), QRCodeAnalyzer(onQRCodeScanned))
            }

        try {
            provider.unbindAll()
            provider.bindToLifecycle(
                lifecycleOwner,
                CameraSelector.DEFAULT_BACK_CAMERA,
                preview,
                imageAnalysis
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }

        // Composable销毁时解绑所有相机用例
        onDispose {
            provider.unbindAll()
        }
    }

    AndroidView(
        factory = { previewView },
        modifier = Modifier.fillMaxSize()
    )
}

3. 优化Navigation切换逻辑,避免重复入栈

原导航逻辑每次点击都会触发navigate,导致栈中存在多个相机页面实例,加剧资源竞争。修改为点击已选中项时不重复导航,且确保导航时清理旧栈:

NavigationBar {
    navigationItems.forEachIndexed { _, item ->
        val isSelected = currentDestination?.hierarchy?.any { it.route == item.route } == true
        NavigationBarItem(
            selected = isSelected,
            onClick = {
                if (!isSelected) {
                    navController.navigate(item.route) {
                        // 清除当前栈到根,避免多个相机实例
                        popUpTo(navController.graph.startDestinationId) {
                            saveState = true
                        }
                        // 避免重复添加相同目的地
                        launchSingleTop = true
                        // 恢复之前保存的状态
                        restoreState = true
                    }
                }
            },
            icon = { /* 原有图标逻辑 */ },
            label = { /* 原有标签逻辑 */ }
        )
    }
}

额外验证点

  • 开启R8混淆时,确保CameraX相关类未被错误优化,添加ProGuard规则:
    -keep class androidx.camera.** { *; }
    -keep interface androidx.camera.** { *; }
    
  • 若崩溃仍存在,可在相机页面添加LifecycleEventObserver,监听ON_DESTROY事件时主动调用cameraProvider?.unbindAll(),确保资源释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:37:32