多设备 OTA 模型
多设备 OTA 模型是杰理 Android OTA SDK(otasdk)中用于管理"一台手机同时对多台广播盒设备执行固件升级"的完整状态机与消息机制,覆盖设备选择、固件分发、断线回连与结束清理的整个生命周期。
Purpose and Scope
本文档介绍 com.jieli.broadcastbox 包内"广播盒多设备升级"能力(catalog:demo-app.multi-device),包括:
- 多设备 OTA 的核心状态模型
MultiOTAState及其状态常量; - 状态流转过程中使用的消息模型(
MultiOTAStart/MultiOTAWorking/MultiOTAReconnect/MultiOTAEnd/MultiOTAStop); - 设备列表的 UI 管理逻辑(
UpgradeFragment的选择与实时更新); - 多设备 OTA 处理器
MultiOTAProcessor的集成入口(BroadcastBoxActivity)。
以下主题属于兄弟页面,不在本文展开:单设备 OTA 升级流程、BLE 连接管理(BleManager)、升级文件选择(UpgradeFilePickerAdapter)、以及 OTA 文件监听(OtaFileObserver)。
Overview
在杰理的 OTA 应用场景中,一台 Android 设备(手机)可以作为上位机,同时连接多台"广播盒"(BroadcastBox)设备进行固件升级。与单设备 OTA 相比,多设备模型需要额外解决三个问题:
- 状态机统一:每台设备的升级进度不同步,需要以统一的
MultiOTAState状态常量描述整个批处理的宏观阶段(空闲 → 启动 → 工作中 → 回连 → 结束); - 消息解耦:升级过程中的每个阶段(开始、进行中、断线回连、结束、停止)都封装为独立的消息类,便于 UI 层通过事件机制感知状态变化;
- 选择列表管理:用户在多台已连接设备间勾选,
UpgradeFragment维护一份"已选择设备列表",并随连接状态实时增删,保证升级开始前列表与真实连接一致。
MultiOTAState 是这一模型的抽象基类,所有阶段消息都继承自它,通过 getState() 暴露当前所处阶段。整个模型由 MultiOTAProcessor 驱动,该处理器由 BroadcastBoxActivity 在广播盒升级场景中创建并调用。
Architecture
下图展示了多设备 OTA 模型的整体架构:UI 层负责设备勾选与列表维护,核心处理器驱动状态机,状态机通过消息模型与每台 BLE 设备交互。
flowchart TD
subgraph sg_UI["UI 层 (com.jieli.broadcastbox)"]
Activity["BroadcastBoxActivity"]
Fragment["UpgradeFragment"]
VM["UpgradeViewModel<br/>SelectedDeviceList"]
Adapter["UpgradeDeviceAdapter"]
end
subgraph sg_Core["多设备 OTA 核心"]
Processor["MultiOTAProcessor"]
StateModel["MultiOTAState<br/>(状态常量抽象基类)"]
Models["MultiOTA 阶段消息模型"]
end
subgraph sg_Device["设备层"]
BLE1["BLE 设备 1 (广播盒)"]
BLE2["BLE 设备 2 (广播盒)"]
BLE3["BLE 设备 N (广播盒)"]
end
Activity -->|"创建/调用"| Processor
Fragment -->|"勾选/取消"| VM
Fragment -->|"展示"| Adapter
VM -->|"待升级设备列表"| Processor
Processor -->|"继承/实例化"| StateModel
Processor -->|"阶段切换"| Models
Models -->|"固件传输"| BLE1
Models -->|"固件传输"| BLE2
Models -->|"固件传输"| BLE3
架构说明
BroadcastBoxActivity:广播盒升级场景的 Activity 入口,导入并持有MultiOTAProcessor(见 BroadcastBoxActivity.java),是多设备升级的驱动起点。UpgradeFragment:负责渲染设备列表、维护用户勾选结果,并在连接状态变化时(MSG_UPDATE_DEVICE_LIST消息)重建实时列表。MultiOTAState:抽象状态基类,定义了批处理升级的五个宏观阶段常量,是全部阶段消息的父类。model/ota消息模型:MultiOTAStart、MultiOTAWorking、MultiOTAReconnect、MultiOTAEnd、MultiOTAStop五个具体阶段消息,分别对应升级的启动、工作、回连、结束、停止动作。- BLE 设备层:多台广播盒设备,处理器逐个推送固件,断线时进入回连状态。
状态模型:MultiOTAState
MultiOTAState 是多设备 OTA 状态机的抽象基类,位于 com.jieli.broadcastbox.model.ota 包。它只做两件事:定义状态常量、持有并暴露当前状态值。这种设计把"状态定义"与"状态行为"分离——具体阶段消息(如 MultiOTAWorking)通过继承获得 state 字段,同时可以携带各自阶段的专属数据。
public abstract class MultiOTAState {
public static final int STATE_IDLE = 0;
public static final int STATE_START = 1;
public static final int STATE_WORKING = 2;
/**
* 回连状态
*/
public static final int STATE_OTA_RECONNECT = 3;
/**
* ota结束
*/
public static final int STATE_OTA_STOP = 4;
private final int state;
public MultiOTAState(int state) {
this.state = state;
}
public int getState() {
return state;
}
}
Source: MultiOTAState.java
状态常量语义
| 常量 | 值 | 含义 | 对应阶段消息 |
|---|---|---|---|
STATE_IDLE | 0 | 空闲,无升级任务 | — |
STATE_START | 1 | 升级启动,准备推送固件 | MultiOTAStart |
STATE_WORKING | 2 | 升级进行中,正在传输固件 | MultiOTAWorking |
STATE_OTA_RECONNECT | 3 | 设备断开,进入回连流程 | MultiOTAReconnect |
STATE_OTA_STOP | 4 | 升级结束(成功或失败) | MultiOTAEnd / MultiOTAStop |
设计意图:状态常量使用 int 而非枚举,是为了在 Handler/Message 跨线程传递时保持轻量(Message.what 天然是 int),并且允许扩展方在不修改基类的前提下新增自定义阶段值(只要不与现有值冲突)。
状态机流转
多设备 OTA 的批处理生命周期可以用状态机表达:升级从空闲进入启动,随后处于工作中;任一设备断线时进入回连状态,回连成功后回到工作状态继续传输;全部完成后进入结束/停止状态。
stateDiagram-v2
[*] --> STATE_IDLE: 初始化
STATE_IDLE --> STATE_START: 用户点击升级
STATE_START --> STATE_WORKING: 开始推送固件
STATE_WORKING --> STATE_OTA_RECONNECT: 设备断开
STATE_OTA_RECONNECT --> STATE_WORKING: 回连成功
STATE_OTA_RECONNECT --> STATE_OTA_STOP: 回连失败/超时
STATE_WORKING --> STATE_OTA_STOP: 全部设备升级完成
STATE_OTA_STOP --> [*]: 释放资源
该状态机由 MultiOTAProcessor 驱动。本次文档探索中,MultiOTAProcessor 的实现细节未能在仓库路径 com/jieli/broadcastbox/multidevice/MultiOTAProcessor.java 下直接读取到(该文件未被当前目录树收录或位于其他模块),但其存在与职责已由 BroadcastBoxActivity.java 的导入语句证实;上述状态流转是依据 MultiOTAState 常量定义与阶段消息命名归纳的模型语义。
阶段消息模型
model/ota 包内除基类 MultiOTAState 外,还有五个具体阶段类,共同构成消息模型族:
classDiagram
class MultiOTAState {
<<abstract>>
+STATE_IDLE: int = 0
+STATE_START: int = 1
+STATE_WORKING: int = 2
+STATE_OTA_RECONNECT: int = 3
+STATE_OTA_STOP: int = 4
-state: int
+getState() int
}
class MultiOTAStart {
}
class MultiOTAWorking {
}
class MultiOTAReconnect {
}
class MultiOTAEnd {
}
class MultiOTAStop {
}
MultiOTAState <|-- MultiOTAStart
MultiOTAState <|-- MultiOTAWorking
MultiOTAState <|-- MultiOTAReconnect
MultiOTAState <|-- MultiOTAEnd
MultiOTAState <|-- MultiOTAStop
| 消息类 | 源文件 | 阶段 | 典型触发时机 |
|---|---|---|---|
MultiOTAStart | model/ota/MultiOTAStart.java | 启动 | 用户确认升级,处理器开始遍历设备 |
MultiOTAWorking | model/ota/MultiOTAWorking.java | 工作中 | 固件分包传输、进度上报 |
MultiOTAReconnect | model/ota/MultiOTAReconnect.java | 回连 | 设备在升级中途断开 BLE 连接 |
MultiOTAEnd | model/ota/MultiOTAEnd.java | 结束 | 全部设备升级成功 |
MultiOTAStop | model/ota/MultiOTAStop.java | 停止 | 升级失败、用户取消或异常终止 |
这种"每阶段一个消息类"的设计使得 MultiOTAProcessor 可以通过统一的 MultiOTAState 引用向 UI 层派发阶段事件,UI 层只需判断 getState() 的取值即可决定界面表现,无需感知具体子类,实现了处理逻辑与表现逻辑的解耦。
设备列表管理(UI 层)
多设备 OTA 的用户入口是 UpgradeFragment。它通过 UpgradeViewModel 维护两份列表:已连接设备列表(getConnectedBleDevices())和已选择设备列表(getSelectedDeviceList())。已选择列表是升级批次的输入,处理器将以此为依据逐个推送固件。
勾选 / 取消勾选
用户在设备列表中勾选设备时,UpgradeFragment 会维护 SelectedDeviceList 的增删:
if (!state) {
if (!viewModel.getSelectedDeviceList().contains(boxInfo)) {
viewModel.getSelectedDeviceList().add(boxInfo);
}
} else {
viewModel.getSelectedDeviceList().remove(boxInfo);
}
Source: UpgradeFragment.java
设计意图:先做 contains 判重再 add,避免同一设备被重复加入升级批次;取消勾选时直接 remove,保持列表与用户意图严格一致。
连接列表实时同步
升级开始前必须保证"已选择列表 ⊆ 已连接列表"。MSG_UPDATE_DEVICE_LIST 消息触发 updateConnectedDeviceList(),将已连接设备与缓存选择做交集合并,重建实时列表:
private void updateConnectedDeviceList() {
List<BroadcastBoxInfo> connectedDevices = viewModel.getConnectedBleDevices(); //已连接设备列表
List<BroadcastBoxInfo> cacheSelected = viewModel.getSelectedDeviceList(); //缓存的选择设备列表
List<BroadcastBoxInfo> realTimeList = new ArrayList<>(); //实时更新列表
JL_Log.i(TAG, "updateConnectedDeviceList >> connectedDevices = " + connectedDevices.size() + ", cacheSelected = " + cacheSelected.size());
boolean isEmpty = connectedDevices.isEmpty();
// ... 合并逻辑:保留仍处于连接状态的选择设备 ...
JL_Log.i(TAG, "updateConnectedDeviceList >> realTimeList = " + realTimeList.size());
adapter.setList(connectedDevices);
viewModel.getSelectedDeviceList().clear();
viewModel.getSelectedDeviceList().addAll(realTimeList);
}
Source: UpgradeFragment.java
这段代码体现了多设备模型的关键容错策略:已断开连接的设备会被自动从选择列表中剔除(realTimeList 只保留仍连接的选择设备,随后清空并重建 SelectedDeviceList)。这样即使用户在勾选后有一台设备掉线,升级批次也不会把失效设备纳入,从源头避免升级失败。
升级按钮的可用性同样依赖选择列表:UpgradeFragment 会遍历 getSelectedDeviceList(),只要存在 getSelectFile() == null 的设备(未选择升级文件)就视为未就绪,阻止开始升级:
boolean ready = true;
for (BroadcastBoxInfo info : viewModel.getSelectedDeviceList()) {
if (info.getSelectFile() == null) {
ready = false;
break;
}
}
Source: UpgradeFragment.java
核心流程
一次典型的多设备 OTA 升级,从用户操作到设备固件传输,完整时序如下:
sequenceDiagram
participant U as 用户
participant F as UpgradeFragment
participant VM as UpgradeViewModel
participant P as MultiOTAProcessor
participant D1 as 广播盒设备 A
participant D2 as 广播盒设备 B
U->>F: 勾选设备 A、B
F->>VM: add/remove SelectedDeviceList
VM-->>F: 已选择设备列表
U->>F: 点击"开始升级"
F->>F: 校验每台设备已选升级文件
F->>P: 启动多设备 OTA (STATE_START)
P->>D1: 推送固件 (STATE_WORKING)
D1-->>P: 进度上报
P->>D2: 推送固件 (STATE_WORKING)
D2--xP: 连接断开
P->>P: 进入回连 (STATE_OTA_RECONNECT)
P->>D2: 尝试重连
D2-->>P: 回连成功,继续传输
P-->>F: 全部完成 (STATE_OTA_STOP)
F->>U: 展示升级结果
流程要点
- 入口:
BroadcastBoxActivity持有MultiOTAProcessor(BroadcastBoxActivity.java#L15),当用户从广播盒设备列表进入升级页时,UpgradeFragment负责收集选择结果。 - 预检:处理器启动前,UI 层确认每台选择设备都绑定了升级文件(
getSelectFile()非空),防止"空文件升级"。 - 逐台分发:处理器按
STATE_WORKING逐台推送固件;多台设备共享同一套状态常量,但各设备进度独立上报。 - 断线回连:任一台设备中断时状态切到
STATE_OTA_RECONNECT,由MultiOTAReconnect消息承载回连逻辑;回连成功则回到STATE_WORKING续传。 - 收尾:全部设备完成进入
STATE_OTA_STOP;MultiOTAEnd/MultiOTAStop分别对应成功与异常终止两种结局。
API Reference
MultiOTAState(int state)
多设备 OTA 阶段消息的抽象基类构造方法。
参数:
state(int):阶段状态值,取值必须是STATE_IDLE、STATE_START、STATE_WORKING、STATE_OTA_RECONNECT、STATE_OTA_STOP之一(或扩展的自定义值)。
说明: 子类构造时传入自身所属阶段,基类保存为 final 字段,阶段一旦创建不可变更。
Source: MultiOTAState.java
int getState()
返回当前阶段状态值。
返回: int,值为上述五个常量之一。UI 层通过该值决定界面表现(如显示"升级中""回连中""完成")。
Source: MultiOTAState.java
状态常量表
| 常量 | 值 | 说明 |
|---|---|---|
MultiOTAState.STATE_IDLE | 0 | 空闲 |
MultiOTAState.STATE_START | 1 | 启动 |
MultiOTAState.STATE_WORKING | 2 | 工作中 |
MultiOTAState.STATE_OTA_RECONNECT | 3 | 回连 |
MultiOTAState.STATE_OTA_STOP | 4 | 结束 |
失败模式、边界情况与并发
断线剔除(已连接列表变化)
updateConnectedDeviceList() 只在 MSG_UPDATE_DEVICE_LIST 消息到达时重建列表。若设备在两次消息之间掉线,SelectedDeviceList 中会短暂残留失效设备;但升级前的"文件就绪校验"与重建逻辑会在下一次消息到达时将其剔除,因此最终批次是收敛的。
空文件升级防护
升级触发前逐台检查 getSelectFile() == null,任何一台设备未选文件即阻止整批启动。这是"全有或全无"的批次语义:不会出现部分设备升级、部分设备因缺文件失败的情况。
重复勾选防护
add 前 contains 判重,杜绝同一 BroadcastBoxInfo 被重复加入批次;remove 时不存在则静默忽略,不会抛异常。
回连失败
STATE_OTA_RECONNECT 若回连失败或超时,状态机进入 STATE_OTA_STOP(异常终止路径,对应 MultiOTAStop)。回连窗口与重试次数由 MultiOTAProcessor 内部策略决定(本次探索未读取到处理器实现细节,此处依据状态机常量语义归纳)。
并发与线程模型
MSG_UPDATE_DEVICE_LIST 通过 Handler/Message(Message.what 与状态常量同为 int,风格一致)在 UI 线程投递,列表增删(add/remove/clear/addAll)均在主线程串行执行,不存在并发修改问题。BLE 传输的回调若来自工作线程,需要投递回主线程再触碰列表——这是 MultiOTAState 使用 int 常量的原因之一:与 Message 机制天然兼容。
扩展点
- 自定义阶段消息:继承
MultiOTAState并选用未被占用的int值,即可扩展新的批处理阶段(例如"等待用户确认"),无需修改基类。 - 消息族扩展:在
model/ota包内仿照MultiOTAStart/MultiOTAWorking新增消息类,处理器与 UI 层按getState()分发,新增阶段对既有逻辑透明。 - 列表策略替换:
UpgradeFragment的勾选与实时同步逻辑集中在updateConnectedDeviceList(),若需不同容错策略(如保留断线设备并标记),可在此方法内替换合并算法。
性能与运维
- 列表规模:多设备批次直接受 BLE 连接数限制(通常个位数到十几台),
SelectedDeviceList的线性contains判重开销可忽略。 - 日志可观测性:
updateConnectedDeviceList()通过JL_Log.i输出连接设备数与缓存选择数,运维时可据此判断"用户选择了 N 台、实际在线 M 台"的差异,快速定位批次被裁剪的原因。 - 资源释放:
STATE_OTA_STOP是终态,处理器应在该状态下释放各设备的传输会话;UI 层在批次结束后重建列表(clear()+addAll()),避免陈旧引用。
Related Links
- UpgradeFragment.java — 设备列表选择与实时同步的 UI 实现
- BroadcastBoxActivity.java — 多设备 OTA 处理器集成入口
- MultiOTAState.java — 状态模型基类
model/ota/包:MultiOTAStart.java、MultiOTAWorking.java、MultiOTAReconnect.java、MultiOTAEnd.java、MultiOTAStop.java — 阶段消息模型- 兄弟页面:单设备 OTA 升级流程、BLE 连接管理、升级文件选择与 OTA 文件监听