Android多SDK摄像头资源争抢与仲裁方案
发布时间:2026/9/19 11:32:10 锦皓数字建站

1. 项目概述当两个SDK同时伸手要摄像头Android系统会怎么判“两个 SDK 抢一个摄像头”——这句话不是比喻是我在做车载智能终端项目时连续三周被凌晨两点的崩溃日志叫醒后写在工位便签纸上的第一行字。它背后是真实存在的、高频发生的、几乎每个接入多厂商视觉能力的 Android 设备厂商都会撞上的硬伤Camera Device 资源独占性与多 SDK 并发调用诉求之间的根本矛盾。核心关键词就五个Android、SDK、摄像头、相机仲裁、Android设备。这不是某个特定 App 的兼容问题而是 Android 系统层 Camera HAL硬件抽象层与上层应用/SDK 交互机制共同决定的底层约束。简单说Android 的 Camera API无论是旧的 Camera API 还是新的 Camera2/CameraX设计哲学里摄像头设备本质上是一个排他性资源。就像一台物理打印机不能同时被两个 Word 文档打印——哪怕它们都装了驱动系统内核也只允许一个进程持有它的句柄。SDK 本质是封装了 Camera 调用逻辑的代码库当 A SDK比如某家 ADAS 厂商的车道线检测 SDK和 B SDK比如某家 DMS 厂商的驾驶员疲劳监测 SDK都试图通过CameraManager.openCamera()或CameraDevice.StateCallback获取同一枚前置或后置摄像头时系统不会自动“分时复用”而是直接抛出CameraAccessException错误码通常是CAMERA_IN_USE或CAMERA_ERROR_NOT_AVAILABLE。你看到的 App 崩溃、黑屏、预览卡死根源都在这里。这个问题特别容易被误判为“SDK 写得烂”或“手机兼容性差”。但实测下来哪怕在同一台 Pixel 7 上用官方 CameraX 示例 App 和某家头部视觉 SDK 同时运行照样抢失败。它不挑机型不挑 Android 版本从 Android 8 到 14 都存在只挑“是否允许多个客户端并发访问同一 Camera Device”。而答案从 Android 架构设计第一天起就是“不允许多个”。所以所谓“相机仲裁”不是让系统变魔术般支持并发而是由开发者主动设计一套规则在多个 SDK 之间协商谁先用、用多久、何时释放、失败后如何降级。它不是系统功能是必须手动实现的业务逻辑层调度器。适合谁看如果你正在做车载中控、智能座舱、工业手持终端、AR 教育盒子这类需要集成多家第三方视觉能力的 Android 设备开发或者你是 SDK 提供方正被客户反复投诉“你们的 SDK 和 XX 家冲突”那这篇就是为你写的。它不讲抽象理论只讲我踩过的坑、改过的代码、压测过 37 台不同品牌 Android 设备后的仲裁策略。下面所有内容都来自真实产线环境不是实验室 Demo。2. 核心设计思路为什么不能靠“加锁”或“重试”解决很多人第一反应是“加个全局锁不就行了”或者“捕获异常后等 500ms 再重试”——我试过全挂了。不是逻辑错是没吃透 Android Camera 的生命周期和线程模型。下面拆解为什么常见直觉方案会失效以及我们最终选择的“中心化仲裁器 状态机驱动”架构背后的硬逻辑。2.1 为什么“synchronized 锁”治标不治本假设你在 Application 类里写了个静态ReentrantLock cameraLockA SDK 调用前lock()B SDK 发现锁被占就wait()。表面看似乎能串行化访问。但问题在于Camera 的打开和关闭不是原子操作且涉及跨进程 IPC。CameraManager.openCamera()实际会触发 Binder 调用到cameraserver进程这个过程耗时几十到几百毫秒且可能因 HAL 层初始化失败而阻塞。如果 A SDK 拿着锁去openCamera()结果 HAL 初始化卡住比如某家国产 SoC 的 ISP 驱动有 bugB SDK 就永远wait()在那里整个 App UI 线程被拖死。更糟的是如果 A SDK 因异常没调用close()锁永远不释放设备直接变砖——用户重启 App 都没用因为锁是 static 的。提示Android 的 Camera 资源管理不在 Java 层而在 native 层的CameraDeviceClient。Java 层的CameraDevice对象只是个代理真正的设备句柄在cameraserver进程里。任何 Java 层锁都无法覆盖 native 层的资源状态。2.2 为什么“指数退避重试”会引发雪崩另一种常见做法是A SDK 打开失败就Thread.sleep(100)再试失败sleep(200)再失败sleep(400)……直到成功。这在单 SDK 场景下很稳妥但在双 SDK 场景下它制造了经典的“惊群效应”。A 和 B 几乎同时发起openCamera()请求都失败然后 A 在 100ms 后重试B 在 100ms 后重试——又撞上再失败A 在 200ms 后重试B 在 200ms 后重试……恶性循环。实测数据在低端联发科 MT6765 设备上这种重试模式会让openCamera()平均耗时从 80ms 拉长到 2.3 秒且失败率高达 47%。更致命的是它让两个 SDK 的调用时间完全不可预测DMS 算法要求每 200ms 必须拿到一帧而重试导致帧间隔抖动超过 800ms直接触发误报。2.3 我们最终采用的“三层仲裁架构”及其选型依据基于以上教训我们放弃了所有“在 SDK 调用点加补丁”的思路转而构建一个独立于所有 SDK 的中心化相机仲裁服务CameraArbiterService。它不是简单的队列而是包含三个协同层第一层设备层抽象Device Abstraction Layer不直接操作CameraManager而是封装一个CameraDeviceProxy它持有一个WeakReferenceCameraDevice和一个AtomicBoolean isReleased。所有 SDK 只能通过CameraArbiterService.acquireCamera(cameraId, priority)获取这个 proxy而不是原始CameraDevice。这样即使 SDK 持有 proxy只要它没调用proxy.open()就不会触发真正的openCamera()。这层隔离了 SDK 对底层 Camera API 的直接依赖。第二层优先级队列与抢占式调度Priority Queue Preemption仲裁器内部维护一个PriorityBlockingQueueCameraRequest每个CameraRequest包含cameraId、priority数值越小优先级越高、timeoutMs、callerTagSDK 标识。当新请求到来若当前无占用则立即执行若有占用且新请求priority current.priority则强制中断当前使用者调用current.proxy.close()并通知其“被抢占”给予 100ms 缓冲期保存最后一帧。实测证明ADASpriority1必须能无条件抢占 DMSpriority5否则紧急车道保持会失效。第三层状态机驱动的生命周期管理State Machine Driven Lifecycle仲裁器自身是一个StateMachine状态包括IDLE、ACQUIRING、ACQUIRED、RELEASING。每个状态转换都伴随明确的回调如onAcquired()、onReleased()SDK 必须注册这些回调才能收到帧数据。这确保了 SDK 无法绕过仲裁器直接操作 Camera也杜绝了“忘记 close”的内存泄漏。状态机还内置超时保护ACQUIRING状态超过 1500ms 自动转IDLE并回调onAcquireFailed()避免 HAL 卡死拖垮整机。这个架构的选型理由很务实它不挑战 Android 底层限制而是把“资源争抢”这个不可控问题转化为“请求调度”这个可控的软件逻辑问题。所有复杂度收束在仲裁器内部SDK 只需遵循简单接口大幅降低集成成本。更重要的是它可测试、可监控、可灰度——我们在生产环境埋点统计每个 SDK 的平均等待时长、抢占成功率、超时率这些数据直接驱动算法优化。3. 核心细节解析仲裁器如何与 SDK 无缝对接设计再好如果 SDK 接入成本高一线工程师就会抵触。我们花了两周时间打磨 SDK 接入协议目标是现有 SDK 无需修改一行核心算法代码只需替换 3 个 Camera 相关调用即可接入仲裁。下面详解关键细节、参数设计逻辑以及那些文档里绝不会写的“为什么这么设”。3.1 SDK 接入的“最小改造三步法”几乎所有视觉 SDK 的初始化流程都类似init()→startPreview()→setFrameCallback()。我们的仲裁协议只动这三处替换init()中的CameraManager获取方式原 SDK 代码mCameraManager (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);改为mCameraArbiter CameraArbiterService.getInstance(context); // 注意这里不获取 CameraManager而是直接向仲裁器申请重写startPreview()为异步 acquire 流程原 SDK 代码同步阻塞mCameraDevice mCameraManager.openCamera(cameraId, stateCallback, handler);改为异步非阻塞mCameraArbiter.acquireCamera(cameraId, new CameraArbiterCallback() { Override public void onAcquired(NonNull CameraDeviceProxy proxy) { // 此处 proxy 已 ready可调用 proxy.open() proxy.open(new CameraDevice.StateCallback() { ... }); } Override public void onAcquireFailed(int errorCode, String reason) { // 处理失败如降级到本地模拟摄像头 } }, priority, // 如 ADAS 传 1DMS 传 5 3000 // 最大等待 3 秒超时则失败 );将setFrameCallback()绑定到 proxy 的输出流原 SDK 代码直接 setSurfacemCaptureSession.setRepeatingRequest(request, null, handler);改为监听 proxy 的 Surface 输出proxy.setOnFrameAvailableListener(new CameraDeviceProxy.OnFrameAvailableListener() { Override public void onFrameAvailable(Surface surface) { // SDK 的图像处理逻辑放在这里surface 就是帧数据 processFrame(surface); } });注意CameraDeviceProxy内部已封装了ImageReader和Surface创建逻辑SDK 完全不用关心ImageReader的setMaxImages()、setOnImageAvailableListener()等细节。这是为了降低接入门槛做的关键封装。3.2 优先级priority参数的设定逻辑与实测阈值priority不是随便填的数字它直接决定抢占行为。我们定义了 5 级优先级每级对应明确的业务语义和实测响应时间Priority业务场景允许最大等待时长抢占权实测平均 acquire 耗时中端设备1ADAS 紧急车道保持100ms可抢占所有其他 priority42ms2DMS 驾驶员分神检测300ms可抢占 priority ≥368ms3车内乘客人数统计800ms可抢占 priority ≥4112ms4后视镜盲区影像增强1500ms仅可被 priority ≤2 抢占287ms5语音助手手势识别低频3000ms不可抢占他人仅排队等待415ms这个分级不是拍脑袋定的。我们做了两件事一是分析各算法的实时性 SLAService Level AgreementADAS 要求控制环路延迟 120ms所以 acquire 必须在 100ms 内完成二是用Systrace抓取不同 priority 下openCamera()的 native 调用耗时发现 priority 1 的请求在cameraserver进程中会被调度器优先处理比 priority 5 快 3.2 倍。所以 priority 不是“礼貌排序”而是直接映射到 Linux CFS 调度器的nice值仲裁器会通过Process.setThreadPriority()动态调整cameraserver中对应线程的优先级。3.3 timeoutMs 参数的计算公式与容错设计timeoutMs看似简单但设错会导致两种灾难设太短正常场景频繁失败设太长用户感知卡顿。我们推导出一个动态计算公式timeoutMs baseTimeout (maxWaitTime - currentQueueLength * avgAcquireTime) * 0.7其中baseTimeout是该 SDK 的 SLA 要求如 ADAS 的 100msmaxWaitTime是队列中最高 priority 请求的等待上限如 priority 1 是 100mscurrentQueueLength是当前仲裁队列中 pending 请求的数量avgAcquireTime是过去 10 次该 cameraId 的平均 acquire 耗时单位 ms这个公式保证当队列空时timeoutMs baseTimeout当队列积压严重如 5 个请求且历史 acquire 慢如 200ms则timeoutMs会自适应拉长避免因瞬时拥塞导致误判。但上限仍受maxWaitTime限制防止无限等待。更关键的是容错设计当timeoutMs到期仲裁器不会简单返回失败而是启动“降级通道”。例如对 DMS SDK降级为使用MediaCodec解码本地录制的 1080p 视频流提前缓存 3 秒虽然精度下降 15%但保证了基本功能可用。这个降级开关是 SDK 可配置的通过CameraArbiterConfig.setFallbackStrategy(FallbackStrategy.VIDEO_DECODE)启用。4. 实操过程从零搭建仲裁服务的完整步骤与现场记录下面是我手把手在一台 Android 12 的 RK3399 车载设备上从创建工程到跑通双 SDK 的完整实操记录。所有命令、代码片段、ADB 日志都来自真实终端不是伪代码。重点标注了那些“文档没写但实际必填”的坑。4.1 环境准备与依赖声明Android Studio 2022.3.1新建一个camera-arbiterModulebuild.gradleModule关键配置android { compileSdk 33 defaultConfig { minSdk 21 // 必须 ≥21Camera2 API 要求 targetSdk 33 // 关键必须声明 camera 权限否则 acquire 会静默失败 manifestPlaceholders [camera_permission: android.permission.CAMERA] } } dependencies { implementation androidx.camera:camera-core:1.2.3 // CameraX core implementation androidx.camera:camera-camera2:1.2.3 // Camera2 backend implementation androidx.concurrent:concurrent-futures:1.1.0 // 用于 ListenableFuture // 注意不要引入 androidx.camera:camera-lifecycle它会与 SDK 的 Activity 生命周期冲突 }提示minSdk 21是硬性要求。曾有客户坚持minSdk 19结果在 Android 4.4 设备上CameraManager的getCameraIdList()返回空数组连设备列表都拿不到。这不是 Bug是 Android 官方明确的 API 限制。4.2 核心类CameraArbiterService的骨架实现创建CameraArbiterService.java继承Service关键字段public class CameraArbiterService extends Service { private static CameraArbiterService sInstance; private final CameraManager mCameraManager; private final PriorityBlockingQueueCameraRequest mRequestQueue; private final AtomicReferenceCameraState mCurrentState; private final HandlerThread mHandlerThread; // 专用线程处理 Camera 调用 private final Handler mHandler; // 状态枚举 private enum CameraState { IDLE, ACQUIRING, ACQUIRED, RELEASING } Override public void onCreate() { super.onCreate(); sInstance this; mCameraManager (CameraManager) getSystemService(Context.CAMERA_SERVICE); mRequestQueue new PriorityBlockingQueue(); mCurrentState new AtomicReference(CameraState.IDLE); mHandlerThread new HandlerThread(CameraArbiterHandler); mHandlerThread.start(); mHandler new Handler(mHandlerThread.getLooper()); } // acquire 方法入口 public void acquireCamera(NonNull String cameraId, NonNull CameraArbiterCallback callback, int priority, long timeoutMs) { CameraRequest request new CameraRequest(cameraId, callback, priority, timeoutMs); mRequestQueue.offer(request); // 立即触发调度避免 queue.poll() 的竞态 scheduleNextRequest(); } private void scheduleNextRequest() { if (mCurrentState.compareAndSet(CameraState.IDLE, CameraState.ACQUIRING)) { mHandler.post(this::doAcquire); } } private void doAcquire() { CameraRequest request mRequestQueue.poll(); if (request null) { mCurrentState.set(CameraState.IDLE); return; } // 关键检查是否被更高优先级抢占 if (isPreemptedByHigherPriority(request)) { // 通知原请求者被抢占并重新入队 request.callback.onAcquireFailed(CameraArbiterCallback.ERROR_PREEMPTED, Preempted); scheduleNextRequest(); return; } // 执行真正的 openCamera try { mCameraManager.openCamera(request.cameraId, new CameraDevice.StateCallback() { Override public void onOpened(NonNull CameraDevice camera) { // 成功创建 proxy 并回调 CameraDeviceProxy proxy new CameraDeviceProxy(camera); request.callback.onAcquired(proxy); mCurrentState.set(CameraState.ACQUIRED); } Override public void onClosed(NonNull CameraDevice camera) { // 设备关闭清理 mCurrentState.set(CameraState.IDLE); scheduleNextRequest(); } Override public void onError(NonNull CameraDevice camera, int error) { // HAL 层错误记录并重试 Log.e(Arbiter, Camera open error: error); request.callback.onAcquireFailed(error, HAL Error); mCurrentState.set(CameraState.IDLE); scheduleNextRequest(); } }, new Handler(Looper.getMainLooper()) ); } catch (CameraAccessException e) { // 系统级拒绝如权限未授、设备忙 request.callback.onAcquireFailed(CameraArbiterCallback.ERROR_ACCESS_DENIED, e.getMessage()); mCurrentState.set(CameraState.IDLE); scheduleNextRequest(); } } }4.3CameraDeviceProxy的关键封装与 Surface 管理CameraDeviceProxy是 SDK 与 Camera 的唯一接口必须安全地管理Surface生命周期public class CameraDeviceProxy { private final CameraDevice mCameraDevice; private final ImageReader mImageReader; private final Surface mOutputSurface; private final AtomicBoolean mIsClosed; private final ListOnFrameAvailableListener mListeners; public CameraDeviceProxy(CameraDevice camera) { mCameraDevice camera; // 创建 ImageReader宽度高度必须与 SDK 需求匹配这里设为 1280x720 mImageReader ImageReader.newInstance(1280, 720, ImageFormat.YUV_420_888, 4); // maxImages4 防止丢帧 mOutputSurface mImageReader.getSurface(); mIsClosed new AtomicBoolean(false); mListeners new CopyOnWriteArrayList(); // 关键设置 ImageReader 监听将帧转发给 SDK mImageReader.setOnImageAvailableListener( reader - { if (mIsClosed.get()) return; Image image null; try { image reader.acquireLatestImage(); // 取最新帧丢弃中间帧 if (image ! null) { for (OnFrameAvailableListener listener : mListeners) { listener.onFrameAvailable(mOutputSurface); } } } finally { if (image ! null) image.close(); } }, new Handler(Looper.getMainLooper()) ); } public void open(CameraDevice.StateCallback callback, Handler handler) { // 此处不调用 openCamera因为 camera 已由仲裁器打开 // 只需创建 CaptureSession try { mCameraDevice.createCaptureSession( Arrays.asList(mOutputSurface), new CameraCaptureSession.StateCallback() { Override public void onConfigured(NonNull CameraCaptureSession session) { // 配置预览请求 CaptureRequest.Builder builder mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(mOutputSurface); session.setRepeatingRequest(builder.build(), null, null); } Override public void onConfigureFailed(NonNull CameraCaptureSession session) { // 配置失败通知 SDK } }, handler ); } catch (CameraAccessException e) { // 处理异常 } } public void setOnFrameAvailableListener(OnFrameAvailableListener listener) { mListeners.add(listener); } public void close() { if (mIsClosed.compareAndSet(false, true)) { mImageReader.close(); // 注意不调用 mCameraDevice.close()由仲裁器统一管理 } } }实操心得ImageReader.newInstance()的maxImages参数必须 ≥3。我们曾设为 1结果在 30fps 场景下acquireLatestImage()频繁返回null因为前一帧还没被 SDK 处理完ImageReader 缓冲区已满。设为 4 后丢帧率从 12% 降到 0.3%。这是硬件缓冲区与软件处理速度匹配的关键参数。4.4 在 Application 中初始化并启动服务MyApplication.javapublic class MyApplication extends Application { Override public void onCreate() { super.onCreate(); // 启动仲裁服务 Intent intent new Intent(this, CameraArbiterService.class); if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { startForegroundService(intent); } else { startService(intent); } // 注册全局异常捕获防止 SDK 崩溃影响仲裁器 Thread.setDefaultUncaughtExceptionHandler((thread, ex) - { Log.e(Arbiter, Uncaught exception in thread.getName(), ex); // 记录后不杀进程让仲裁器继续服务 }); } }并在AndroidManifest.xml中声明service android:name.camera.CameraArbiterService android:enabledtrue android:exportedfalse /4.5 双 SDK 并发测试与 ADB 日志验证部署后用 ADB 抓取关键日志adb logcat | grep -E (Arbiter|CameraArbiter)成功日志示例08-15 14:22:31.201 12345 12345 I Arbiter: Acquire request for camera0, priority1, timeout100ms 08-15 14:22:31.205 12345 12345 I Arbiter: State changed to ACQUIRING 08-15 14:22:31.247 12345 12345 I Arbiter: Camera opened successfully, creating proxy 08-15 14:22:31.250 12345 12345 I Arbiter: State changed to ACQUIRED 08-15 14:22:31.252 12345 12345 I Arbiter: Callback onAcquired called for priority1 08-15 14:22:32.105 12345 12345 I Arbiter: New acquire request for camera0, priority5, timeout3000ms 08-15 14:22:32.108 12345 12345 I Arbiter: Priority 5 request queued, waiting... 08-15 14:22:32.850 12345 12345 I Arbiter: ADAS released camera, state changed to IDLE 08-15 14:22:32.852 12345 12345 I Arbiter: Processing queued request priority5 08-15 14:22:32.895 12345 12345 I Arbiter: Camera opened for priority5, callback called看到Priority 5 request queued, waiting...和后续的Processing queued request说明仲裁生效。如果看到ERROR_ACCESS_DENIED或ERROR_PREEMPTED则说明抢占逻辑在工作。5. 常见问题与排查技巧实录那些让我掉头发的坑最后这部分全是血泪经验。不是教科书式的 FAQ而是我对着日志一行行 debug、在 37 台设备上反复验证后总结出的“速查表”。每个问题都附带真实日志片段、定位方法和一招毙命的解决方案。5.1 问题速查表高频故障现象与根因定位现象典型日志特征根本原因一招解决SDK 启动后黑屏无任何错误日志logcat中完全搜不到Arbiter关键字CameraArbiterService未启动或MyApplication.onCreate()未执行在Application的onCreate()第一行加Log.d(App, App created)确认 Application 生命周期正常检查AndroidManifest.xml中application的android:name是否指向正确类两个 SDK 都能 acquire 成功但只有一方有帧数据Arbiter日志显示onAcquired被调用两次但onFrameAvailable只触发一次CameraDeviceProxy的mOutputSurface被重复创建或ImageReader的Surface被错误复用检查CameraDeviceProxy构造函数确保mImageReader和mOutputSurface是 per-request 创建而非 static 共享ImageReader必须在open()前创建不能复用高优先级 SDK 被低优先级长期占用不触发抢占Arbiter日志显示Priority 1 request queued但后续无Preempted记录isPreemptedByHigherPriority()逻辑有 bug或mCurrentState更新不及时在scheduleNextRequest()前加Log.d(Arbiter, Current state: mCurrentState.get())确保onClosed()回调中mCurrentState.set(IDLE)执行且无异常吞没设备偶尔 crashlogcat 显示JNI ERROR (jobject is an invalid reference)crash 日志中出现art/runtime/check_jni.cc:65CameraDeviceProxy的mCameraDevice在onClosed()后仍被调用或Surface在ImageReader.close()后被 SDK 使用在CameraDeviceProxy.close()中先mImageReader.close()再mIsClosed.set(true)在onFrameAvailableListener中每次调用前检查if (!mIsClosed.get())在 Android 14 设备上 acquire 失败错误码CAMERA_ERROR_UNKNOWNlogcat中CameraManager.openCamera()抛CameraAccessExceptione.getReason()返回100Android 14 引入了更严格的CameraManager权限检查需在AndroidManifest.xml中显式声明 android:foregroundServiceTypemicrophonecamera5.2 独家避坑技巧文档里找不到的实战秘籍技巧1用adb shell dumpsys media.camera查看实时占用这个命令能显示当前cameraserver进程中每个 Camera Device 的持有者 PID 和包名。当遇到“明明没 SDK 在用却提示CAMERA_IN_USE”时运行adb shell dumpsys media.camera | grep -A 10 camera0如果看到PID: 1234 Package: com.android.systemui说明是系统 UI如锁屏相机快捷方式占用了资源。此时需在CameraArbiterService的acquire前加delay(500)让系统 UI 释放而非硬抢。技巧2为低端设备定制ImageReader格式某些 Rockchip 平台如 RK3288的 HAL 不支持YUV_420_888openCamera()会静默失败。实测发现改用ImageFormat.NV21可 100% 兼容。在CameraDeviceProxy构造函数中// 根据设备型号动态选择格式 String device Build.DEVICE.toLowerCase(); int format device.contains(rk3288) ? ImageFormat.NV21 : ImageFormat.YUV_420_888; mImageReader ImageReader.newInstance(width, height, format, 4);技巧3规避CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL陷阱很多 SDK 会检查CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL是否为HARDWARE_LEVEL_FULL才启用高级功能。但仲裁器acquire后SDK 拿到的CameraCharacteristics是CameraDevice的不是CameraManager的。必须在CameraDeviceProxy中提供getCharacteristics()方法内部调用mCameraManager.getCameraCharacteristics(cameraId)而非mCameraDevice.getCameraCharacteristics()后者在 Android 12 已废弃。技巧4处理SurfaceTexture的 GL 上下文丢失当 SDK 使用SurfaceTexture作为输出时onFrameAvailable()可能因 GL 上下文切换而失效。解决方案是在CameraDeviceProxy中增加setOnSurfaceTextureUpdatedListener()并在onFrameAvailable()中强制eglMakeCurrent()确保 GL 上下文有效。这需要 SDK 提供EGLContext属于深度集成但对 AR 类应用必不可少。5.3 性能压测结果与调优建议我们在 37 台设备覆盖高通、联发科、瑞芯微、紫光展锐上做了 72 小时连续压测关键指标如下设备类型平均 acquire 耗时最大队列长度抢占成功率降级触发率高端骁龙8 Gen238ms1100%0%中端天玑810065ms299.8%0.1%低端MT6765210ms594.3%3.7%调优建议对低端设备将timeoutMs基础值提高 50%并启用FallbackStrategy.VIDEO_DECODE对高帧率需求如 60fpsImageReader的maxImages必须 ≥6且acquireLatestImage()改为acquireNextImage()配合Image.close()的及时性内存敏感场景CameraDeviceProxy的mImageReader在close()后立即System.gc()实测可减少 12MB 堆内存占用。我在实际项目中发现最有效的优化不是改代码而是在设备出厂前用dumpsys media.camera扫描所有摄像头生成一份《本设备 Camera HAL 兼容性白皮书》明确标注每个 cameraId 支持的分辨率、格式、最大帧率。这份文档让 SDK
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。