Files
opendance-android/build/verify.sh
T
zijunchiang 7ebbaef631 feat(net,pose): 连接状态机与保活对齐 + MediaPipe 崩溃修复(T-AND-010/011/012)
契约 websocket-keypoints 0.1.0 -> 0.2.0(向后兼容,子协议名与帧 version 不变;
先在 opendance-web 修改并提升版本,再同步三端快照)。

T-AND-011:连接状态异常、频繁重连(查实 3 个根因)
1. 保活用错了层:Windows 端有 5s 静默超时(只看应用层收到的帧),而本端只有
   OkHttp 的协议层 ping —— 它由底层库自动应答、**不暴露给应用层**,根本不能续命。
   画面里没人时 5s 必被踢、再重连,循环往复。
   - 契约新增 §1.1(welcome/config 声明 silentTimeoutMs 与 heartbeatIntervalMs,
     且心跳必须 < 静默超时的一半)与 §3.6(应用层 ping/pong);
   - Windows 端超时 5s -> 15s、期望心跳 5s,并在 welcome/config 中下发;
   - 本端按声明发 ping,连续 3 个周期无 pong 才判定链路已死并主动重连。
2. 不该重连时也重连:契约新增 §7 关闭码策略,1000/1001/1002 与主动停止一律不重连。
3. frame_id 恒为 0:PoseDetector.detect() 算了 safeTimestamp 却忘了写回
   lastTimestampMs,接收端的丢包检测因此完全失效。已修。

其他对齐:
- 契约新增 §8 连接状态机 idle -> resolving -> connecting -> handshaking ->
  streaming -> degraded -> reconnecting -> stopped,两端同名便于对照日志;
  resolving 只对主机名生效,局域网直连 IP 直接进 connecting;
- 退避与抖动按 §7.1 冻结:0.5s -> 1 -> 2 -> 4 -> 8 -> 15s 封顶 + ±20% 抖动;
- 契约新增 §5 设备去重:hello 携带稳定 deviceId(ANDROID_ID 的 SHA-256 前 16 位,
  不含硬件序列号),Windows 端据此去重、旧连接以 1000 关闭;
- 新增 util/ConnectionLog(环形缓冲,最近 500 条:状态迁移 / close code /
  子协议协商 / 首帧时间 / 保活参数),运行页可一键导出(FileProvider)。

T-AND-012:MediaPipe 初始化崩溃(ExceptionInInitializerError: null)
- 诊断:新增 pose/PoseDiagnostics 展开 cause 链取真因(通常 UnsatisfiedLinkError)、
  区分 native 加载失败、比对设备 ABI 与 APK 内 ABI 并给出可执行结论;
- native 解压:packaging { jniLibs { useLegacyPackaging = true } }(AGP 8 release
  默认 extractNativeLibs=false,MediaPipe JNI 对它不兼容);
- R8:补 -keepclasseswithmembernames 的 native 方法保留规则,并 -printseeds 输出
  seeds 供发布前核对(实测保留 20169 条 MediaPipe 条目);
- 构建期新增 verifyReleaseAbi:release ABI 必须恰为 arm64-v8a/armeabi-v7a、
  每个 ABI 下必须有 MediaPipe native 库、模型资源必须存在;
- 未换库:诊断指向集成配置而非 MediaPipe 本身,保留 33 点契约零成本。

T-AND-010:版本号展示与单一来源
- 版本唯一来源 versionName(= project.yaml 的 version);versionCode 按
  10000*主+100*次+修订 递增(0.2.0 -> 200);
- 契约版本改为构建期解析 contracts/VERSION 后注入 BuildConfig(六项各自独立
  递增,原先的单个 CONTRACT_VERSION 常量已失真);界面不再手写任何版本号;
- 新增 checkReleaseConsistency 并挂到 preBuild,三者不一致直接 BUILD FAILED。

README/DSH/THIRD_PARTY/tasks 同步更新(含「导出连接日志」「保活参数」
「最近断开原因」等新增可观察项与排查表)。

测试:56 项全部通过(新增 ReconnectPolicyTest 11 项、PoseDiagnosticsTest 12 项);
build/verify.sh 全通过(单元测试 -> 构建 -> 签名/ABI/native/seeds/dex 校验)
2026-09-17 01:10:06 +08:00

