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
相关产品推荐
相关产品推荐

