CameraDevice打开优化:异步打开、设置回调优先级、减少openCamera等待时间
相机启动慢,很多时候就卡在 openCamera 这一步。
你想想看,用户点下相机图标,等了好几秒才看到画面,这体验谁受得了?
我做过好几个相机项目,每次优化启动,CameraDevice 的打开过程都是重头戏。今天咱们就聊聊怎么把这步做快。
为什么 openCamera 会慢?
说白了,openCamera 不是个简单的函数调用。它背后涉及:
- HAL 层的 Camera 设备初始化
- 底层驱动的加载与配置
- Stream 管道的预分配
- 权限校验与安全策略检查
这些操作加起来,几百毫秒甚至一秒多都很常见。我遇到过一台低端机,光 openCamera 就花了 1.8 秒——用户早把 App 杀了。
核心优化策略
嗯,这里要注意,优化不是硬砍底层时间,而是让上层不傻等。我总结了三个方向:
1. 异步打开,别阻塞主线程
很多新手会这么写:
CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
manager.openCamera(cameraId, callback, handler); // 这行不阻塞
// 但有人会在 openCamera 前后加同步锁,或者等回调才继续
其实 openCamera 本身是异步的,问题出在开发者自己加了同步等待。我见过有人用 CountDownLatch 等回调,主线程直接卡死。
正确做法:
- 把
openCamera放在后台线程执行 - 回调里再切回主线程更新 UI
- 不要用任何同步锁去等结果
核心原则:openCamera 的调用和回调,都不应该阻塞主线程。你只需要在回调里处理结果即可。
2. 设置回调优先级,让关键回调先跑
你可能会问:回调还有优先级?
有的。CameraDevice.StateCallback 里有三个回调:
onOpened— 设备打开成功onDisconnected— 设备断开onError— 打开失败
我建议把 onOpened 的回调处理放到高优先级线程。因为一旦设备打开,紧接着就要创建 CaptureSession、开始预览。这个链路越长,用户等待越久。
我曾经在一个项目里,把 onOpened 里的逻辑从主线程移到 HandlerThread,启动时间直接降了 200ms。为什么?因为主线程当时还在做 Activity 的布局渲染,两个任务抢资源。
小技巧:给 Camera 相关回调单独开一个 HandlerThread,设置线程优先级为 THREAD_PRIORITY_URGENT_DISPLAY。这样系统调度时会优先跑你的回调。
3. 减少 openCamera 等待时间
这部分是硬功夫。我列几个实战中验证过的点:
| 优化项 | 说明 | 效果 |
|---|---|---|
| 预热 Camera Service | 在 Application 启动时提前 bind CameraService | 减少 100-300ms |
| 缓存 Camera Characteristics | 避免每次 open 前重复查询 | 减少 50-100ms |
| 减少 openCamera 参数 | 只传必要的 cameraId,不要带多余配置 | 减少 20-50ms |
| 提前申请权限 | 在点击相机按钮前就拿到 CAMERA 权限 | 避免权限弹窗阻塞 |
我个人习惯在 App 启动后的空闲期,提前调用 CameraManager.availableCameraIds() 和 getCameraCharacteristics()。这样真正打开时,数据已经准备好了。
注意:预热 CameraService 要小心。有些厂商的 HAL 实现有 bug,提前 bind 会导致后续 open 失败。建议在主流机型上充分测试。
实战代码片段
下面是我常用的异步打开模板:
// 在后台线程执行
HandlerThread cameraThread = new HandlerThread("CameraThread");
cameraThread.start();
Handler cameraHandler = new Handler(cameraThread.getLooper());
// 异步打开
CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice device) {
// 这里已经在 cameraThread 上了
// 直接开始创建 CaptureSession
createPreviewSession(device);
}
@Override
public void onDisconnected(@NonNull CameraDevice device) {
device.close();
}
@Override
public void onError(@NonNull CameraDevice device, int error) {
device.close();
// 上报错误
}
}, cameraHandler);
你看,核心就两点:异步 + 独立线程。别搞复杂了。
知识体系总览
我把这节的核心逻辑画了张图,方便你理解:
避坑指南
最后说几个我踩过的坑:
- 不要重复 openCamera: 我见过有人每次切换前后置都重新 open,其实可以复用同一个 CameraDevice,只切换 Stream。
- 注意生命周期:
onPause里一定要 close,否则下次 open 会失败。有些机型会报CAMERA_ERROR_SERVER_DIED。 - 回调线程别用 Looper.getMainLooper(): 主线程 Handler 会导致回调排队,建议用独立 HandlerThread。
我曾经在一个项目里,因为回调用了主线程 Handler,导致 onOpened 被 UI 任务卡了 300ms。改成独立线程后,问题立刻解决。
好了,这节就聊到这儿。记住:异步 + 优先级 + 预热,三个方向一起抓,CameraDevice 打开时间一定能降下来。