Files
opendance-android/tasks/015-native-libs.md
T
zijunchiang 95429e8ce3 test(native): APK native 库体检——ELF 类型 / DT_NEEDED 闭包 / 16 KB 页对齐(T-AND-015)
T-AND-012 的结论是「ExceptionInInitializerError 的根因在集成配置(native 解压 +
R8 + ABI),不是 MediaPipe 本身不稳定」,但当时的核查是**手工一次性**的(unzip 看
lib/、grep seeds、strings 找类名)——下次照样可能漏,且漏了也没人发现,只会在用户
装上手机、点开相机那一刻暴露。

新增 build/check_native_libs.py(纯 Python 标准库解析 ELF,不需要 readelf / NDK),
检查三项**无设备也能查**的「一装上就崩」根因:
  * ABI 与 ELF 类型匹配(把 x86_64 的 .so 放进 lib/arm64-v8a/ → 一加载就崩);
  * DT_NEEDED 依赖闭包(非系统库必须同 ABI 目录内自带;MediaPipe 早期版本的
    libc++_shared.so 就是这么缺的);
  * PT_LOAD 页对齐 ≥ 16 KB(Android 15 起部分设备页 16 KB,4 KB 对齐会直接加载失败;
    Google Play 2025-11 起也要求)。
外加 zipalign -c -P 16 -v 4(有 build-tools 时)。已接入 verify.sh 与 accept-release.sh。

实测(已发布的 opendance-android-v0.2.0.zip 内 APK):
  双 ABI 下 6 个 .so 全部 ELF 类型匹配、依赖闭包完整(libmediapipe_tasks_jni.so 只依赖
  libdl/libjnigraphics/libEGL/libGLESv2/libm/libz/liblog/libandroid/libc,全是系统库)、
  PT_LOAD p_align=0x4000(16 KB 兼容)、zipalign -P 16 通过。
  结论:打包配置这一侧的「一装上就崩」风险已排除,剩下的只能靠真机实测。

与 readelf 交叉验证(6 个 .so 的 DT_NEEDED / p_align / e_machine 逐项一致)——
这次交叉验证抓到解析器自己的 bug:第一版程序头偏移写错(ELF64 的
p_offset/p_vaddr 之后第三个 QWORD 是 p_paddr 而非 p_filesz),导致 vaddr→offset
映射失败、DT_NEEDED 解析为空,页面照样打印「✓ 依赖闭包完整(系统库 0 项)」——
一句**假通过**,唯一破绽就是那个 0。教训:通过输出必须带可核对的数量/取值。

反向验证(三条分支都用真实负例证明会失败):
  * 注入宿主 x86_64 的 libz.so.1 到 lib/arm64-v8a/libbogus.so → 同时报 ELF 类型不符、
    缺依赖 libc.so.6、页对齐 0x1000 < 16384;
  * 把 libz.so 从系统库白名单去掉 → 报缺少依赖 libz.so(证明闭包分支是活的);
  * --expected-abis 传错 → 报 ABI 集合不符。

verify.sh 全绿;对**已发布资产**重新下载后跑完整 accept-release.sh 全部通过。
未新发版本:APK 本身未变(本任务是检查工具与文档),README 的新增项随下次发布进包。
2026-09-17 05:25:04 +08:00

4.6 KiB
Raw Blame History

T-AND-015:APK native 库体检(ABI / ELF 类型 / 依赖闭包 / 16 KB 页对齐)

  • 状态: done
  • 依赖: T-AND-012(MediaPipe 初始化崩溃修复)/ T-AND-014(发布包级验收)
  • 文档: v1.6 §6.6 / §7.5
  • 契约: 不涉及(打包体检)

为什么需要

T-AND-012 的结论是「ExceptionInInitializerError 的根因在集成配置(native 解压 + R8 + ABI), 不是 MediaPipe 本身不稳定」。但当时的核查是手工一次性的:unzip 看 lib/、grep seeds、 strings 找类名。手工核查的问题是下次照样可能漏,而且漏了也不会有人发现 —— 这类缺陷只会在用户装上手机、点开相机的那一刻暴露。

本任务把它变成可重复、可反向验证的产物级检查:纯 Python 标准库解析 ELF, 不依赖 readelf / NDK,因此任何装了 python3 的机器(含 CI)都能跑。

