# 产品决策(duooomi-android-sdk) ## owner / scanNamePrefix 设为必填(破坏性) **决策时间**:2026-05-08(commit `2dde274`) `DuooomiBleConfig` 删除 `firmwareIdentifier` / `firmwareStatus`,改 `owner` / `scanNamePrefix` 为必填。 **原因**: - iOS HEAD 已经这么做,1:1 复刻要求一致 - 多租户 `x-owner` header 是后端鉴权前提,给默认值反而隐患(默认填到错的租户) - `scanNamePrefix` 给默认值会扫到非预期设备 **影响**:集成方升级时构造函数签名报错,必须显式传两个参数。可接受,因为 demo / iOS / Harmony 都同步改了。 ## bind 改瘦 + fetchLatestFirmware 串三步(已废弃,见下条) **决策时间**:2026-05-08(commit `2dde274`) 旧设计:`bind(userId)` 一键搞定 BLE 硬绑 + 软绑 + 拉最新固件 + 对账上报。 新设计:`bind` 只做硬绑 + getVersion;软绑/对账/拉固件搬到 `fetchLatestFirmware` 内部串。 **原因**: - 与 expo `bindDeviceWithOrchestration` 行为对齐,便于跨平台一致排查 - bind 速度更快(不卡 HTTP 软绑),用户感知更自然 - fetchLatestFirmware 失败可重试,但 bind 失败重试要回滚 BLE 状态——分开后职责清晰 **集成方迁移指南**:bind 之后必须显式调 `fetchLatestFirmware(sn, currentVersion, userId)` 才能拿到固件信息和触发对账。 ## bind 内聚双层绑定 + updateMetadata 上报 **决策时间**:2026-05-18(同步 iOS commit `4bc8fdb` 复刻) 把软绑 + updateMetadata 从 `fetchLatestFirmware` 重新搬回 `bind` 内部,集成方一次调用完成"完整绑定"。 | 阶段 | 上一版(2026-05-08) | 本版(2026-05-18) | |---|---|---| | `bind(userId)` | BLE 硬绑 + getVersion | BLE 硬绑 + getVersion + **软绑** + **updateMetadata** + 失败回滚 | | `fetchLatestFirmware(sn, currentVersion, userId)` | 对账 + 软绑 + checkUpgrade | 对账 + checkUpgrade(不再软绑) | **原因**: - 软绑是"绑定"语义的一部分,集成方不该感知双层之分(与 iOS HEAD 对齐) - updateMetadata 在 bind 时 versionLog 刚新鲜,语义上"绑定时一并上报状态"更连贯 - 上一版"bind 改瘦"是过度拆分;事实证明拆开反而让集成方容易遗漏软绑 **集成方迁移指南**:bind 调用方式不变,但**不再需要**调 `fetchLatestFirmware` 才完成软绑。检查更新仍调 `fetchLatestFirmware`,但它只做对账+checkUpgrade。 **新增 SDK 状态**:`versionLog: StateFlow` —— 设备 BLE getVersion 响应中 `log` 字段(任意 JSON 对象,老固件不返回则 null)。bind 时 fire-and-forget 透传到 `POST /api/auth/loomart/shipment-sn/device/update-metadata`。**前向兼容**:老固件 versionLog == null → 自动跳过 updateMetadata。 **JSON object 选型**:直接用 `org.json.JSONObject?` 透传(不解析、不转 Map),HttpHelper.post 已支持 `JSONObject` 作为 body 子节点序列化。对应 iOS 的 `[String: Any]?` 方案。 ## disconnect 时不清 sn / firmwareUpgrade **决策时间**:2026-05-08(commit `0dbfab9`) `sn` 和 `firmwareUpgrade` 在被动断开 / 主动 disconnect 时**不清空**,与 iOS HEAD 严格对齐。 **原因**: - `sn` 是设备身份。断线重连同设备时仍有效,避免业务方每次都要重新 bind 拿 sn - `firmwareUpgrade` 是 UI 展示数据,断线时 UI 突然空白体验差 - 想清空 → unbind 即可(unbind 会清 sn) **潜在风险**:如果集成方在 disconnect 后**换了一台设备 connect**,但没 unbind 也没 bind 直接调 fetchLatestFirmware,会用错 sn。判定:靠集成方约束,SDK 不强行守卫(与 iOS 一致)。 ## UpgradeRecord 30 天 TTL + 对账 4 状态 **决策时间**:跟随 expo 设计(2026-05-08 复刻) 每条 UpgradeRecord 持久化于 SharedPreferences,状态机: | reported | outcome | 含义 | 触发 | |---|---|---|---| | false | null | 待上报 / HTTP 失败重试中 | 默认 / report 网络错误 | | true | SUCCESS | 升级正确 | 设备实际版本 == 目标版本,report-success 成功 | | true | VERSION_MISMATCH | 升级失败 | 设备实际版本 ≠ 目标版本,report-failure 成功 | | true | null | 30 天 TTL 过期未上报 | 触发 OTA 后 30 天内仍未上报,标记审计轨迹 | **原因**: - 30 天 TTL:防止持久化数据无限膨胀;30 天后用户感知低,可放弃 - 状态保留:对账成功后**不删 record**,便于 UI 展示历史 - `outcome=null` 与 "reported=false" 区分意图(一个是过期放弃,一个是网络在等重试) ## scanNamePrefix 由集成方约定(不再硬编码 "Duooomi-") **决策时间**:跟随 iOS(2026-05-08) 旧版扫描自动按 `Duooomi-` 前缀过滤。新版作为必填 Config 字段。 **原因**:同一 SDK 代码可服务多个 brand(duomi / xmly 等),广播名前缀不同,硬编码只服务一家。Demo 默认填 `Duooomi-`(可改)。 ## Demo 内运行时编辑 Config + Reload SDK **决策时间**:2026-05-08(commit `417012c`) Demo 顶部 Config 区可编辑 owner / apiKey / scanPrefix,SharedPreferences 持久化,点击 Reload SDK 重建实例。 **原因**:调试时切换租户 / 切换 apiKey 不用重装 app。iOS demo 已这么做。 **实现细节**:旧 SDK fire-and-forget disconnect 释放 BLE,再 `mutableStateOf` 赋新实例。Compose `collectAsState(sdk)` 自动订阅新 SDK 的 flow。 来源:复刻对话 2026-05-08。