1. Camera与Camera2 API概览:Android摄像头架构演进
做Android摄像头开发这么多年,我最大的感受就是——这玩意儿真不是随便调个接口就能搞定的。尤其是从Camera API切换到Camera2 API那段时间,说实话,很多老项目直接翻车了。
今天咱们就来聊聊Android摄像头架构的演进,以及Camera和Camera2这两套API到底有什么区别。嗯,我会结合我实际踩过的坑来讲,希望能帮你少走弯路。
1.1 Android摄像头架构演进
Android的摄像头架构,说白了经历了三个阶段:
- Android 1.0 ~ 4.4:Camera API(android.hardware.Camera)
- Android 5.0 ~ 7.0:Camera2 API预览版 + 正式版(android.hardware.camera2)
- Android 8.0+:Camera2 API成为主流,Camera API被标记为废弃
为什么会这样?因为早期的Camera API设计太简单了。你想想看,那时候手机摄像头就一个后置,分辨率也不高,调个预览、拍个照就完事了。但后来手机摄像头越来越多——双摄、三摄、景深、广角、长焦……老API根本撑不住。
我记得在2015年做第一个双摄项目时,用Camera API去控制两个摄像头,那叫一个痛苦。两个Camera对象互相抢资源,崩溃率居高不下。后来Android 5.0推出了Camera2 API,我才算真正解脱。
核心变化:Camera API是同步阻塞模型,Camera2 API是异步管道模型。说白了,Camera2把摄像头当成一个流水线,你可以精细控制每一道工序。
1.2 Camera API vs Camera2 API 对比
很多新手问我:到底用哪个?我的建议是:新项目一律用Camera2。除非你要兼容Android 4.4以下的古董机,否则别碰老API了。
咱们直接看对比表,一目了然:
| 对比项 | Camera API | Camera2 API |
|---|---|---|
| 引入版本 | Android 1.0 | Android 5.0 |
| 模型 | 同步阻塞 | 异步管道 |
| 摄像头控制 | 单一Camera对象 | CameraManager → CameraDevice → CaptureSession |
| 多摄像头支持 | 不支持(需hack) | 原生支持 |
| 手动控制 | 有限(白平衡、曝光补偿) | 全面(ISO、快门、对焦、AE/AWB等) |
| 预览/拍照 | setPreviewCallback | CaptureRequest + CaptureCallback |
| 性能 | 较低,容易ANR | 高,支持多路并发 |
| 当前状态 | 废弃(deprecated) | 推荐使用 |
我的经验:曾经有个项目为了快速上线用了Camera API,结果后来要加美颜滤镜,发现预览帧回调延迟高得离谱。换成Camera2后,用SurfaceView + ImageReader,延迟直接降了60%。所以,别图一时省事。
1.3 核心类介绍
Camera2 API的核心类就三个:CameraManager、CameraDevice、CameraCaptureSession。它们的关系就像工厂流水线:
- CameraManager:工厂管理员,负责发现摄像头、打开摄像头
- CameraDevice:具体的摄像头设备,相当于一台机器
- CameraCaptureSession:生产流水线,负责处理预览、拍照等任务
1.3.1 CameraManager
这是入口。你首先要通过getSystemService()拿到它:
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
然后用它来:
- 获取摄像头列表:
manager.getCameraIdList() - 查询摄像头特性:
manager.getCameraCharacteristics(cameraId) - 打开摄像头:
manager.openCamera(cameraId, callback, handler)
这里有个坑——记得检查摄像头权限。我遇到过好几次,在Android 6.0以上没动态申请权限,直接调openCamera,结果回调里报CameraAccessException。嗯,这个异常就是权限问题的典型信号。
1.3.2 CameraDevice
当你成功打开摄像头后,会得到一个CameraDevice对象。它代表了一个物理摄像头。你可以用它来:
- 创建
CaptureRequest:定义拍照或预览的参数 - 创建
CameraCaptureSession:建立数据通道 - 关闭摄像头:
device.close()
我个人习惯在onOpened回调里立即创建预览请求,而不是等用户点击按钮。因为摄像头打开本身就有延迟,早点创建请求能缩短用户等待时间。
CameraDevice.StateCallback callback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
// 摄像头已打开,可以创建CaptureSession了
createCaptureSession(camera);
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
// 处理错误,比如摄像头被占用
}
};
注意:CameraDevice的close()方法必须在主线程调用,否则会抛出异常。我曾经在子线程里调了close(),结果崩溃日志查了半天才发现是线程问题。
1.3.3 CameraCaptureSession
这是最核心的类。它负责管理摄像头数据的流向。你可以把它理解成一条流水线:
- 输入:
CaptureRequest(告诉摄像头要做什么) - 输出:
CaptureCallback(接收摄像头返回的数据) - 目标:
Surface(数据显示到哪里,比如TextureView、ImageReader)
创建Session的典型代码:
cameraDevice.createCaptureSession(
Arrays.asList(previewSurface, imageReaderSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
// Session已就绪,可以发送请求了
startPreview(session);
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
// 配置失败,通常是Surface不合法
}
},
handler
);
这里有个关键点:Surface的顺序很重要。第一个Surface通常是预览用的,第二个是拍照用的。如果你把顺序搞反了,预览画面可能出不来。我刚开始学Camera2时就犯过这个错,预览黑屏了半天。
1.4 核心流程总结
咱们用一张图来总结Camera2的工作流程:
1.5 避坑指南
最后,分享几个我实际项目中踩过的坑:
- 摄像头被占用:曾经有个bug,用户打开相机后切到后台再回来,摄像头就打不开了。原因是
onPause里没调close()。记住:CameraDevice和CaptureSession都要在onPause中释放。 - SurfaceView vs TextureView:我建议用TextureView,因为它支持硬件加速,而且可以方便地做旋转、缩放。SurfaceView虽然性能更好,但没法做动画变换。
- 预览方向:默认摄像头预览是横屏的,竖屏时画面会旋转90度。记得用
CameraCharacteristics.SENSOR_ORIENTATION来校正方向。
小技巧:调试Camera2时,可以用adb shell dumpsys media.camera来查看摄像头状态。这个命令能告诉你摄像头是否被占用、当前分辨率、帧率等信息。排查问题非常有用。
好了,这一章的内容就到这里。Camera2 API虽然学习曲线陡峭,但掌握了核心类的用法后,你会发现它其实很灵活。下一章咱们会深入讲解CameraManager的详细用法,包括如何获取摄像头特性、如何选择最佳摄像头等。
公众号:蓝海资料掘金营,微信deep3321