测试与发布证据

Android R8/ProGuard 本地 AI 发布版故障诊断

只诊断 release shrinker 对动态入口的影响,不重复讲 ABI 拆分或 JNI 异常清理。

审核状态: 内容已校验;中文稿供用户审核 · 2026-10-05 · Fact IDs: COVE-LIFECYCLE-01, BENCHMARK-01

直接回答

R8 会重命名、删除代码并优化程序结构;如果代码通过 JNI 回调或反射动态访问,而静态分析看不到调用关系,就可能只在混淆后的 release 构建中失败。Android 官方 keep-rule 示例说明,原生代码调用 Java/Kotlin 方法时,R8 无法自动看到这些上行调用,必须通过规则保留所需类成员。故障可能表现为方法不存在、类查找失败或行为改变,但不能自动等同于 native 库缺失或模型制品错误。应先比较准确的 debug 与 release 路径,再加规则。 锁定应用构建变体、AGP/R8 配置、运行时制品、ABI 打包、mapping 文件以及具体桥接入口。保存完整 release 异常和阶段:库加载、JNI 注册、native 回调托管代码、反射构造、序列化、模型初始化或任务执行。R8 mapping 文件可帮助把混淆名称映射回源代码;应按 release 保存。不要随意公开或写入普通日志,因为映射文件可能暴露代码结构,也不能仅为让崩溃消失就加全局 keep-all。 为应用使用的每个动态入口准备小型夹具:Kotlin 到 native 的调用、native 回调、反射创建的类、序列化器及真实集成使用的注解成员。使用相同输入分别构建 debug 和混淆后的 release。如果 debug 通过而 release 失败,结合 mapping 和 shrinker 诊断找到确切被删除或改名符号,再为最窄的文档化入口添加 keep 规则并重跑受影响测试。不能假设运行时依赖中的每个类都需要保留;库消费者规则与应用自有回调的所有权不同。 将 R8 故障与 native split/ABI 交付分开。如果找不到或无法打开库,先检查 APK/设备定向 split 与 ABI。如果库已加载、但 release 独有回调或反射方法缺失,再查 R8 转换。如果 native 调用已经进入方法后抛错,则走 JNI 异常边界;若引擎已加载但输出变化,则另测制品、tokenizer 和提示。这样的顺序能避免用宽泛 keep 规则掩盖不同问题。 控制 release 证据:保存构建变体、mapping 文件摘要、keep-rule 版本、制品/运行时身份、设备/系统、调用入口、失败阶段和结果。测试正常与失败路径,包括清理和下一次请求。本文不声称 Cove 当前存在 R8 缺陷,也不声称某条规则能修复所有运行时;只提供对已测试代码入口和制品的 release 故障方法。 不要为调试方便就永久开启宽泛规则。精确规则应说明保留了哪个 JNI 方法、反射类或签名类型,以及证据如何证明运行时动态调用了它。若 R8 mapping 显示符号仍在而异常发生于方法内部,就应转入 JNI/运行时阶段排查,不再扩大规则范围。变更后比较 release 体积、映射和实际任务行为;没有原始性能或设备数据时,不把“崩溃不再出现”宣称成推理变快或设备兼容改善。

Debug 推理成功,混淆 release 找不到 native 回调

本地模型在 debug 版可初始化。release 构建在 native 代码回调 Kotlin 时失败;模型制品和目标设备似乎未变,但团队未比较 R8 mapping 或确定最早缺失符号。

决策

比较等价的 debug/release 制品,检查 mapping 和发布诊断,仅为有证据的动态入口添加窄范围 keep 规则。

预期结果

团队能把 shrinker 删除/改名与 ABI split、JNI 异常和模型加载问题分开,不会交付 keep-all 规则。

需要收集的证据

只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:

  • COVE-LIFECYCLE-01:当前 ModelManager 有显式加载状态;它不证明 release shrink 配置正确。
  • BENCHMARK-01:项目没有完整真实设备基准集;R8 故障验证是构建/运行边界,不应伪装为性能结果。

