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 基本使用步骤
我把过程拆成五步,你照着搭起来就行:
- 获取 Provider:调用
ProcessCameraProvider.getInstance(context),异步得到实例。 - 创建 Preview 用例:new 一个
Preview.Builder().build(),记得设置分辨率、帧率等。 - 绑定 SurfaceProvider:
preview.setSurfaceProvider(previewView.getSurfaceProvider()),把预览视图挂上去。 - 绑定生命周期:调用
provider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)。 - 处理异常: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 到最终的相机断开:
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,直接崩溃了。嗯,以后注意。