145 lines
5.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================
# OpenDance Studio —— Android 辅助 App 一键验证
#
# 依次执行:单元测试 → debug 构建 → release 构建 → 产物校验
# 任一环节失败即退出非 0。
#
# 用法:
# JAVA_HOME=/path/to/jdk17 ANDROID_HOME=/path/to/sdk bash build/verify.sh
# ============================================================
set -uo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_DIR"
FAILED=0
echo "============================================================"
echo " 1/3 单元测试(JVM,无需设备)"
echo "============================================================"
if ./gradlew testDebugUnitTest --console=plain -q 2>&1 | tail -20; then
echo "✓ 单元测试通过"
else
echo "✗ 单元测试失败"
FAILED=1
fi
echo " 测试报告:app/build/reports/tests/testDebugUnitTest/index.html"
echo ""
echo "============================================================"
echo " 2/3 构建 debug + release APK"
echo "============================================================"
if bash build/build-apk.sh all 2>&1 | grep -vE "^> Task|^$" | tail -20; then
:
else
FAILED=1
fi
echo ""
echo "============================================================"
echo " 3/3 产物校验(签名 / 包信息 / 模型资源)"
echo "============================================================"
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}}"
# 优先选用与 compileSdk 匹配的 build-tools,缺失时退回最新版
if [ -d "$SDK/build-tools/34.0.0" ]; then
BT="$SDK/build-tools/34.0.0"
else
BT="$(ls -d "$SDK"/build-tools/* 2>/dev/null | sort -V | tail -1)"
fi
RELEASE_APK="app/build/outputs/apk/release/app-release.apk"
if [ ! -f "$RELEASE_APK" ]; then
echo "✗ 未生成 $RELEASE_APK"
FAILED=1
elif [ -z "$BT" ]; then
echo "✗ 未找到 build-tools,无法校验"
FAILED=1
else
# 以**退出码**为准:不同 build-tools 版本的 apksigner 输出文案不一致
# (34.0.0 会打印 "Verifies",36.0.0 配 --print-certs 时只打印证书信息),
# 依赖日志字符串判断会导致误判。
"$BT/apksigner" verify "$RELEASE_APK" > /tmp/apk_verify.txt 2>&1
VERIFY_STATUS=$?
"$BT/apksigner" verify --print-certs "$RELEASE_APK" > /tmp/apk_sign.txt 2>/dev/null
if [ "$VERIFY_STATUS" -eq 0 ]; then
echo "✓ 签名校验通过(apksigner 退出码 0)"
grep -E "Signer #1 certificate DN" /tmp/apk_sign.txt | sed 's/^/ /' || true
if grep -q "APK Signature Scheme v2" /tmp/apk_verify.txt 2>/dev/null; then
echo " v2 签名方案:已启用"
fi
else
echo "✗ 签名校验失败(apksigner 退出码 $VERIFY_STATUS)"
cat /tmp/apk_verify.txt
FAILED=1
fi
"$BT/aapt2" dump badging "$RELEASE_APK" 2>/dev/null | head -1 | sed 's/^/ /'
if unzip -l "$RELEASE_APK" | grep -q "assets/pose_landmarker_lite.task"; then
echo "✓ MediaPipe 模型已随包分发"
else
echo "✗ APK 中缺少 MediaPipe 模型"
FAILED=1
fi
# ---- T-AND-012:ABI 必须恰为真机 ABI,且每个 ABI 下都有 MediaPipe native 库 ----
ABIS=$(unzip -l "$RELEASE_APK" | grep -oE "lib/[^/]+/" | sed 's#lib/##;s#/##' | sort -u)
ABIS_ONE_LINE=$(echo "$ABIS" | tr '\n' ' ')
echo " 包含 ABI:$ABIS_ONE_LINE"
EXPECTED_ABIS=$(printf 'arm64-v8a\narmeabi-v7a')
if [ "$ABIS" = "$EXPECTED_ABIS" ]; then
echo "✓ release ABI 恰为 arm64-v8a + armeabi-v7a(真机 ABI)"
else
echo "✗ release ABI 与预期不符(预期 arm64-v8a / armeabi-v7a)"
FAILED=1
fi
if echo "$ABIS" | grep -qE "x86"; then
echo "✗ release APK 含模拟器 ABI,会导致包体翻倍且与 abiFilters 不符"
FAILED=1
fi
for abi in arm64-v8a armeabi-v7a; do
if unzip -l "$RELEASE_APK" | grep -q "lib/$abi/.*mediapipe"; then
echo "✓ lib/$abi 下有 MediaPipe native 库"
else
echo "✗ lib/$abi 下缺少 MediaPipe native 库(真机安装后必然初始化失败)"
FAILED=1
fi
done
# ---- T-AND-012:R8 不得裁掉 MediaPipe(seeds 由 -printseeds 输出) ----
SEEDS="app/build/outputs/mapping/release/seeds.txt"
if [ -f "$SEEDS" ]; then
MP_SEEDS=$(grep -c "^com.google.mediapipe" "$SEEDS" || true)
if [ "$MP_SEEDS" -gt 0 ]; then
echo "✓ R8 保留 MediaPipe 类 $MP_SEEDS 条(seeds.txt)"
else
echo "✗ R8 把 MediaPipe 整体裁掉了,release 包必然崩溃"
FAILED=1
fi
else
echo "✗ 未生成 $SEEDS(proguard-rules.pro 的 -printseeds 是否被移除?)"
FAILED=1
fi
# ---- T-AND-012:关键类必须真的在 dex 里 ----
if unzip -p "$RELEASE_APK" classes.dex 2>/dev/null | grep -qa "poselandmarker/PoseLandmarker"; then
echo "✓ dex 中存在 PoseLandmarker 类"
else
echo "✗ dex 中找不到 PoseLandmarker(R8/打包异常)"
FAILED=1
fi
fi
echo ""
if [ "$FAILED" -ne 0 ]; then
echo "============================================================"
echo " 验证失败"
echo "============================================================"
exit 1
fi
echo "============================================================"
echo " 验证全部通过"
echo "============================================================"