发布结论只对所记录的配置和验收集有效;扩大适用范围需要更多记录。

实施流程

  1. 冻结 debug 与 release 身份. 记录应用变体、AGP/R8 版本和配置、运行时依赖、ABI、模型制品、release mapping 文件及失败夹具。两种变体尽量使用相同输入。
  2. 定位动态调用入口. 找出 native 到托管回调、反射加载类、序列化器或按名称访问的方法。记录预期类/成员/签名及查找失败阶段。
  3. 修改规则前先分类失败边界. 确认 native 库是否加载。缺库/ABI 属于包检查;只有 release 独有的回调或反射成员缺失才支持怀疑 shrinker。安全保留完整异常和 mapping。
  4. 只加文档支持的窄规则. 根据官方 keep-rule 指南只保留动态调用所需的类/成员/签名。避免通配符 keep-all,否则可能阻止大量无关优化。
  5. 用混淆 release 做回归与恢复测试. 在 release 重测 native 回调、反射、初始化、任务、异常、清理和下一请求。按 release 保留 mapping,并确认 debug 行为未意外变化。

让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。

验收标准

  • Debug/release 记录绑定同一制品、运行时和任务,并保存用于诊断的 release R8 mapping。
  • 只有 native 库成功加载且必需动态符号缺失/改名时,才归因 shrinker。
  • Keep 规则精确对应文档化 JNI/反射入口,并经过混淆版回归。
  • 结论不混淆 R8、ABI split、JNI 异常处理、模型兼容和运行性能。

决策对照表

维度选择或证据解读方式
Native 库找不到/无法打开检查 APK/split ABI 与链接依赖属于打包或库加载边界,keep 规则可能无效。
Release 独有回调/方法缺失检查 R8 mapping 与动态入口规则静态分析看不到 JNI/反射访问时,可能删除或重命名托管成员。
方法存在但内部抛错检查 JNI 异常契约与运行时阶段符号已找到,应查异常和清理,不要继续扩大 keep 范围。

需要避免的失败模式

  • release 失败就加 keep-all,未确认是否为 R8 删除或改名。
  • 把 ABI 原生库缺失、JNI 异常和模型加载失败混成同一故障。

还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。

最小复现记录

层级记录内容
设备厂商、型号、芯片、内存档位、操作系统版本
软件应用版本和 Git 提交号
模型系列、变体、版本、格式、文件长度、摘要、量化方式
运行时名称、版本、请求后端及实际后端证据
工作负载样例版本、输入摘要、提示词摘要、输出策略
结果完成、失败、取消、回退及保护隐私的诊断信息

发布检查表

  • ☐ 主要搜索问题已回答,且没有未经支持的数字。
  • ☐ 所有模型文件和运行时版本均已固定。
  • ☐ 代表性任务和失败处理策略均有说明。
  • ☐ 生命周期、取消、清理和回退均已测试。
  • ☐ 用户内容与网络边界均有说明。
  • ☐ 结论只适用于被记录的配置。
  • ☐ 新增测试结果时链接到原始方法或证据。

来源与相关证据

中文部署与排错专题见。奇连 AI 端侧专题。

常见问题

Release 推理崩溃就应该加 keep-all 吗?

不应该。先确认是否有动态调用类或方法被删除/改名。仅为已证实的 JNI/反射入口添加窄规则,再重测混淆版本,并检查规则没有掩盖其它边界。

R8 问题是否等于 APK 没有原生库?

不一定。库缺失或 ABI 不匹配属于打包问题;R8 也可能删除/改名动态调用的托管成员。应先按最早失败阶段、APK 内容和 mapping 分类;若方法已进入后才抛错,应检查异常契约与清理。

Debug 通过是否足够验证发布构建?

不够。混淆会改变最终制品。必须测试 release 变体并保存 mapping,覆盖 native 回调、反射、初始化、任务和恢复,同时比较异常阶段;仅构建成功不能证明动态入口可用。