Files
DishMatch/scale包架构分析.md
T
lvmeng 6595c4bde0 feat(dialog): FoodSearchDialog 新增空页面效果及 Window.Callback 手势冲突修复
- FoodSearchDialog:adapter 启用 isStateViewEnable,新增 loadEmptyView() 通过 adapter.stateView 展示空视图,替换原 toast 提示;覆写 show() 通过 Window.Callback.dispatchTouchEvent 动态控制 behavior.isDraggable,解决列表滑动与 BottomSheet 拖拽的手势冲突;注释旧 addOnScrollListener 方案保留备查
- SeasoningSelectDialog:同步补齐空页面效果,修复网络错误时也触发 loadEmptyView,优化 Handler 为 View.postDelayed
- FoodRecognizeActivity:修复识别结果回填后列表未显示的问题
- 新增 scale包架构分析.md 文档
2026-05-06 17:35:29 +08:00

7.1 KiB
Raw Blame History

scale 包架构分析

概览

scale 包负责多设备秤数据的采集、发现、传输与聚合,采用 mDNS + UDP 双路冗余发现 + WebSocket 长连接的架构。

根据设备角色(MASTER/SLAVE)运行不同的服务组合:

子设备 主设备
MdnsRegisterManager
ScaleWebSocketServer
UdpBroadcastSender
MdnsDiscoveryManager
UdpBroadcastReceiver
ScaleWebSocketClient
ScaleDataAggregator

整体数据流

子设备                                         主设备
  │                                              │
  ├─ UdpBroadcastSender ──UDP广播(8766)──▶ UdpBroadcastReceiver ─┐
  ├─ MdnsRegisterManager ──mDNS注册──▶ MdnsDiscoveryManager     ─┤
  │                                              │               │
  │                                       onDeviceFound          │
  │                                              │               │
  ├─ ScaleWebSocketServer ◀──WS连接(8765)── ScaleWebSocketClient ◀┘
  │        │ 秤数据推送                           │
  │        └──────────────────────────────▶ ScaleDataAggregator
  │                                              │
  │                                        StateFlow → UI
  │
  │ ◀── ScaleCommand(清零指令)─────────────────┤
  │ ◀── ScaleEvent(配置同步)──────────────────┤
  │ ──▶ ScaleEvent(调料添加通知)──────────────▶│

数据模型(3个)

ScaleData

单个秤的数据快照,是整个包内流转的核心数据结构。

字段 类型 说明
deviceId String 所属设备 ID
address Int 秤硬件地址编号
weight Double 重量(克)
state Int 1=稳定,0=不稳定,2=量程溢出
ts Long 数据时间戳(毫秒)
ip String 所属设备 IP,用于 UI 展示;网络传输数据中可能为空
name String? 秤槽位名称(可选),默认为 null

ScaleEvent

主子设备之间的非重量类通知,通过 WebSocket 传输。

事件类型 方向 说明
seasoning_added 子设备 → 主设备 某秤检测到调料添加,携带 delta(重量变化量)
seasoning_config 主设备 → 子设备 调料槽位配置同步,携带 List<SlotConfig>

ScaleCommand

主设备向子设备发送的控制指令,子设备收到后校验 deviceId 是否匹配自身再执行。

指令 说明
tare 清零指定地址的秤

设备发现(4个)

采用 mDNS + UDP 双路冗余,任意一路发现子设备均可触发连接。

MdnsRegisterManager(主设备和子设备均运行)

将本机 WebSocket 服务以 mDNS 形式注册到局域网。

  • 服务名格式:DishMatch-{deviceId}
  • 服务类型:_dishmatch._tcp.
  • 端口:8765

MdnsDiscoveryManager(仅主设备运行)

持续扫描局域网中所有 DishMatch-* 的 mDNS 服务,解析出 IP:PORT 后触发 onDeviceFound

