3. CameraX PreviewView源码解读

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 时,工作流程如下:

  1. CameraX 调用 onSurfaceRequested,传入一个 SurfaceRequest 对象。
  2. PreviewView 内部根据当前选择的渲染模式创建 Surface(SurfaceView 的 holder.surface 或 TextureView 的 SurfaceTexture)。
  3. 通过 request.provideSurface(surface, executor, callback) 将 Surface 返回给 CameraX。
  4. 当 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();

配置流程分为四步:

  1. 设置 PreviewView 尺寸:Layout 布局后,PreviewView 的宽高会传递给 CameraX。
  2. 构建 ViewPort:通过 PreviewView.getViewPort() 获取当前 PreviewView 的推荐 ViewPort(内部自动计算宽高比与旋转)。
  3. 绑定到 Preview 用例:将 ViewPort 设置到 Preview 对象上:preview.setViewPort(viewPort);
  4. CameraX 内部解析:CameraX 根据 ViewPort 裁剪传感器输出区域,并缩放至 PreviewView 提供的 Surface 分辨率。

缩放类型(ScaleType)对显示效果的影响对比:

ScaleType效果适用场景
FILL_CENTER等比例缩放,裁剪超出 PreviewView 的部分,居中显示视频通话、全屏预览(减少黑边)
FIT_CENTER等比例缩放,保持完整画面,上下或左右留黑边图片拍摄预览(保证全部内容可见)
FILL_START同 FILL_CENTER,但裁剪边不对齐中心,从左上角开始特定 UI 对齐需求
CameraX PreviewView 内部协作流程 PreviewView (SurfaceProvider 实现) SurfaceProvider onSurfaceRequested() CameraX Core Capture Session 管理 ViewPort 裁剪 & 缩放配置 SurfaceRequest 分辨率、回调 PreviewOutput Surface + 配置 提供实现 provideSurface() 输出绑定 裁剪规则 创建 Surface 📌 虚线关系:ViewPort 影响 CameraX 裁剪传感器输出,但不直接参与 Surface 交换

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 以启用硬件缓冲区。