3. CameraX PreviewView 源码解读:内部实现、SurfaceProvider 接口与 ViewPort 配置流程
PreviewView 是 CameraX 预览能力的核心 UI 组件,它承担着将相机数据流正确、高效地绘制到界面上的任务。理解 PreviewView 的内部实现、SurfaceProvider 接口契约以及 ViewPort 配置流程,是解决预览黑屏、画面变形、旋转适配问题的关键基础。本章从源码角度逐一拆解这三个模块的协作关系。
3.1 PreviewView 内部实现
PreviewView 继承自 FrameLayout,在构造时并不直接绑定相机数据,而是维护一个 Surface 消费者 的内部实现。核心源码结构如下:
// PreviewView 实际包含一个 PreviewViewMeteringPointFactory
// 内部使用 PreviewTransformation 管理缩放与裁剪
// 通过实现 SurfaceProvider 接口来接收相机 Surfaces
关键设计决策:
- Surface 与 Texture 自动切换:内部根据设备 API 级别和硬件加速支持,自动选择使用
SurfaceView或TextureView作为渲染目标。在 API 24+ 优先使用 SurfaceView 以获得更低延迟。 - 坐标系映射:
PreviewViewMeteringPointFactory负责将触摸坐标从 PreviewView 坐标系转换到相机传感器坐标系,结合 ViewPort 计算实际点击区域。 - ScaleType 管理:
PreviewTransformation类根据用户设置的scaleType(如 FILL_CENTER、FIT_CENTER、FILL_START 等)计算内部矩阵,对预览画面进行缩放与裁剪。
| 内部类 / 属性 | 功能 |
|---|---|
PreviewViewMeteringPointFactory | 坐标转换:UI → 传感器坐标 |
PreviewTransformation | 缩放 + 裁剪矩阵计算 |
SurfaceOutput | 实际绑定的 Surface 包装对象 |
setPreferredImplementationMode | 允许强制指定 SurfaceView 或 TextureView |
内部机制结论:PreviewView 本身不直接持有 Surface,而是通过 SurfaceProvider 接口向 CameraX 请求 Surface,获取后交给内部 View 渲染。Surface 的创建与释放完全由 CameraX 控制,PreviewView 仅负责显示已准备好的 Surface 内容。
3.2 SurfaceProvider 接口剖析
SurfaceProvider 是 PreviewView 与 CameraX 核心管线之间的关键桥梁。CameraX 在准备预览时,会调用 SurfaceProvider.onSurfaceRequested() 来获取一个 Surface 用于创建 PreviewOutput。接口定义:
// CameraX 内部接口(位于 Preview.SurfaceProvider)
// 开发者通常不需要直接实现此接口,PreviewView 已内置实现
public interface SurfaceProvider {
void onSurfaceRequested(@NonNull SurfaceRequest request);
}
// SurfaceRequest 包含分辨率、预期旋转、回调等
public final class SurfaceRequest {
// 提供一个 Surface 给 CameraX
void provideSurface(@NonNull Surface surface, @NonNull Executor executor,
@NonNull SurfaceCallback callback);
// 通知 CameraX 无法提供 Surface(例如 Activity 已销毁)
void willNotProvideSurface();
}
PreviewView 内部实现 SurfaceProvider 时,工作流程如下:
- CameraX 调用
onSurfaceRequested,传入一个SurfaceRequest对象。 - PreviewView 内部根据当前选择的渲染模式创建 Surface(SurfaceView 的 holder.surface 或 TextureView 的 SurfaceTexture)。
- 通过
request.provideSurface(surface, executor, callback)将 Surface 返回给 CameraX。 - 当 CameraX 不再需要该 Surface(如预览停止),会通过
SurfaceCallback回调通知 PreviewView 释放。
常见陷阱:如果在
onSurfaceRequested 回调中延迟超过几秒才调用 provideSurface,CameraX 会认为提供失败并自动切换为“黑屏”状态。开发自定义 SurfaceProvider 时必须确保及时回调。3.3 ViewPort 配置流程
ViewPort 用于定义相机预览画面在 PreviewView 上的裁剪区域与缩放方式。其核心属性:
// CameraX ViewPort 构建示例
ViewPort viewPort = new ViewPort.Builder(
new Rational(dstWidth, dstHeight), // 目标宽高比(如 1920:1080)
rotation) // 设备旋转角度(0/90/180/270)
.setScaleType(ViewPort.FILL_CENTER) // 缩放类型
.build();
配置流程分为四步:
- 设置 PreviewView 尺寸:Layout 布局后,PreviewView 的宽高会传递给 CameraX。
- 构建 ViewPort:通过
PreviewView.getViewPort()获取当前 PreviewView 的推荐 ViewPort(内部自动计算宽高比与旋转)。 - 绑定到 Preview 用例:将 ViewPort 设置到
Preview对象上:preview.setViewPort(viewPort); - CameraX 内部解析:CameraX 根据 ViewPort 裁剪传感器输出区域,并缩放至 PreviewView 提供的 Surface 分辨率。
缩放类型(ScaleType)对显示效果的影响对比:
| ScaleType | 效果 | 适用场景 |
|---|---|---|
FILL_CENTER | 等比例缩放,裁剪超出 PreviewView 的部分,居中显示 | 视频通话、全屏预览(减少黑边) |
FIT_CENTER | 等比例缩放,保持完整画面,上下或左右留黑边 | 图片拍摄预览(保证全部内容可见) |
FILL_START | 同 FILL_CENTER,但裁剪边不对齐中心,从左上角开始 | 特定 UI 对齐需求 |
3.4 调试与常见错误
在实际集成中,最频繁出现的问题集中在 Surface 提供时机与 ViewPort 宽高比不匹配:
- 黑屏:检查
SurfaceRequest.willNotProvideSurface()是否被误调用;或者provideSurface传入的 Surface 格式不被 CameraX 支持(需为MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT)。 - 画面拉伸:ViewPort 的宽高比与 PreviewView 的实际显示尺寸不一致。建议使用
PreviewView.getViewPort()自动计算,而非手动构建。 - 旋转方向错误:ViewPort 的
rotation必须与设备的自然方向(Display.getRotation())对齐,否则预览会颠倒。
// 推荐的生产级配置
Preview preview = new Preview.Builder()
.setViewPort( previewView.getViewPort() ) // 委托 PreviewView 自动计算
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
性能优化要点:当使用 SurfaceView 模式时,Surface 的生命周期与 Activity 的窗口分离,在 onResume 之前即可产生 Surface,预览启动速度比 TextureView 提高约 30%。若必须使用 TextureView,建议将
setPreferredImplementationMode 设为 PERFORMANCE 以启用硬件缓冲区。