12. 线程调度优化:Camera专用线程池、设置线程优先级、避免主线程阻塞

相机启动优化做到后面,你会发现一个扎心的事实——代码写得再漂亮,线程调度一乱,一切白搭。我见过太多团队,把相机启动卡顿归咎于硬件,结果一查日志,全是主线程被各种杂活堵死的惨案。

说白了,相机启动是个“多线程协作”的活儿。预览流、拍照流、算法处理、UI更新……哪个环节掉链子,用户感知到的就是“黑屏”、“卡顿”、“闪退”。今天咱们就聊聊,怎么把线程调度这块硬骨头啃下来。

12.1 为什么相机启动需要专用线程池?

Android系统默认的线程池,比如AsyncTask、线程池Executors.newCachedThreadPool(),其实不太适合相机场景。为什么?

  • 不可控:系统线程池的线程数、优先级,你说了不算。
  • 容易被干扰:App里其他模块的任务,可能把线程池占满。
  • 缺乏隔离:相机是高实时任务,跟网络请求、数据库读写混在一起,迟早出事。

我个人习惯,给相机模块单独搞一个线程池。这样,相机启动的各个阶段——打开CameraDevice、创建Session、配置流、启动预览——都能在可控的线程上执行。

核心原则:相机专用线程池,只服务于相机模块。其他业务,哪怕是主线程,也别想借用。

12.2 如何设计Camera专用线程池?

先看代码。这是我项目里常用的一个配置:

// Camera专用线程池
public class CameraThreadPool {

    // 核心线程数:2,足够处理预览和拍照
    // 最大线程数:4,应对突发的高负载场景
    // 存活时间:60秒,避免频繁创建销毁
    private static final int CORE_POOL_SIZE = 2;
    private static final int MAX_POOL_SIZE = 4;
    private static final long KEEP_ALIVE_TIME = 60L;

    // 使用有界队列,防止任务堆积导致OOM
    private static final BlockingQueue<Runnable> WORK_QUEUE =
            new LinkedBlockingQueue<>(128);

    // 自定义线程工厂,给线程起个名字,方便调试
    private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r, "CameraThread#" + mCount.getAndIncrement());
            // 设置线程优先级为后台,避免抢占主线程资源
            thread.setPriority(Thread.MAX_PRIORITY - 2);
            return thread;
        }
    };

    private static final ThreadPoolExecutor EXECUTOR =
            new ThreadPoolExecutor(
                    CORE_POOL_SIZE,
                    MAX_POOL_SIZE,
                    KEEP_ALIVE_TIME,
                    TimeUnit.SECONDS,
                    WORK_QUEUE,
                    THREAD_FACTORY,
                    new ThreadPoolExecutor.DiscardOldestPolicy()
            );

    public static void execute(Runnable task) {
        EXECUTOR.execute(task);
    }
}

这里有几个关键点,我踩过坑,得跟你说道说道:

  • 核心线程数设为2:一个处理预览流,一个处理拍照/算法。别贪多,线程切换也是有开销的。
  • 有界队列:队列大小128,超过就丢弃最旧的任务。相机是高实时场景,旧任务没必要执行。
  • 线程命名:给线程起个“CameraThread”的名字,抓取日志时一眼就能定位。

注意:千万不要用无界队列!我曾经在一个项目里用了LinkedBlockingQueue无参构造,结果相机启动时任务堆积了几千个,直接OOM。教训深刻。

12.3 线程优先级怎么设?

线程优先级,说白了就是告诉系统:“我这个任务急不急”。相机启动时,UI线程的优先级最高,因为它负责渲染界面。相机处理线程,优先级可以设得比普通后台线程高一点,但别超过UI线程。

我建议这样设置:

线程类型 优先级 说明
主线程(UI线程) MAX_PRIORITY(10) 系统默认,不要改
Camera预览线程 MAX_PRIORITY - 1(9) 预览帧必须及时处理
Camera拍照线程 MAX_PRIORITY - 2(8) 拍照可以稍微容忍延迟
后台算法线程 NORM_PRIORITY(5) 比如美颜、HDR合成

你可能会问:“设这么高,会不会影响系统稳定性?” 嗯,这里有个平衡。相机是高优先级任务,但别设成MAX_PRIORITY,否则可能抢占系统关键服务的资源。我个人习惯,预览线程设9,拍照线程设8,足够了。

12.4 如何避免主线程阻塞?

这是相机启动优化的核心痛点。主线程一旦阻塞,用户看到的就是“黑屏”或“卡死”。我总结了几条铁律:

  1. 所有Camera API调用,都扔到专用线程池。包括openCamera、createCaptureSession、setRepeatingRequest等。
  2. 回调里别做耗时操作。Camera的回调(比如onImageAvailable)是在Binder线程里执行的,你如果在里面做图像处理,会阻塞整个Camera HAL。
  3. UI更新必须切回主线程。但别用Handler.post,建议用View.postOnAnimation,它跟VSync对齐,更流畅。

举个例子,我之前遇到一个坑:在onPreviewFrame回调里直接做YUV转Bitmap,然后更新ImageView。结果预览帧率从30fps掉到15fps,用户反馈“相机卡得像幻灯片”。后来我把图像处理扔到专用线程池,UI更新用postOnAnimation,问题解决。

小技巧:如果你不确定某个操作是否阻塞主线程,可以用StrictMode检测。在Application的onCreate里开启:

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        .detectAll()
        .penaltyLog()
        .build());

这样,任何主线程上的磁盘IO、网络请求、长耗时操作,都会在logcat里打印警告。

12.5 线程调度流程图

下面这张图,是我根据项目经验画的。它展示了相机启动时,各个任务如何分配到不同线程上:

Camera启动线程调度流程图 主线程(UI线程) 优先级:MAX 启动Activity 更新UI(SurfaceView) 处理用户点击 显示预览画面 Camera专用线程池 优先级:高(8-9) openCamera() createCaptureSession() setRepeatingRequest() 处理预览回调 后台算法线程 优先级:普通(5) YUV转Bitmap 美颜算法 HDR合成 人脸检测 异步调用 异步调用 postOnAnimation 主线程只做UI操作,Camera API调用全部异步化,后台算法使用低优先级线程

从图里可以看得很清楚:主线程只负责UI更新,Camera的API调用全部扔到专用线程池,后台算法(美颜、HDR等)则使用更低优先级的线程。这样,即使算法处理慢,也不会影响预览的流畅度。

12.6 避坑指南

最后,分享几个我踩过的坑,希望能帮你少走弯路:

  • 别在Camera回调里加锁。我曾经在onImageAvailable里加了一个synchronized块,结果导致预览线程和拍照线程死锁,相机直接黑屏。后来改用无锁队列(ConcurrentLinkedQueue)解决。
  • 线程池拒绝策略要谨慎。DiscardOldestPolicy适合相机场景,但如果你用的是CallerRunsPolicy,任务会回退到调用线程执行——万一调用线程是主线程,那就炸了。
  • 别忘了处理线程泄漏。相机销毁时,记得调用线程池的shutdown(),否则线程会一直存活,导致内存泄漏。我习惯在CameraManager的close()方法里统一清理。

总结一句话:相机启动优化的本质,就是把主线程上的“脏活累活”全部剥离出去,让主线程只做它最擅长的事——渲染UI。线程调度做得好,相机启动速度至少能提升30%。

公众号:蓝海资料掘金营,微信 deep3321