使用CameraXViewfinder与AnimatedVisibility时出现SurfaceView BufferQueue已弃用错误
问题原因
核心问题是UI重组导致CameraXViewfinder被重复创建:
- 所有UI元素(包括相机预览和覆盖层)都包裹在
uiState.surfaceRequest?.let块中,当isInfoScreenOpen变化触发UI重组时,整个let块会重新执行,CameraXViewfinder被重复渲染。 - 传入CameraXViewfinder的
surfaceRequest是已被处理过的旧实例,导致CameraX持有的Surface被销毁,触发SurfaceView BufferQueue has been abandoned错误,相机预览消失。
解决方案
1. 拆分状态收集,隔离相机预览的重组触发条件
将surfaceRequest和isInfoScreenOpen的状态分开收集,确保只有surfaceRequest变化时才重新渲染相机预览,isInfoScreenOpen变化仅影响提示栏:
@Composable fun CameraScreen( modifier: Modifier = Modifier, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, cameraViewModel: CameraViewModel = CameraViewModel(), ) { // 单独收集相机Surface请求状态,仅当它变化时触发相机预览重组 val surfaceRequest by cameraViewModel.uiState .map { it.surfaceRequest } .collectAsStateWithLifecycle() // 单独收集提示栏显示状态,变化时仅更新提示栏UI val isInfoScreenOpen by cameraViewModel.uiState .map { it.isInfoScreenOpen } .collectAsStateWithLifecycle() val context = LocalContext.current LaunchedEffect(lifecycleOwner) { cameraViewModel.bindToCamera(context.applicationContext, lifecycleOwner) } // 仅在Surface请求有效时渲染相机预览,不受提示栏状态变化影响 surfaceRequest?.let { request -> CameraXViewfinder( surfaceRequest = request, modifier = modifier.fillMaxSize() ) } // 覆盖层UI独立于相机预览渲染,避免触发相机组件重组 Box( contentAlignment = Alignment.BottomEnd, modifier = Modifier.fillMaxSize() ) { AnimatedVisibility(visible = isInfoScreenOpen) { // 给延迟关闭逻辑添加状态key,确保仅在提示栏显示时启动倒计时 LaunchedEffect(isInfoScreenOpen) { if (isInfoScreenOpen) { delay(5000) cameraViewModel.toggleInfoScreen() } } Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .padding( end = 48.dp, bottom = dimensionResource(R.dimen.padding_medium) ) .background(Color.LightGray.copy(alpha = 0.5f), MaterialTheme.shapes.small) .width(275.dp) ) { Text( text = "Scan a qr code or point at a place to see more details about it!", modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)) ) } } IconButton(onClick = cameraViewModel::toggleInfoScreen) { Icon( painter = painterResource(R.drawable.round_info_outline_24), contentDescription = "Info button", tint = Color.White ) } } }
2. 优化ViewModel中的SurfaceProvider逻辑(可选)
确保仅在CameraX真正需要新Surface时更新状态,避免不必要的UI触发:
private val cameraPreview = Preview.Builder().build().apply { setSurfaceProvider { newSurfaceRequest -> // 仅当新请求与当前状态中的实例不同时才更新 if (_uiState.value.surfaceRequest != newSurfaceRequest) { _uiState.update { it.copy(surfaceRequest = newSurfaceRequest) } } } }
3. 验证相机绑定生命周期
原代码中bindToCamera的协程逻辑是正确的:awaitCancellation()会在Composable销毁时触发unbindAll(),确保相机资源正确释放,无需修改。
为什么之前的尝试未解决?
- 拆分surfaceRequest时未正确隔离状态收集逻辑,导致相机预览仍会因其他状态变化触发重组。
- 移出非CameraX代码时未彻底分离状态依赖,UI仍会因整个
uiState变化而重新执行相机预览渲染。
内容的提问来源于stack exchange,提问作者Angel
相关产品推荐
相关产品推荐

