• v0.2.0 7ebbaef631

    zijunchiang released this 2026-09-17 01:11:55 +08:00 | 7 commits to main since this release

    ⚠️ 本版已被 v0.2.1 取代(建议直接用 v0.2.1):
    v0.2.1 修掉了重连链路的一处真实缺陷 —— 上一次会话的关闭码会污染下一次会话,
    导致重新开始推流后遇到网络失败时不再自动重连(只能反复手点)。
    另新增 JVM 内以真实 WebSocket 验证重连链路的集成测试(8 项,无需设备)。

    OpenDance Studio -- Android 辅助 App v0.2.0

    重连状态机修复(《DSH 开发文档 v1.6》6.5 / T-AND-011)、
    MediaPipe 初始化崩溃修复(6.6 / T-AND-012)、
    「关于」清单版本号(11 / T-AND-010)。

    契约 websocket-keypoints 提升到 0.2.0(向后兼容:子协议名与二进制帧
    version 均不变,只新增可选控制字段与一种新控制类型),三端快照逐字节一致。

    一、连接状态异常 / 频繁重连(T-AND-011)

    逐条核对后找到 3 个真实根因,都不是"退避参数没调好"这种表象问题:

    根因 1:保活用错了层(最要命)

    Windows 端有 5 秒静默超时,只看应用层收到的数据帧/文本帧;
    而本端原先只有 OkHttp 的协议层 ping —— 它由底层库自动应答,
    根本不会暴露给接收端的应用层,也就是说它不能续命。
    结果:画面里没有人时 5 秒必然被踢、然后重连,循环往复。

    • 契约新增 1.1:接收端必须在 welcome/config 中声明 silentTimeoutMs 与
      heartbeatIntervalMs,且 心跳必须小于静默超时的一半;
    • 契约新增 3.6:应用层 ping / pong;
    • Windows 端超时 5s → 15s、期望心跳 5s,并在 welcome/config 中下发;
    • 本端按声明值周期发 ping,连续 3 个周期没收到 pong 才判定链路已死并主动重连。

    根因 2:不该重连的时候也重连

    • 契约新增 7 关闭码策略:1000 / 1001 / 1002 与主动停止一律不重连,
      避免"重连风暴";
    • 退避与抖动按 7.1 冻结:0.5s → 1 → 2 → 4 → 8 → 15s 封顶 + ±20% 抖动。

    根因 3:frame_id 恒为 0

    PoseDetector.detect() 算出 safeTimestamp 后忘了写回 lastTimestampMs,
    于是 frameId 永远是 0 —— 接收端据此做的丢包检测完全失效。已修。

    其他对齐

    • 契约新增 8 连接状态机:idle → resolving → connecting → handshaking → streaming → degraded → reconnecting → stopped,两端同名,可直接对照日志;
    • 契约新增 5 设备去重:hello 携带稳定 deviceId(ANDROID_ID 的 SHA-256
      前 16 位,不含任何硬件序列号),Windows 端据此去重,"已连接 N 台"不再抖动;
    • 运行页显示状态机名、协商子协议、最近断开原因(close code + 中文说明)、
      保活参数、心跳 RTT,并提供 「导出连接日志」(最近 500 条:状态迁移 /
      close code / 子协议协商 / 首帧时间)。

    二、MediaPipe 初始化崩溃(T-AND-012)

    ExceptionInInitializerError: null 的 message 为 null,没有任何信息量 ——
    真因一定在 cause 链里(通常是 UnsatisfiedLinkError)。逐条核对 6.6 的五步,
    本项目实际命中 三处:

    项 处理
    拿到真因 新增 PoseDiagnostics:展开完整 cause 链取最内层真因,给出"缺少的具体库"
    ABI 核对 比较 Build.SUPPORTED_ABIS 与 APK 内 lib/<abi>/ 实际目录,直接指出"APK 只包含 arm64-v8a/armeabi-v7a,而模拟器(x86_64)需要 x86_64"这类必崩组合;构建期新增 verifyReleaseAbi 强制核对
    native 解压方式 packaging { jniLibs { useLegacyPackaging = true } }(AGP 8 的 release 默认 extractNativeLibs=false,MediaPipe 的 JNI 加载对它有历史兼容问题)
    R8 细化 补 -keepclasseswithmembernames class com.google.mediapipe.** { native <methods>; },并用 -printseeds 输出 seeds 供发布前核对(实测保留 20169 条 MediaPipe 条目)

    未换库:诊断指向的是集成配置(native 解压 + R8 + ABI),不是 MediaPipe
    本身不稳定,因此保留 MediaPipe(33 点契约零成本)。

    三、「关于」清单展示版本号(T-AND-010)

    • 版本唯一来源 versionName(= project.yaml 的 version = APK 文件名 = Git tag);
      versionCode 按 10000*主 + 100*次 + 修订 递增(0.2.0 → 200);
    • 契约版本改为构建期解析 contracts/VERSION 后注入 BuildConfig
      (六项契约各自独立递增,原先的单个 CONTRACT_VERSION 常量已失真),
      关于页逐项列出;Kotlin 界面代码里不出现任何版本号字面量;
    • 新增 checkReleaseConsistency 并挂到每个变体的 preBuild:
      versionName / project.yaml / contracts/VERSION / versionCode 任一不自洽
      直接 BUILD FAILED,不让版本漂移进入产物。

    校验

    • 单元测试 56 项全部通过(新增 ReconnectPolicyTest 11 项、
      PoseDiagnosticsTest 12 项;金标准 239 字节向量测试仍逐位一致);
    • bash build/verify.sh 三步全通过:单元测试 → debug/release 构建 →
      签名 / 包信息 / ABI / MediaPipe native 库 / R8 seeds / dex 关键类校验
      (release ABI 恰为 arm64-v8a + armeabi-v7a);
    • opendance-android-v0.2.0.zip sha256: 12db050f151a9ad6970646c5e1956918908c5662fd0dd38a8c3e12fc4d57423f

    真机 30 分钟连续推流与「连续 30 分钟推理无崩溃」两项验收需要真机实测
    (本环境无 Android 设备),根因修复与回归防护已由单测与构建期校验覆盖。

    安装

    sha256sum -c SHA256SUMS
    unzip opendance-android-v0.2.0.zip
    adb install -r opendance-android-v0.2.0/app-release.apk
    

    也可直接把 app-release.apk 拷到手机安装(Android 10+)。
    安装后请到「关于」页确认显示 v0.2.0 与六项契约版本。

    Downloads