22、Camera 性能优化:Camera启动优化、预览延迟优化、拍照延迟优化、内存占用优化与OOM预防。

做Camera开发这么多年,我踩过最多的坑,就是性能问题。启动慢、预览卡、拍照延迟、内存爆掉……这些几乎是每个Camera工程师的噩梦。今天咱们就好好聊聊,怎么把这些硬骨头啃下来。

说白了,Camera性能优化就是一场与时间、内存的博弈。你想想看,用户按下快门那一瞬间,背后是几十个模块在协同工作。任何一个环节慢了,体验就崩了。

Camera启动优化:从点击到预览的第一帧

启动优化,核心目标就一个:缩短从点击图标到预览画面出现的时间。我见过有些App启动要3秒多,用户早跑了。

启动流程大致分这几步:

  • 进程创建与初始化
  • CameraService绑定
  • Camera HAL加载
  • Stream配置与创建
  • 第一帧预览数据回调

这里有个关键点——并行化。很多开发者习惯串行做事情,其实没必要。比如UI初始化可以和CameraService绑定同时进行。

核心优化策略:

  • 预加载CameraService:在Application启动时就提前绑定CameraService,减少首次调用时的等待。我在项目中遇到过,冷启动时CameraService还没起来,硬等了几百毫秒。
  • 延迟非必要初始化:像闪光灯控制、对焦模式切换这些,别在启动时一股脑全配好。先出画面,再慢慢配。
  • 使用CameraProvider:Android 10+推荐用CameraX或Camera2的扩展,底层做了很多预热优化。
// 预加载CameraService示例
public class CameraApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 提前绑定CameraService
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        // 这里只是触发Service启动,不实际打开Camera
        manager.registerAvailabilityCallback(executor, new CameraManager.AvailabilityCallback() {
            @Override
            public void onCameraAvailable(String cameraId) {
                // CameraService已就绪
            }
        });
    }
}

嗯,这里要注意:预加载别做得太重。我曾经试过在Application里直接打开Camera,结果导致ANR——因为主线程被占住了。预加载只是触发Service绑定,不是真的打开Camera。

预览延迟优化:让画面丝滑起来

预览延迟,说白了就是画面跟不上手指移动的速度。用户转个身,画面还在半秒前的位置,这体验太糟糕了。

预览延迟的根源,通常是这几个:

  • SurfaceView/TextureView的缓冲区交换不及时
  • HAL层处理帧数据太慢
  • 应用层做了太多图像处理

我的经验:预览延迟优化,优先检查Surface的缓冲区大小。默认的缓冲区可能只有3个,如果处理不过来就会丢帧。我建议至少配置4个缓冲区。

// 配置预览缓冲区大小
StreamConfigurationMap map = characteristics.get(
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map != null) {
    // 获取支持的输出尺寸
    Size[] outputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
    // 选择合适的分辨率,不要一味追求最大
    Size previewSize = chooseOptimalSize(outputSizes, targetWidth, targetHeight);
    // 创建CaptureRequest时设置
    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 
        new Range<Integer>(30, 30)); // 锁定30fps
}

还有一个容易被忽略的点——预览分辨率。很多App直接取最大分辨率,结果帧率掉到15fps。你想想看,1080p和4K的预览,对带宽要求差了多少?我一般建议预览分辨率不超过1080p,除非有特殊需求。

拍照延迟优化:按下快门到出片

拍照延迟,是用户感知最明显的痛点。按下快门,等半天才听到快门声,用户心里肯定在骂娘。

拍照延迟的构成:

阶段 耗时占比 优化方向
Capture Request提交 10% 减少Request构建开销
HAL处理与ISP 40% 使用ZSL模式
图像编码 30% 硬件编码器/JPEG压缩优化
文件写入 20% 异步写入/内存映射

我个人习惯,拍照延迟优化优先考虑ZSL(Zero Shutter Lag)模式。ZSL的原理很简单:预览时一直在循环缓冲区里存着帧数据,按下快门时直接从缓冲区取,而不是重新Capture。这样能省掉几十到几百毫秒。

注意:ZSL模式会持续占用内存,因为要维护一个帧缓冲区。我见过有App开了ZSL后内存涨了200MB,结果在低端机上直接OOM。所以ZSL的缓冲区大小要控制好,一般3-5帧就够了。

// 开启ZSL模式
captureRequestBuilder.set(CaptureRequest.CONTROL_ENABLE_ZSL, 1);
// 设置缓冲区大小(部分厂商支持)
captureRequestBuilder.set(CaptureRequest.CONTROL_ZSL_CAPTURE_TRIGGER_ID, triggerId);

还有一个技巧——预对焦。用户打开Camera时,先做一次自动对焦。等用户按下快门时,对焦已经完成了,省掉对焦时间。我做过测试,预对焦能省掉约150ms。

内存占用优化与OOM预防

内存问题,是Camera开发中最头疼的。一张照片可能就10MB+,如果处理不当,内存分分钟爆掉。

内存占用的主要来源:

  • 预览帧数据(YUV/RGB)
  • 拍照帧数据(RAW/YUV/JPEG)
  • 图像处理中间结果
  • 编码器/解码器缓冲区

我遇到过最惨的一次,是在某款低端机上,拍照时直接OOM崩溃。后来发现是Bitmap没有及时回收。Camera的回调里拿到的Image对象,如果不及时close,底层的内存就不会释放。

OOM预防三板斧:

  1. 及时释放Image对象:在ImageReader的回调中,用完就close,别等GC。
  2. 控制并发帧数:不要同时处理太多帧,设置合理的maxImages。
  3. 使用内存池:复用ByteBuffer和Bitmap,减少频繁分配。
// 正确的Image处理方式
ImageReader.OnImageAvailableListener listener = reader -> {
    Image image = null;
    try {
        image = reader.acquireLatestImage(); // 只取最新帧
        if (image != null) {
            // 处理图像...
            processImage(image);
        }
    } finally {
        if (image != null) {
            image.close(); // 必须释放
        }
    }
};

还有一个容易被忽视的点——YUV转Bitmap。很多开发者习惯把YUV数据转成Bitmap再处理,但Bitmap在Java堆上分配,很容易触发GC。我建议直接用ImageReadergetPlanes()操作YUV数据,或者用RenderScript做GPU加速转换。

避坑指南:我曾经在某个项目里,把预览帧全部转成Bitmap存到ArrayList里做回放。结果用户拍了10张照片后,内存涨了300MB,直接OOM。后来改成只存YUV数据,内存降了70%。

知识体系总览

下面这张图,是我整理的Camera性能优化核心逻辑。你可以看到,启动、预览、拍照、内存这四个维度是相互关联的。比如启动优化做得好,预览延迟也会降低;内存优化做得好,拍照延迟也会改善。

Camera性能优化核心逻辑 启动优化 预览延迟优化 拍照延迟优化 内存优化与OOM 预加载CameraService 延迟非必要初始化 使用CameraProvider 缓冲区大小优化 分辨率控制 锁定帧率30fps ZSL零快门延迟 预对焦策略 硬件编码加速 及时释放Image 控制并发帧数 内存池复用 核心原则:启动要快、预览要稳、拍照要准、内存要省 四个维度相互影响,需要整体权衡,不能顾此失彼

最后说一句,性能优化没有银弹。每个项目、每款机型的情况都不一样。我建议你在做优化时,先用adb shell dumpsys cameraProfiler工具把瓶颈定位清楚,再针对性地优化。别一上来就改代码,那样容易白费功夫。


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