4、预览用例的基本使用:ProcessCameraProvider

讲到 CameraX 预览,最绕不开的就是 ProcessCameraProvider。这东西说白了就是 CameraX 与系统 CameraService 之间的“中间人”。我当年第一次接触时,觉得它不过是个工厂类,后来踩了几个坑才发现,它的生命周期管理才是真正的精髓。你想想看,预览一启动,Activity 一旋转、一来电,相机就得跟着停或重启,全靠它帮你兜底。

4.1 ProcessCameraProvider 是什么?

它是 CameraX 给咱们提供的“一次性单例”入口。每个进程只允许有一个实例,用来绑定 生命周期、配置 用例(UseCase)、然后“提交”给系统去干活。说白了,你就把它想象成一个管家,你告诉它:“我要预览、要拍照”,然后它帮你把相机硬件调好、配置下发进去。

讲个实际的事。有一次项目里,我在 Fragment 里反复创建新的 Provider,结果预览总是闪一下黑屏。排查半天才发现,ProcessCameraProvider.getInstance() 返回的是一个 ListenableFuture,每次 get 都是同一个实例,但如果你在它还没配置完就又去 get,就会产生竞争。嗯,这里要记住:Provider 应该在整个应用或 Activity 生命周期内只初始化一次,而不是每次打开相机都 new。

核心点:ProcessCameraProvider 是单例,通过 getInstance(context) 获取,返回值是 ListenableFuture<ProcessCameraProvider>。

4.2 基本使用步骤

我把过程拆成五步,你照着搭起来就行:

  1. 获取 Provider:调用 ProcessCameraProvider.getInstance(context),异步得到实例。
  2. 创建 Preview 用例:new 一个 Preview.Builder().build(),记得设置分辨率、帧率等。
  3. 绑定 SurfaceProvider:preview.setSurfaceProvider(previewView.getSurfaceProvider()),把预览视图挂上去。
  4. 绑定生命周期:调用 provider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)。
  5. 处理异常:catch CameraInfoUnavailableException 等。

我个人习惯把第 1 步和第 4 步用 ListenableFuture.addListener() 串起来,避免回调嵌套太深。不过也可以直接用 ListenableFuture.transform(),看团队规范。

4.3 核心代码示例

// 1. 获取 ProcessCameraProvider
ListenableFuture<ProcessCameraProvider> providerFuture =
        ProcessCameraProvider.getInstance(context);

providerFuture.addListener(() -> {
    try {
        ProcessCameraProvider provider = providerFuture.get();

        // 2. 创建 Preview 用例
        Preview preview = new Preview.Builder()
                .setTargetResolution(Size(1920, 1080))
                .build();

        // 3. 将 Preview 与 PreviewView 连接
        preview.setSurfaceProvider(previewView.getSurfaceProvider());

        // 4. 选择后置摄像头
        CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;

        // 5. 绑定到生命周期
        Camera camera = provider.bindToLifecycle(
                lifecycleOwner,
                cameraSelector,
                preview
        );
    } catch (ExecutionException | InterruptedException e) {
        // 处理异常
    }
}, ContextCompat.getMainExecutor(context));

我曾经忘记在 bindToLifecycle 之前调用 setSurfaceProvider,结果预览画面一直黑屏。原因是 Provider 绑定时会立即启动预览数据流,但 Surface 还没挂上去,数据就丢了。所以顺序很重要:先挂 Surface,再 bind。

4.4 生命周期绑定细节

bindToLifecycle 这个方法,你把 lifecycleOwner 传进去,CameraX 就会自动监听其生命周期变化。比如 Activity 进入 onStop 时,相机自动释放;回到 onStart 时,自动恢复。这一点我觉得比 Camera2 省心太多。不过要注意,Provider 本身并不持有生命周期,它只是把用例和生命周期关联起来。如果 owner 已经处于 DESTROYED 状态,绑定会抛出异常。所以建议在 onStart 之后、onStop 之前调用。

4.5 多用例组合

预览通常不止一个用例,你可能会同时绑定 ImageAnalysis 或 ImageCapture。这时只需要把多个用例作为可变参数传入:

provider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageCapture, imageAnalysis);

系统会自动合并配置。如果真的冲突(比如分辨率不一致),CameraX 会抛 UseCaseGroup 相关的异常。我遇到过“预览和图像分析的分辨率要求不同,导致绑定失败”的情况,后来统一设置了 setTargetResolution 才解决。

4.6 ProcessCameraProvider 生命周期流程

下面这张图把整个过程串起来了,从获取 Provider 到最终的相机断开:

ProcessCameraProvider 预览启动流程 获取ProcessCameraProvider getInstance(context) 创建Preview用例 Preview.Builder().build() 绑定SurfaceProvider setSurfaceProvider(previewView) bindToLifecycle (lifecycleOwner, cameraSelector, preview) 预览启动 ⚠️ 先setSurfaceProvider 再bindToLifecycle 🔁 自动管理onStart/onStop 无需手动释放

4.7 关键 API 速查表

API 作用 注意点
getInstance(context) 获取进程唯一的 Provider 返回值是 ListenableFuture,别用 get() 在主线程等
bindToLifecycle() 绑定用例到生命周期 Owner 不能是 DESTROYED 状态
setSurfaceProvider() 将预览数据输出到 PreviewView 必须在 bind 之前调用,否则黑屏
unbindAll() 解绑所有用例 通常在 onDestroy 或重新配置时使用

小提示:如果需要在 Activity 的 onCreate 里绑定预览,记得把 lifecycleOwner 传入 LifecycleOwner(通常就是 Activity 本身)。因为 onCreate 时生命周期处于 CREATED,CameraX 会在 ON_START 时自动打开相机,不会提前抢占。

4.8 避坑指南

  • Context 传递:getInstance() 只能传 Application 级别的 context,传 Activity 会泄漏。
  • 绑定顺序:再次强调——setSurfaceProvider 必须在 bindToLifecycle 之前。
  • 多用例冲突:不同用例的分辨率、帧率要求如果不兼容,CameraX 会抛异常。解决方法是统一设置 setTargetResolution,或使用 UseCaseGroup。
  • 线程安全:Provider 的获取和绑定需要在主线程执行(mainExecutor),否则可能引发状态不一致。我曾经在子线程里调用 provider.bindToLifecycle,直接崩溃了。嗯,以后注意。