第 3 章 设备兼容性矩阵与适配策略

3.1 为什么需要设备兼容性矩阵

Android 多媒体管线中的 Surface 边界既受 AOSP 框架版本制约,又被厂商定制行为深度影响。任何无法在版本与厂商间正确映射的适配策略都会导致黑屏、花屏、绿屏或 SurfaceFlinger 死锁。兼容性矩阵是适配工作的出发点:它把 API 版本差异、厂商定制行为 和 common/vendor 层边界 对齐到一个可执行的决策表上。

3.2 不同 Android 版本 API 差异(关键边界)

以下表格列出影响 Surface 分配的 API 级别变化,这些差异直接影响 MediaCodec / MediaPlayer / SurfaceView / TextureView 的选择策略。

API 级别 Android 版本 关键变更点 Surface 边界影响
23 (M) 6.0 MediaCodec.createPersistentInputSurface() 引入 准备持久输入 Surface,但部分 OEM 实现不完整
26 (Oreo) 8.0 Surface.getBuffersSize() 废弃;getAvailableImages() 替代 厂商需更新 Gralloc 实现,否则 BufferQueue 计数错误
29 (Q) 10 MediaCodec.getOutputSurface() 改为 createInputSurface() 同步模型 部分华为 EMUI 10 仍使用旧异步模型,导致 dequeueOutputBuffer 超时
30 (R) 11 SurfaceControl 引入;SurfaceView 必须通过 WindowManager 创建 OEM 自定义 Launcher 若跳过 SurfaceControl 会引发 BufferQueue 泄漏
31 (S) 12 MediaCodec 新增 PARAMETER_KEY_SUSPEND;Surface 必须支持多缓冲 小米 MIUI 13 的 SurfaceFlinger 对 SUSPEND 支持不完整
33 (T) 13 SurfaceView.setSecure() 改为 setProtectedContent();DRM 路径变化 OV 定制 ROM 的 Secure Surface 层次与 AOSP 不一致
适配策略提示: 不要仅依赖 Build.VERSION.SDK_INT 条件判断;必须结合 Build.MANUFACTURER 与 Build.BRAND 进行二次路由,因为同一 API 级别在不同厂商的实现差异可能大于跨版本差异。

3.3 主流厂商定制行为矩阵(华为/小米/OV/三星)

以下四家 OEM 的定制行为具体到 Surface 层的差异记录在 /vendor/etc/ 和 /system/etc/ 配置中。

厂商 典型定制行为 导致问题 解决策略
华为 (EMUI/HarmonyOS)
  • 修改 SurfaceFlinger 的 BufferQueue 最大容量为 3(AOSP 默认为 4)
  • 在 MediaCodec 中注入私有参数 "vendor.huawei.input-buffer-count"
dequeueInputBuffer 返回 -1 时,应用进入死循环 在 configure 前设置 MediaFormat.KEY_MAX_INPUT_SIZE 为预估帧大小
小米 (MIUI/HyperOS)
  • 移除了 SurfaceFlinger 的 vsync 回调间隔限制,导致 SurfaceView 帧率暴增
  • 在 MediaCodec 初始化时强制检查 drm 路径
播放非 DRM 内容时崩溃:java.lang.IllegalStateException: drm not supported 在 configure 之前捕获 MediaCodec.CodecException 并 fallback 到 MediaPlayer
OPPO/vivo (ColorOS/OriginOS)
  • 在 android.graphics.SurfaceTexture 中增加 setDefaultBufferSize 验证
  • 修改 BufferQueue 的 dequeue 超时时间从 500ms 缩短至 200ms
在弱光/低电量场景下 SurfaceTexture.updateTexImage 频繁超时 降低 setDefaultBufferSize 的宽高到 1080p 以内,并使用 GLSurfaceView 替代 TextureView
三星 (One UI)
  • 在 SurfaceFlinger 的中增加了 BufferQueue 校验,要求 Buffer 宽度为 16 对齐
  • 在 MediaCodec 中强制使用 color-format=YUV420Flexible
宽高不是 16 倍数时渲染黑屏 MediaCodec 配置时计算 width=ceil(width/16)*16

3.4 Common 层与 Vendor 层边界

Android 多媒体管线中的 Surface 边界落在 common 层(AOSP 框架) 与 vendor 层(HAL + OEM 驱动) 之间。

  • common 层: android.hardware.graphics.composer (HWC) 的通用实现、libstagefright 解码器、android.media.MediaCodec Java 接口。
  • vendor 层: 厂商提供的 gralloc 实现、hwcomposer 私有扩展、Codec2 驱动的私有 input-buffer 策略。