检查项(都在「无设备也能查」的范围内)

项 为什么它会导致「一装上就崩」
ABI 与 ELF 类型匹配 把 x86_64 的 .so 放进 lib/arm64-v8a/:安装能过,一加载就 UnsatisfiedLinkError
DT_NEEDED 依赖闭包 只打包 libmediapipe_jni.so 却没带它依赖的 libc++_shared.so 是 MediaPipe 的经典坑;非系统库必须同 ABI 目录内自带(系统库由 Android 提供,见脚本内白名单)
16 KB 页对齐 Android 15 起部分设备页大小为 16 KB;PT_LOAD 的 p_align 若仍是 4 KB(0x1000),这些设备上直接加载失败(Google Play 2025-11 起也要求 16 KB 对齐)
zip 层对齐 zipalign -c -P 16 -v 4(有 build-tools 时跑)

实现:build/check_native_libs.py(新增,纯标准库)。已接入 build/accept-release.sh(发布包)与 build/verify.sh(本地构建)。

实测(已发布的 opendance-android-v0.2.0.zip 内 APK)

[arm64-v8a]
  ✓ libandroidx.graphics.path.so     ELF64 AArch64 | 系统库 3 项 | 页对齐 0x4000
  ✓ libimage_processing_util_jni.so  ELF64 AArch64 | 系统库 6 项 | 页对齐 0x4000
  ✓ libmediapipe_tasks_jni.so        ELF64 AArch64 | 系统库 9 项 | 页对齐 0x4000
[armeabi-v7a]
  ✓ libandroidx.graphics.path.so     ELF32 ARM      | 系统库 3 项 | 页对齐 0x4000
  ✓ libimage_processing_util_jni.so  ELF32 ARM      | 系统库 6 项 | 页对齐 0x4000
  ✓ libmediapipe_tasks_jni.so        ELF32 ARM      | 系统库 9 项 | 页对齐 0x4000
✓ native 库体检通过 | ✓ zipalign -c -P 16 -v 4 通过

结论(对当前产物):没有缺失的 native 依赖(libmediapipe_tasks_jni.so 只依赖 libdl/libjnigraphics/libEGL/libGLESv2/libm/libz/liblog/libandroid/libc,全部系统库), 两个 ABI 都已是 16 KB 页对齐,ABI 目录内 ELF 类型正确。 也就是说:打包配置这一侧的「一装上就崩」风险已被排除;剩下的只能靠真机实测。

与 readelf 交叉验证

解析器输出与 LC_ALL=C readelf -d/-lW 在 6 个 .so 上逐项一致 (DT_NEEDED 集合、PT_LOAD p_align、e_machine)。

这次交叉验证抓到了解析器自己的一个 bug:第一版程序头字段偏移写错 —— ELF64 的 p_offset/p_vaddr 之后第三个 QWORD 是 p_paddr 而不是 p_filesz (ELF32 同理)。后果是 vaddr → offset 映射永远失败、DT_NEEDED 解析为空, 于是页面照常打印「✓ 依赖闭包完整(需自带 无;系统库 0 项)」—— 一句假通过。0 项 这个数字是唯一的破绽,靠和 readelf 比对才暴露出来。 教训:「通过」的输出里必须带可核对的数量/取值,否则假通过和真通过长得一模一样。

反向验证(三条例都实测会失败)

负例 结果
把宿主 x86_64 的 libz.so.1 塞进 lib/arm64-v8a/libbogus.so ✗ 三条同时报:ELF class=2 machine=x86-64,与该 ABI 不符、缺少依赖 libc.so.6、PT_LOAD p_align=0x1000 … < 16384
把 libz.so 从系统库白名单里去掉(证明闭包这条分支是活的) ✗ arm64-v8a/libmediapipe_tasks_jni.so:缺少依赖 libz.so、armeabi-v7a/... 同样报
--expected-abis 传成单个 ABI ✗ ABI 集合为 [...],期望 [...]

仍未覆盖(诚实声明)

  • 运行时行为:库能加载 ≠ 推理能跑。GPU/驱动差异、真机内存、连续 30 分钟稳定性仍需设备;
  • 设备页大小:这里只保证「16 KB 兼容」,无法证明某台具体设备上的实际行为;
  • 因此本检查不替代真机验收,只是把「打包配置错误」这一类彻底挡在发布前。