第3章:PreviewView在Compose中的封装
PreviewView 是 CameraX 在传统 View 体系中提供的核心预览组件。在 Jetpack Compose 环境下,需要通过 AndroidView 将其嵌入到可组合函数中,并完成生命周期绑定与尺寸适配。本章将深入讲解这三步的核心原理与标准实现。
3.1 封装原理
- AndroidView 桥梁:
AndroidView是 Compose 与 View 系统的互操作节点,允许在组合函数中创建并展示 View 实例。 - 生命周期绑定:PreviewView 需要感知 Activity/Fragment 的生命周期以正确启动/停止相机预览,通过
ProcessCameraProvider.getInstance(context)获取 provider 后,调用cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)完成绑定。 - 尺寸适配:PreviewView 的
scaleType决定了预览画面在控件内的拉伸、裁剪或填充方式;在 Compose 中需通过Modifier.fillMaxSize()或自定义尺寸让 PreviewView 与容器对齐。
核心契约:PreviewView 的尺寸、生命周期所有者、CameraX 实例三者必须同时就绪,预览才能稳定输出。任何一环缺失都会导致黑屏、崩溃或预览错位。
3.2 实现流程
- 依赖注入:在
build.gradle.kts中添加 CameraX 库依赖。 - 组合函数中创建
AndroidView,工厂函数内实例化PreviewView。 - 在
update回调(或LaunchedEffect)中获取ProcessCameraProvider,配置Preview用例并绑定到当前LocalLifecycleOwner。 - 设置
PreviewView.scaleType为ScaleType.FILL_CENTER或FIT_CENTER等,控制画面适配方式。 - 调用
cameraProvider.unbindAll()做清理(可选)。
3.3 关键代码实现
以下是一个完整的 Compose 函数,演示了 PreviewView 的封装、生命周期绑定与尺寸适配:
@Composable
fun CameraPreviewView(
context: Context = LocalContext.current,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
AndroidView(
factory = { ctx ->
PreviewView(ctx).apply {
scaleType = PreviewView.ScaleType.FILL_CENTER
}
},
modifier = Modifier.fillMaxSize(),
update = { previewView ->
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview
)
} catch (e: Exception) {
Log.e("CameraX", "绑定失败", e)
}
}, ContextCompat.getMainExecutor(context))
}
)
}
注意:update 回调会在重组时多次触发。建议将 cameraProvider 的获取和绑定操作放入 LaunchedEffect(Unit) 中执行,以避免重复绑定。上述代码仅为演示最简单路径,生产环境应加入 remember 或 DisposableEffect 进行生命周期管理。
3.4 生命周期绑定细节
| 绑定元素 | 作用 | 常见错误 |
|---|---|---|
LocalLifecycleOwner |
提供当前界面的生命周期状态 | 使用 context as LifecycleOwner 强转导致 Activity 泄漏 |
ProcessCameraProvider |
管理 CameraX 生命周期与用例绑定 | 未调用 unbindAll() 重复绑定导致异常 |
Preview.SurfaceProvider |
将预览数据流输出到 PreviewView 的 Surface | 在 onDestroy 后仍然持有 provider 引用 |
3.5 尺寸适配方案对比
| ScaleType | 效果 | 适用场景 |
|---|---|---|
FILL_CENTER |
填充整个控件,裁切超出部分 | 全屏预览,需画面充满屏幕 |
FIT_CENTER |
保持比例缩放到控件内,可能出现黑边 | 固定比例预览,避免裁剪 |
FILL_START |
填充并从左上角对齐 | 异形屏或自定义布局 |
FILL_END |
填充并从右下角对齐 | 底部对齐的悬浮预览 |
3.6 调试与常见错误
- 黑屏:未获取到相机权限或
SurfaceProvider未设置。检查CameraProvider.bindToLifecycle是否成功。 - 预览画面拉伸:
scaleType与控件尺寸比例不匹配。推荐使用FILL_CENTER并配合Modifier.aspectRatio(ratio)固定比例。 - 崩溃:”Camera is in use”:重复调用了
bindToLifecycle而未先unbindAll()。 - 生命周期泄漏:
ProcessCameraProvider的监听器中直接持有 Activity 引用。确保使用WeakReference或Lifecycle.Event做清理。
性能警告:避免在 AndroidView 的 factory 中执行耗时操作(如 CAMERA 权限检查),应提前在 LaunchedEffect 中异步请求。PreviewView 实例复用可减少 Surface 重建开销,建议使用 remember 保持单个实例。
3.7 架构流程示意
3.8 稳定性与优化建议
- 使用 DisposableEffect 管理资源:在
DisposableEffect的onDispose中调用cameraProvider.unbindAll(),确保离开界面时释放相机。 - 异步初始化:
ProcessCameraProvider.getInstance()是异步操作,不要在factory中阻塞等待。 - 预览帧率控制:如果预览卡顿,考虑设置
Preview.Builder().setTargetFrameRate(30)限制帧率。 - 避免重复创建 PreviewView:将
PreviewView实例写在remember中,防止重组时重新构造 Surface。