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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:23:16