一、测试抓到的真实缺陷(T-AND-017)
契约 §3.6 明确:「接收端收到任何一帧都算活动,ping **无需额外应答**即可保活,
pong 只用于诊断」—— 即"合规但不回 pong"的接收端是**合法**的。
但 PoseWebSocketClient.onOpen 里写的是 lastPongAtMs = timing.nowMs(),
于是心跳循环的守卫 if (lastPongAtMs > 0 && silentFor > limit) **恒为真**:
对任何接收端都会「判定链路已死 → 主动断链 → 重连」,默认参数
(heartbeatIntervalMs=5000)下即**每 15 秒断一次**,正是用户抱怨的「频繁重连」。
为什么以前没暴露:我们自己的接收端(Windows PoseServer)**会**回 pong
(T-WIN-020 实测 9 次 ping 全部收到 pong),这条路径在自家联调里永远走不到。
修复:onOpen 复位 lastPongAtMs = 0L(0 = 本连接还没收到过 pong),
只有「曾经回过 pong、之后又不回了」才判定链路已死 —— 这正是该诊断本来的意图。
二、可注入计时 Timing(时钟 + 等待)
契约 §7.1 的退避序列 0.5s→1→2→4→8→15s 封顶若要端到端验证,真实等待约 30 秒;
「连续 3 个心跳周期没收到 pong」也要等几十秒。抽成接口后:
* 生产默认 SystemTiming = 真实时钟 + 真实 delay,行为与抽取前完全一致
(10 处 System.currentTimeMillis() 与 2 处 delay() 改走 timing);
* 测试注入「可控时钟 + 立即返回的等待」,毫秒级跑完整条序列。
三、新增 4 项 JVM 用例(PoseWebSocketBackoffTest,无需设备)
* 退避序列在 15 秒封顶且不无限增长:连续失败下断言第 1..8 次等待分别落在
500/1000/2000/4000/8000/15000/15000/15000 的 ±20% 区间内,且真实耗时 < 5 秒;
* 等待走注入实现:前三次重连真实耗时 < 1.5 秒(真实退避需 3.5 秒);
* **合规但从不回 pong 的接收端不会被判定链路已死**(修复前此处失败):
走过 6 个心跳周期仍为 streaming、无「判定链路已死」日志;
* 曾经回过 pong 之后停止回复时判定链路已死并重连:诊断仍然有效。
实测:./gradlew :app:testDebugUnitTest → 7 个测试类 / 68 项用例 / 0 失败(原 64)。
build/verify.sh 全绿(签名、APK 元数据 0.2.2/202、native 体检、ABI、R8、dex)。
版本号 0.2.1/201 → 0.2.2/202。
写测试踩的两个坑(已写入任务文档):
1. 不要在瞬时重连下读 stats.nextRetryMs(等待立即返回后已被清零,与重连循环竞争),
等待时长应取注入实现记录的事实;
2. 虚拟时钟需要一个真实让出点(FakeTiming.await 里 2ms 真实 delay),
否则心跳循环会在网络回调落地前把时钟推过阈值。
109 lines
3.6 KiB
YAML
109 lines
3.6 KiB
YAML
project: opendance-android-app
|
||
version: 0.2.2
|
||
repository: opendance-android
|
||
default_branch: main
|
||
platform: Android 10+(minSdk 29 / targetSdk 34)
|
||
contracts_version:
|
||
rest-api: 0.5.0
|
||
websocket-keypoints: 0.2.0
|
||
data-models: 0.7.0
|
||
skeleton-topology: 0.2.0
|
||
model-formats: 0.5.0
|
||
database: 0.4.0
|
||
|
||
# 契约变更流程(文档 7.2):
|
||
# 任何跨端契约变更必须先在 opendance-web 修改 contracts/ 并提升版本,
|
||
# 再在本项目创建适配任务。本项目内的 contracts/ 只是只读快照。
|
||
|
||
dependencies:
|
||
- name: opendance-windows
|
||
kind: websocket-producer
|
||
contract: contracts/websocket-keypoints.md
|
||
usage: 本端为**发送方**,主动连接 Windows 客户端的 ws://<host>:8765/pose
|
||
- name: opendance-web
|
||
kind: contract-source
|
||
contract: contracts/skeleton-topology.md
|
||
usage: 33 点索引顺序与坐标语义(MediaPipe 原生顺序即契约顺序)
|
||
|
||
tasks:
|
||
- id: T-AND-001
|
||
file: tasks/001-scaffold.md
|
||
scope: Android Studio 项目骨架
|
||
status: done
|
||
- id: T-AND-002
|
||
file: tasks/002-camera-x.md
|
||
scope: CameraX 采集
|
||
status: done
|
||
- id: T-AND-003
|
||
file: tasks/003-mediapipe-pose.md
|
||
scope: MediaPipe Pose 端侧推理
|
||
status: done
|
||
- id: T-AND-004
|
||
file: tasks/004-keypoint-serializer.md
|
||
scope: 33 点 -> 239 字节帧
|
||
status: done
|
||
- id: T-AND-005
|
||
file: tasks/005-websocket-client.md
|
||
scope: WebSocket 推流 + 自动重连
|
||
status: done
|
||
- id: T-AND-006
|
||
file: tasks/006-connect-ui.md
|
||
scope: 连接页 + 主机发现
|
||
status: done
|
||
- id: T-AND-007
|
||
file: tasks/007-running-ui.md
|
||
scope: 运行页 + 状态指示
|
||
status: done
|
||
- id: T-AND-008
|
||
file: tasks/008-about-page.md
|
||
scope: 关于页 + 开源声明
|
||
status: done
|
||
- id: T-AND-009
|
||
file: tasks/009-build-apk.md
|
||
scope: Android Studio 打包
|
||
status: done
|
||
- id: T-AND-010
|
||
file: tasks/010-about-version.md
|
||
scope: 「关于」清单展示当前版本号 + 契约版本单一来源
|
||
status: done
|
||
- id: T-AND-011
|
||
file: tasks/011-reconnect.md
|
||
scope: 连接状态机 / 保活 / 去重与 Windows 端对齐
|
||
status: done
|
||
- id: T-AND-012
|
||
file: tasks/012-mediapipe-crash.md
|
||
scope: MediaPipe 初始化崩溃修复 + 诊断
|
||
status: done
|
||
- id: T-AND-013
|
||
file: tasks/013-apk-version.md
|
||
scope: APK 产物版本元数据(AndroidManifest 的 versionName/versionCode)校验 + 打包脚本 die 未定义修复
|
||
status: done
|
||
- id: T-AND-014
|
||
file: tasks/014-release-acceptance.md
|
||
scope: 发布包级端到端验收(读 zip 内 APK:版本元数据 / ABI+MediaPipe native / 模型资源 / R8 / 契约快照)
|
||
status: done
|
||
- id: T-AND-015
|
||
file: tasks/015-native-libs.md
|
||
scope: APK native 库体检(ELF 类型 / DT_NEEDED 闭包 / 16 KB 页对齐),纯标准库实现
|
||
status: done
|
||
- id: T-AND-016
|
||
file: tasks/016-reconnect-integration.md
|
||
scope: JVM 内以真实 WebSocket(MockWebServer)验证重连链路 + 修「上一次关闭码污染下一次会话」
|
||
status: done
|
||
- id: T-AND-017
|
||
file: tasks/017-backoff-and-pong.md
|
||
scope: 可注入计时 + 退避封顶端到端验证 + 修「合规不回 pong 的接收端被周期性误判链路已死」
|
||
status: done
|
||
|
||
deliverable:
|
||
artifact: opendance-android-v0.2.2.apk
|
||
packaging: Gradle 命令行 / Android Studio
|
||
includes:
|
||
- app-release.apk
|
||
- THIRD_PARTY.md
|
||
- README.txt
|
||
- contracts/ # 契约快照
|
||
release_rules:
|
||
- release 包只含 arm64-v8a / armeabi-v7a(真机 ABI)
|
||
- 版本号单一来源:versionName(= project.yaml 的 version)与 BuildConfig 契约版本
|