关键细节Android NsdManager.resolveService 不支持并发调用,多台子设备同时被发现时会报 FAILURE_ALREADY_ACTIVE(3)。内部使用串行队列(resolveQueue)逐一解析,避免解析失败。

UdpBroadcastSender(仅子设备运行)

每 5 秒向 255.255.255.255:8766 广播一个 JSON 包,作为 mDNS 的兜底发现机制。

广播包结构:

{ "deviceId": "xxx", "ip": "192.168.1.x", "port": 8765 }

UdpBroadcastReceiver(仅主设备运行)

监听 8766 端口,接收子设备的 UDP 广播包,解析后触发 onDeviceFound

  • 内部用 knownDevicesConcurrentHashMap)缓存已发现的设备,避免每 5 秒重复触发连接
  • 设备断线时需调用 removeDevice() 清除缓存,才能在重连时重新触发 onDeviceFound

数据传输(2个)

ScaleWebSocketServer(主设备和子设备均运行)

基于 java-websocket 的服务端,监听 8765 端口。

职责:

  • 监听本机 WeightUtil 回调,将秤数据实时推送给所有已连接客户端(节流 100ms)
  • 新客户端连接时,立即推送所有秤的最新快照(latestData 缓存)
  • 接收主设备下发的 ScaleCommand(清零)和 ScaleEvent(配置同步)
  • 通过 connectionCount 计数器跟踪连接数,避免 connections 集合竞态问题

ScaleWebSocketClient(仅主设备运行)

管理主设备与多台子设备的 WebSocket 长连接。

职责:

  • 多设备并发连接(ConcurrentHashMap 管理)
  • 断线自动重连(指数退避:2s → 4s → 8s → ... → 30s
  • 向指定设备或全部设备发送指令/事件
  • 子设备首次连接成功时触发 onDeviceConnected,供主设备推送全量配置

数据聚合(1个)

ScaleDataAggregator(仅主设备运行)

将本机秤和所有子设备秤的数据统一汇总,以 StateFlow 暴露给 UI 层。

  • 本机秤:直接监听 WeightUtil 回调,无需经过网络
  • 子设备秤:由 ScaleWebSocketClient.onScaleData 回调写入
  • Map key 格式{deviceId}#{address},便于 UI 按设备分组展示
  • IP 回填:若数据包先于 IP 信息到达,setDeviceIp() 会回填已缓存数据中的空 IP 字段

配置(1个)

ScaleDeviceConfig

硬编码各子设备的固定 UUID 和秤地址显示顺序。

常量 说明
DEVICE_ID_2/22/18/1 各子设备固定 UUID
SCALE_ORDER_22 22个秤的物理位置排列顺序
SCALE_ORDER_18 18个秤的物理位置排列顺序
DEVICE_ORDER 设备在列表中的显示顺序

门面(1个)

ScaleServiceManager

整个包的统一入口(单例),根据设备角色决定启动哪些服务,并将各组件串联起来。

外部使用方式:

// Application.onCreate()
ScaleServiceManager.start(context)

// 主设备 UI 订阅全量秤数据
ScaleServiceManager.allScales?.collect { scales -> ... }

// 主设备发送清零指令
ScaleServiceManager.sendTare(deviceId, address)

// 主设备广播调料配置
ScaleServiceManager.sendSeasoningConfig(slots)

// 主设备监听子设备秤事件(如调料添加)
ScaleServiceManager.onScaleEvent = { event -> ... }

// 子设备监听主设备下发的调料配置同步
ScaleServiceManager.onSeasoningConfig = { event -> ... }

// 子设备向主设备广播秤事件
ScaleServiceManager.broadcastEvent(event)

// 子设备监听主设备连接状态变化
ScaleServiceManager.onMasterConnectionChanged = { connected -> ... }

// 子设备查询当前是否有主设备连接
ScaleServiceManager.isMasterConnected

// Application.onTerminate()
ScaleServiceManager.stop()

外部代码只需与 ScaleServiceManager 交互,无需感知内部任何组件。