3. CameraManager与CameraDevice:获取CameraManager、打开CameraDevice、处理Camera状态回调
好,咱们正式开始接触摄像头了。
前面两章我们把CameraX的架构捋了一遍,也知道了Camera2 API才是Android官方推荐的底层接口。从这一章开始,我带你手把手地走一遍Camera2的核心流程。
今天要聊的,是Camera2的“入口”和“大门”——CameraManager和CameraDevice。
说白了,CameraManager就是系统里管摄像头的“管家”,而CameraDevice就是具体某个摄像头的“门把手”。你要开门(打开摄像头),得先找到管家,再让他给你钥匙。
3.1 获取CameraManager
CameraManager的获取方式,简单到离谱。
你只需要在Activity或者Service里,通过getSystemService()就能拿到。
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
嗯,就这一行。
不过我个人习惯把它封装成一个单例,或者放在Application里初始化。为什么呢?因为CameraManager本身是系统服务,不会频繁变化,你每次打开摄像头都去重新获取一次,其实没必要。
3.2 枚举摄像头:getCameraIdList
拿到CameraManager之后,第一件事通常是看看设备上有几个摄像头。
try {
String[] cameraIdList = cameraManager.getCameraIdList();
for (String id : cameraIdList) {
Log.d("Camera", "发现摄像头: " + id);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
这里有个坑,我踩过。
getCameraIdList()会抛出CameraAccessException,这个异常通常发生在摄像头被其他应用占用,或者系统服务异常的时候。你想想看,如果用户正在打电话,你突然去枚举摄像头,很可能就抛异常了。
所以,一定要try-catch,别偷懒。
另外,getCameraIdList()返回的ID列表,顺序不一定是固定的。不同厂商、不同Android版本,返回的顺序可能不一样。如果你需要区分前置和后置,别依赖索引,要用getCameraCharacteristics()去查。
3.3 查询摄像头特性:getCameraCharacteristics
每个摄像头都有自己的“身份证”,就是CameraCharacteristics。
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
Log.d("Camera", "这是前置摄像头");
}
除了判断前后置,你还能拿到很多有用的信息:
- 支持的输出尺寸(
SCALER_STREAM_CONFIGURATION_MAP) - 是否支持闪光灯(
FLASH_INFO_AVAILABLE) - 传感器方向(
SENSOR_ORIENTATION) - 支持的分辨率列表
我在项目中遇到过一个问题:某款平板的后置摄像头是竖屏安装的,拍出来的照片默认旋转了90度。后来查了SENSOR_ORIENTATION才发现,这个值是90。所以,千万别硬编码旋转角度,一定要从特性里读。
3.4 打开CameraDevice
拿到CameraManager,选好摄像头ID,接下来就是打开它了。
cameraManager.openCamera(cameraId, stateCallback, backgroundHandler);
三个参数:
- cameraId:你要打开的摄像头ID
- stateCallback:状态回调,摄像头打开成功、失败、断开都会通知你
- backgroundHandler:回调执行的Handler,一般传后台线程的Handler
注意,openCamera()是异步的。它不会立刻返回一个CameraDevice,而是通过回调告诉你结果。
为什么要异步?因为打开摄像头可能涉及硬件初始化、权限校验、资源分配,这些操作如果放在主线程,界面会卡住。你想想看,用户点一下拍照按钮,界面卡了3秒才打开摄像头,这体验能好吗?
3.5 CameraDevice.StateCallback:处理摄像头状态
这个回调是Camera2里最核心的接口之一。你需要实现它,然后交给openCamera()。
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// 摄像头打开成功
Log.d("Camera", "摄像头已打开");
// 保存引用,后续创建CaptureSession
mCameraDevice = cameraDevice;
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
// 摄像头断开连接(比如被其他应用抢占)
Log.d("Camera", "摄像头已断开");
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
// 摄像头出错
Log.e("Camera", "摄像头出错: " + error);
cameraDevice.close();
mCameraDevice = null;
}
};
三个回调方法,分别对应三种状态:
| 回调方法 | 触发时机 | 你应该做什么 |
|---|---|---|
onOpened() |
摄像头成功打开 | 保存CameraDevice引用,准备创建CaptureSession |
onDisconnected() |
摄像头被系统断开 | 关闭CameraDevice,释放资源 |
onError() |
摄像头发生错误 | 关闭CameraDevice,提示用户或重试 |
onOpened()里直接做耗时操作。这个回调默认跑在Handler线程,但如果你传了主线程的Handler,那就会卡UI。我建议你专门创建一个后台线程的Handler来处理这些回调。
3.6 核心流程总结
来,我把这一章的核心逻辑画成一张图,方便你理解。
这张图把流程串起来了。你从获取CameraManager开始,一路走到打开摄像头,然后通过回调拿到结果。每一步都环环相扣,缺一不可。
3.7 避坑指南
最后,我把自己踩过的坑总结一下,你遇到了可以少走弯路。
- 别忘了权限:Android 6.0以上需要动态申请
CAMERA权限。如果你没申请就调用openCamera(),会直接抛SecurityException。 - 关闭摄像头要及时:CameraDevice用完后一定要调用
close(),否则下次打开会失败。我曾经在测试时忘了关,结果连续打开关闭几次后,摄像头就再也打不开了,只能重启手机。 - Handler别用主线程:虽然可以传主线程的Handler,但回调里如果有耗时操作,会卡UI。我建议你创建一个后台线程的HandlerThread。
- onDisconnected和onError都要close:这两个回调都意味着摄像头不可用了,记得释放资源。
核心要点回顾:
- CameraManager是系统服务,通过
getSystemService()获取 - 用
getCameraIdList()枚举摄像头,用getCameraCharacteristics()查询特性 openCamera()是异步操作,结果通过StateCallback返回- StateCallback的三个回调:onOpened、onDisconnected、onError,都要妥善处理
- 用完记得close,否则下次打不开
好了,这一章就到这里。CameraManager和CameraDevice是Camera2的基石,你把它俩搞明白了,后面的CaptureSession和CaptureRequest才能玩得转。