2 Commits
Author SHA1 Message Date
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
zijunchiang c207e7dadd test(accept): 发布包级端到端验收(读 zip 内 APK)+ T-AND-014
`build/verify.sh` 验的是 app/build/outputs/ 里**刚构建**的 APK,而用户装的是 zip
里的 app-release.apk。两端都因此翻过车(Web v0.7.4 实体注册、Windows v0.2.1 的
PE 版本元数据),故三端统一:每次发布都要有一条读产物本身的验收(文档 §7.5)。

新增 build/accept-release.sh,对一个 zip(或已解压目录)跑完:
  1) sha256(有 SHA256SUMS 时);
  2) 解压 + 文件清单(apk / README.txt / THIRD_PARTY.md / contracts/VERSION);
  3) APK 的 AndroidManifest versionName/versionCode = 发布版本(复用
     check-apk-version.sh —— 安装界面与应用信息页读的就是它);
  4) ABI 恰为 arm64-v8a + armeabi-v7a、无模拟器 ABI,且两个 ABI 下都有
     MediaPipe native 库(T-AND-012 崩溃根因之一);
  5) assets/pose_landmarker_lite.task 在包内;dex 里仍有
     poselandmarker/PoseLandmarker(R8 没整体裁掉);
  6) 有 apksigner 时顺带验签;
  7) 包内 contracts/VERSION 与仓库逐项一致(抓「APK 用旧契约构建、快照拷了新的」)。
package-release.sh 结尾打印下一步验收命令;README 补用法。

实测(对**已发布**的 v0.2.0 资产重新下载后跑):sha256 OK、版本元数据 0.2.0/200、
ABI 恰为两个真机 ABI、两个 ABI 下都有 MediaPipe native、模型资源在、dex 命中
PoseLandmarker、apksigner 通过、契约快照与仓库一致 → 发布包级验收全部通过。

反向验证(真实负例):
  * 篡改包内 contracts/VERSION 的 data-models=0.6.0 →
    ✗ data-models:包内 0.6.0,仓库 0.7.0,退出码 1;
  * 期望版本写 0.2.1 对 v0.2.0 的包 → ✗ versionName 是 '0.2.0',期望 '0.2.1'
    且 ✗ versionCode 是 200,期望 201。

诚实声明:**装到真机跑 30 分钟**这条脚本替代不了,仍属设备实测项;本脚本能证明的是
「装得上、不会因打包配置而崩」。verify.sh 仍全绿。
2026-09-17 04:51:16 +08:00