适配核心在于 在 common 层提供最大兼容的配置,同时在 vendor 层注入运行时修正。

设备兼容性矩阵与适配策略 设备兼容性矩阵与适配策略 API 29 (Android 10) API 30 (Android 11) API 31 (Android 12) API 32 (Android 12L) API 33 (Android 13) 华为 (EMUI) 小米 (MIUI) OV (ColorOS) 三星 (One UI) Common 层(AOSP 框架) MediaCodec API | Surface | BufferQueue | HWC 通用实现 Vendor 层(HAL + OEM 驱动) Gralloc 分配 | 厂商私有参数 | hwcomposer 扩展 | Codec2 私有策略 适配策略:条件判断 + 特性检测 + 资源隔离 矩阵映射流程: 检测 SDK_INT → 读取厂商标识 → 查表获取 vendor 差异 → 注入适配参数 输出:稳定的 Surface 边界(无花屏/黑屏/绿屏)

3.5 适配策略实现流程

  1. 运行时检测: 使用 Build.VERSION.SDK_INT 确定基础 API 级别;使用 Build.MANUFACTURER 与 Build.BRAND 确定厂商。
  2. 特性查询: 调用 MediaCodecList.getCodecInfoAt(index).getCapabilitiesForType() 获取 colorFormats、setFeatureEnabled() 等厂商扩展。
  3. 资源隔离: 在 res/xml/ 下按厂商 + API 级别放置 media_codecs.xml 覆盖文件(仅限 AOSP 平台,OEM 定制 ROM 需在 vendor/etc/ 中配置)。
  4. 注入适配参数: 在创建 MediaCodec 之前根据矩阵表设置 MediaFormat 键值对。

3.6 代码示例:多条件匹配适配

// 适配核心:根据 SDK_INT 和厂商注入参数
public static MediaFormat adjustForDevice(MediaFormat fmt) {
    int api = Build.VERSION.SDK_INT;
    String manufacturer = Build.MANUFACTURER.toLowerCase();

    // 三星 16 对齐
    if (manufacturer.contains("samsung")) {
        int w = fmt.getInteger(MediaFormat.KEY_WIDTH);
        int h = fmt.getInteger(MediaFormat.KEY_HEIGHT);
        w = (int) Math.ceil(w / 16.0f) * 16;
        h = (int) Math.ceil(h / 16.0f) * 16;
        fmt.setInteger(MediaFormat.KEY_WIDTH, w);
        fmt.setInteger(MediaFormat.KEY_HEIGHT, h);
    }

    // 华为 buffer 数量修正
    if (manufacturer.contains("huawei")) {
        fmt.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920 * 1080 * 3 / 2);
        // 覆盖默认 buffer 策略
    }

    // 小米 DRM 检查修正
    if (manufacturer.contains("xiaomi") && api >= 31) {
        fmt.setString(MediaFormat.KEY_FEATURE_DRM, "false");
    }

    // OV 超时修正:降低默认 buffer size
    if (manufacturer.contains("oppo") || manufacturer.contains("vivo")) {
        fmt.setInteger(MediaFormat.KEY_MAX_WIDTH, 1920);
        fmt.setInteger(MediaFormat.KEY_MAX_HEIGHT, 1080);
    }

    return fmt;
}
常见错误: 部分开发者在 MediaCodec 配置后调用 format.setInteger(),这会被 configure() 忽略。所有适配参数必须在 configure() 调用前设置完毕。若需要在解码过程中调整 Surface 大小,应使用 SurfaceHolder.setFixedSize() 配合 surfaceChanged() 回调。

3.7 调试与验证方法

  • 抓取厂商日志: 在 logcat 中使用 libstagefright 和 gralloc 标签:
    adb logcat -s libstagefright:V gralloc:V *:S
  • 检查 Surface 状态:
    adb shell dumpsys SurfaceFlinger | grep -A 5 "BufferQueue"
  • 验证对齐: 通过 adb shell getprop 获取厂商特定属性(如 ro.hardware.gralloc、ro.vendor.sf.bq_size)。

3.8 性能与稳定性优化

  1. 缓存适配矩阵: 不要每次 configure() 都重新查询 Build 值;在 Application 启动时一次性读取并存为静态 DeviceProfile。
  2. 异步 fallback: 厂商私有参数注入失败时使用 MediaCodec.Callback 异步通知主线程切换解码器类型(如降级到 MediaPlayer)。
  3. 最小化配置差异: 对于难以统一处理的三星对齐问题,在 SurfaceView 创建时使用 WindowManager.LayoutParams 的 setFormat(PixelFormat.RGBA_8888) 强制像素格式,绕过 Gralloc 的对齐校验。