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);

你看,核心就两点:异步 + 独立线程。别搞复杂了。

知识体系总览

我把这节的核心逻辑画了张图,方便你理解:

CameraDevice 打开优化核心逻辑 openCamera 调用 异步打开 不阻塞主线程 回调优先级 高优先级线程处理 减少等待时间 预热 + 缓存 + 提前权限 启动时间缩短 200-500ms 三个方向并行优化,效果叠加

避坑指南

最后说几个我踩过的坑:

  • 不要重复 openCamera: 我见过有人每次切换前后置都重新 open,其实可以复用同一个 CameraDevice,只切换 Stream。
  • 注意生命周期: onPause 里一定要 close,否则下次 open 会失败。有些机型会报 CAMERA_ERROR_SERVER_DIED
  • 回调线程别用 Looper.getMainLooper(): 主线程 Handler 会导致回调排队,建议用独立 HandlerThread。

我曾经在一个项目里,因为回调用了主线程 Handler,导致 onOpened 被 UI 任务卡了 300ms。改成独立线程后,问题立刻解决。

好了,这节就聊到这儿。记住:异步 + 优先级 + 预热,三个方向一起抓,CameraDevice 打开时间一定能降下来。