10、CameraX+SurfaceView:CameraX简介、Preview用例、ImageAnalysis用例、生命周期绑定
好,咱们进入第10章。前面几章我们把SurfaceView的底层机制、双缓冲、线程模型都啃了一遍。说实话,光用SurfaceView去对接Camera API,那代码量是相当感人的。我记得早期做Android相机开发时,要自己管理Camera.open、预览回调、旋转角度、还要处理各种厂商兼容问题……嗯,那会儿真是“一把辛酸泪”。
但自从Google推出了CameraX,情况就大不一样了。这一章,我们就来聊聊CameraX + SurfaceView这套组合拳。说白了,CameraX帮我们把底层的相机逻辑封装好了,我们只需要关心“把画面渲染到SurfaceView上”这件事。
10.1 CameraX是什么?
CameraX是Google推出的一个Jetpack相机库。它的目标很明确:简化相机开发,同时保证跨设备兼容性。
我个人习惯把CameraX理解成一个“相机中间件”。它不直接操作硬件,而是通过抽象层屏蔽了不同厂商、不同Android版本的差异。你想想看,市面上几千款Android设备,每家的Camera HAL实现都不一样。如果每个开发者都自己去适配,那简直是灾难。
CameraX的核心优势有几点:
- 生命周期感知:自动绑定Activity/Fragment的生命周期,不用手动释放相机
- 用例(UseCase)驱动:预览、拍照、分析,都是一个个独立的用例
- 兼容性:官方做了大量设备测试,覆盖了市面上绝大多数机型
核心概念:CameraX的架构是“一个相机 + 多个用例”。你可以同时绑定Preview、ImageAnalysis、ImageCapture三个用例,它们共享同一个相机会话。
10.2 Preview用例:把画面送到SurfaceView
Preview是CameraX最常用的用例。它的作用很简单:把相机预览画面输出到一个Surface上。而这个Surface,正是我们SurfaceView的SurfaceHolder。
我在项目中遇到过一个问题:直接用SurfaceView的Surface作为Preview的输出目标,结果在某些机型上预览画面是黑的。后来排查发现,是SurfaceView的Surface还没有创建完成,CameraX就开始输出了。所以,一定要在SurfaceHolder.Callback的surfaceCreated回调之后,再去绑定CameraX。
来看一个典型的绑定代码:
// 1. 创建Preview用例
Preview preview = new Preview.Builder()
.setTargetResolution(new Size(1280, 720))
.build();
// 2. 设置SurfaceProvider
preview.setSurfaceProvider(surfaceView.getSurfaceProvider());
// 3. 绑定到生命周期
Camera camera = cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview
);
嗯,这里要注意:surfaceView.getSurfaceProvider()是Android 10以上才有的API。如果你要兼容更低版本,需要自己实现SurfaceProvider接口,把SurfaceHolder的Surface传进去。
个人经验:我建议在surfaceCreated中调用bindToLifecycle,在surfaceDestroyed中调用cameraProvider.unbindAll()。这样可以确保Surface和Camera的生命周期完全同步。
10.3 ImageAnalysis用例:实时处理每一帧
ImageAnalysis是CameraX提供的另一个重要用例。它允许我们以字节码或ImageProxy的形式,获取每一帧相机数据。这个用例特别适合做实时图像处理,比如二维码扫描、人脸检测、滤镜效果等。
它的工作流程是这样的:
- CameraX从相机硬件拿到一帧数据
- 封装成ImageProxy对象
- 回调到我们的Analyzer中
- 我们在Analyzer里处理数据
我曾经在做一个扫码项目时,发现ImageAnalysis的回调频率太高,导致UI线程卡顿。后来加了一个“丢帧”策略:如果上一帧还没处理完,就直接跳过当前帧。这样既保证了实时性,又不会阻塞主线程。
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(new Size(640, 480))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), imageProxy -> {
// 在这里处理每一帧
// 注意:处理完后一定要调用 imageProxy.close()
imageProxy.close();
});
避坑指南:我曾经忘记调用imageProxy.close(),结果相机预览几秒后就卡死了。因为CameraX的帧缓冲区被占满了,无法再接收新帧。切记:每一帧处理完必须close。
10.4 生命周期绑定:自动管理相机资源
CameraX最让我喜欢的一点,就是它的生命周期绑定机制。你不需要在onResume里打开相机、在onPause里释放相机。只需要把LifecycleOwner传进去,CameraX会自动处理这一切。
它的原理其实不复杂:CameraX内部注册了LifecycleObserver,监听Activity/Fragment的生命周期变化。当生命周期到达ON_RESUME时,自动打开相机;到达ON_PAUSE时,自动关闭相机。
但这里有一个坑:如果你在Fragment中使用CameraX,一定要确保Fragment的Lifecycle是可见的。我遇到过一种情况:Fragment已经onPause了,但CameraX还在运行,结果导致相机被占用,其他应用无法使用。
正确的做法是:
// 使用ViewLifecycleOwner,而不是Fragment本身
cameraProvider.bindToLifecycle(
viewLifecycleOwner, // 注意这里
cameraSelector,
preview,
imageAnalysis
);
为什么用viewLifecycleOwner?因为Fragment的Lifecycle和它的View Lifecycle是不同步的。当Fragment被添加到回退栈时,它的onPause会被调用,但View可能还活着。用viewLifecycleOwner可以确保相机在View不可见时自动释放。
10.5 整体架构图
下面这张图展示了CameraX + SurfaceView的整体协作关系:
10.6 实战要点总结
好了,这一章的内容就这些。最后我总结几个实战中容易踩的坑:
| 场景 | 常见问题 | 解决方案 |
|---|---|---|
| Surface未就绪 | 预览黑屏 | 在surfaceCreated回调中绑定CameraX |
| ImageProxy未关闭 | 预览卡死 | 每帧处理完必须调用close() |
| Fragment生命周期 | 相机资源泄漏 | 使用viewLifecycleOwner绑定 |
| 分析线程阻塞 | UI卡顿 | 使用STRATEGY_KEEP_ONLY_LATEST + 独立线程 |
我的建议:刚开始用CameraX时,先只绑定Preview用例,跑通预览流程。确认SurfaceView能正常显示画面后,再逐步添加ImageAnalysis和ImageCapture。这样排查问题会容易很多。
CameraX + SurfaceView这套组合,说白了就是“让专业的库做专业的事”。CameraX负责相机控制,SurfaceView负责高效渲染。你只需要把两者通过SurfaceProvider连接起来,剩下的交给框架就好。