进程级唯一秤体连接修复设计

背景与结论

WeightSingle116 1.6.1 当前能从 /dev/ttyS4 收到合法重量帧,但同一进程出现多个 SerialPortRead 线程。根因不是秤体硬件永久初始化失败,而是应用在 WeightMainActivity.onStart() 重复调用 Weigher.init(),而旧实例可能仍持有串口读取线程。页面层又只在重量变化时转发数据,新页面注册后若重量不变,会表现为“没有重量反馈”。

采用进程级唯一连接方案:CptApplication 持有 WeigherController,厂商 SDK 只允许通过唯一驱动适配器访问;Activity、Fragment、设置调试页和 Demo 页均不得自行初始化或关闭秤体。

目标

非目标

组件边界

WeigherDriver

定义最小厂商能力接口,包括初始化、连续读取、单次探测、滤波、鉴别率、置零、去皮和释放。测试使用 Fake Driver,生产使用 AithingsWeigherDriver

AithingsWeigherDriver

唯一允许引用 com.aithings.Weigher 的生产适配器。适配器只转发 SDK 调用,不保存业务状态。

WeigherController

应用级唯一硬件拥有者,负责:

WeigherReading

不可变重量快照,包含序号、秤地址、状态、克重、稳定标记和单调时钟时间。页面只消费该对象,不依赖 SDK 类型。

WeigherRecoveryPolicy

纯 Java 恢复策略,判断初始化是否超时、连续读取是否静默、软探测是否超时以及自动重启是否超过频率限制。它不直接操作硬件,便于 JVM 单元测试。

状态机

start()INITIALIZINGACTIVEDEGRADEDRETRY_WAIT 状态下均为幂等操作。任何时刻只能存在一个初始化任务、一个恢复任务和一个看门狗任务。

线程模型

创建专用 HandlerThread("weigher-control")。原因是厂商 SensorScale 构造时会创建无参 Handler,初始化线程必须具有 Looper;普通线程池不能满足这个约束。

所有 Weigher 命令都投递到控制线程。Controller 将状态与重量事件切换到主线程后再通知 UI。订阅集合使用线程安全快照,回调时不持有状态锁。

初始化流程

  1. CptApplication 完成 MMKV 和资源初始化。
  2. Constants.APP_MODEL_TAG == 2 时调用 Controller 的幂等 start()
  3. 控制线程先同步调用 driver.setListener(),再调用 driver.init()
  4. 初始化成功后依次请求一次重量、开启连续读取、设置快速滤波、设置鉴别率。
  5. 收到首包合法重量后记录首包耗时并保存 latestReading
  6. 后续页面只订阅 Controller,不再控制串口生命周期。

页面数据流

WeightMainActivityonStart() 订阅 Controller,在 onStop() 取消订阅。取消订阅不关闭秤体。

Activity 每包执行以下动作:

Fragment 注册业务监听时,Activity 在基线初始化完成后回放最新重量。打餐中页面必须先设置 mSinglePickStartValuemSinglePickStartNumber,再注册并回放,避免把默认值当成起始重量。

当 Controller 尚无合法实时重量时,扫码流程不得进入打餐中页面,提示“秤体正在连接,请稍后重试”。

生命周期与入口治理

恢复策略

首次初始化失败

初始化失败时连续发送线程尚未启动,可先释放失败实例,等待具名的串口释放间隔,再自动重试一次。第二次失败进入 FAILED,显示明确故障并上传告警,不进行高频循环。

ACTIVE 后静默

正常秤体约每 200ms 返回重量。连续 5 秒无合法帧时进入 DEGRADED 并发送一次 requestWeight()。再等待 3 秒仍无数据,使用现有重启入口执行受控应用重启,让操作系统彻底回收 SDK 线程和串口 FD。

SDK 的连续发送线程存在中断后继续等待的风险,因此运行中不做无限“停止—重新初始化”。MMKV 记录自动重启时间,5 分钟内最多自动重启一次;超过限制进入 FAILED,避免硬件断开时循环重启。

SDK 错误码

可观测性

关键日志统一包含:statereasonattemptgenerationsequencelast_packet_gap_msweightsdk_error_code

只上传初始化、首包、状态变化、静默、恢复和硬件故障事件;200ms 重量帧不逐包上传,防止日志放大。

验收标准