OTA 升级流程与状态模型
本文档介绍 Android-JL_OTA SDK 中 OTA 升级的完整生命周期:从单设备 UpgradeInfo 状态机到多设备 MultiOTAState 状态体系,以及 UI 层如何通过观察者模式驱动升级进度展示、回连与结束处理。
Purpose and Scope
本页聚焦于 OTA 升级流程与状态模型,涵盖:
- 单设备升级状态机(
UpgradeInfo的STATE_STOP/STATE_WORKING/STATE_RECONNECT) - 多设备 OTA 状态体系(抽象基类
MultiOTAState及其五个事件子类) - 升级类型(bootloader 与 fw 固件)与进度、错误信息的承载方式
- UI 层(
DialogUpgradeDevice、UpgradeProgressAdapter)如何订阅状态并驱动界面
以下内容属于兄弟页面,不在本页展开:BLE 连接管理与数据发送(tool/ota/ble/BleManager、SendBleDataThread)、SPP 串口连接(tool/ota/spp/ConnectionSppThread)、固件文件解析与校验、升级文件选择(DialogUpgradeFilePicker)。如需了解这些底层传输细节,请参阅对应页面。
Overview
OTA(Over-The-Air)升级是嵌入式设备(如蓝牙音箱、耳机)固件更新的核心能力。SDK 将升级过程抽象为可观察的状态机:
- 单设备视角:
UpgradeInfo是单个设备升级的"状态快照",记录设备地址、固件文件名、进度(0~100)、升级类型、当前状态、错误码与错误消息。UI 的进度列表直接渲染该对象。 - 多设备视角:
MultiOTAState是抽象基类,配合LiveData(multiOtaStateMLD)把"开始 / 传输中 / 回连 / 结束 / 停止"等事件推送给 UI。广播盒(BroadcastBox)场景下多个设备并行升级时,每个事件都携带设备维度信息。
状态模型的设计意图在于:把传输层(BLE/SPP)与 UI 解耦——传输层只负责上报状态事件,UI 只依赖状态常量与数据字段渲染,二者之间通过 ViewModel + LiveData 桥接,任何一侧的变更都不会牵动另一侧。
Architecture
flowchart TD
subgraph sg_UI["UI 层 (com.jieli.broadcastbox)"]
Activity["BroadcastBoxActivity"]
Fragment["UpgradeFragment"]
Dialog["DialogUpgradeDevice"]
Adapter["UpgradeProgressAdapter"]
end
subgraph sg_Model["模型层 (model)"]
UpgradeInfo["UpgradeInfo"]
MultiState["MultiOTAState (abstract)"]
Start["MultiOTAStart"]
Working["MultiOTAWorking"]
Reconnect["MultiOTAReconnect"]
End["MultiOTAEnd"]
Stop["MultiOTAStop"]
end
subgraph sg_ViewModel["ViewModel"]
MLD["multiOtaStateMLD (LiveData)"]
end
subgraph sg_SDK["SDK 传输层 (tool/ota)"]
Ble["BleManager / SendBleDataThread"]
Spp["ConnectionSppThread"]
end
Dialog --> MLD
MLD --> Dialog
Dialog --> UpgradeInfo
Adapter --> UpgradeInfo
Activity --> Fragment
Fragment --> Dialog
Dialog --> Ble
Ble --> Spp
MultiState <|-- Start
MultiState <|-- Working
MultiState <|-- Reconnect
MultiState <|-- End
MultiState <|-- Stop
MLD -. "携带事件对象" .-> MultiState
各角色说明:
UpgradeInfo:单设备升级状态载体,以deviceAddress作为唯一标识(equals/hashCode均基于该字段),由DialogUpgradeDevice在收到STATE_START时创建并写入升级列表。MultiOTAState及五个子类:多设备升级的事件模型。DialogUpgradeDevice在multiOtaStateMLD.observe(...)中按getState()分发并强转为对应子类,从而取出各自字段。UpgradeProgressAdapter:升级进度列表的适配器,根据UpgradeInfo.getState()渲染不同文案(如传输中、重连中)。- ViewModel 的
multiOtaStateMLD:状态事件的统一出口,将 SDK 传输层的回调转化为 UI 可观察的 LiveData 流。
状态模型详解
单设备状态:UpgradeInfo
UpgradeInfo.java 定义了单设备升级的三态常量与全部数据字段:
public static final int STATE_STOP = 0;
public static final int STATE_WORKING = 1;
public static final int STATE_RECONNECT = 2;
private final String deviceAddress; //upgrade device mac
private String deviceName;// upgrade device name
private String filename;// upgrade file
private int progress = 0;// ota progress
private int upgradeType = 0;// ota type: 0->bootloader, 1->fw upgrade
private int state = STATE_STOP;// OTA state
private int error;// OTA error
private String message; //error message
Source: UpgradeInfo.java
| 状态常量 | 值 | 含义 |
|---|---|---|
STATE_STOP | 0 | 未开始/已停止,默认初始状态 |
STATE_WORKING | 1 | 升级传输进行中 |
STATE_RECONNECT | 2 | 设备断连,正在回连(如双模设备切换 BLE/SPP 通道) |
字段设计要点:
deviceAddress为 final 且唯一:构造时即固定,equals/hashCode只比较deviceAddress。这意味着升级列表按 MAC 去重,同一设备重复上报不会产生重复条目。upgradeType:0 表示 bootloader 升级,1 表示固件(fw)升级,决定底层指令协议分支。error+message:错误码与可读错误消息分离存储,UI 可同时展示数字码与文案。- Fluent setter 风格:所有 setter 返回
this,便于链式构造(见下文 Usage Examples)。
多设备状态:MultiOTAState 事件体系
MultiOTAState.java 是广播盒多设备升级的抽象状态基类:
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
与单设备 UpgradeInfo 的"三态快照"不同,多设备体系采用事件对象模式:基类只持有不可变的 state 常量,每个子类携带该状态特有的载荷。DialogUpgradeDevice 的观察回调中按状态强转取数,例如 STATE_IDLE 强转为 MultiOTAEnd、STATE_START 强转为 MultiOTAStart、STATE_WORKING 强转为 MultiOTAWorking:
switch (multiOTAState.getState()) {
case MultiOTAState.STATE_IDLE: {
MultiOTAEnd otaEnd = (MultiOTAEnd) multiOTAState;
...
}
case MultiOTAState.STATE_START: {
MultiOTAStart otaStart = (MultiOTAStart) multiOTAState;
...
}
case MultiOTAState.STATE_WORKING: {
MultiOTAWorking otaWorking = (MultiOTAWorking) multiOTAState;
...
}
}
Source: DialogUpgradeDevice.java
| 子类 | 对应状态常量 | 事件含义 |
|---|---|---|
MultiOTAStart | STATE_START (1) | 设备开始升级;UI 据此创建 UpgradeInfo 条目并入列表 |
MultiOTAWorking | STATE_WORKING (2) | 固件数据正在传输,携带进度信息 |
MultiOTAReconnect | STATE_OTA_RECONNECT (3) | 传输中断,设备回连中 |
MultiOTAEnd | STATE_IDLE (0) | 单台设备升级结束,回到空闲态 |
MultiOTAStop | STATE_OTA_STOP (4) | 升级被手动停止或异常终止 |
说明:
MultiOTAEnd对应STATE_IDLE(升级结束后设备回到空闲),与UpgradeInfo.STATE_STOP的语义一致;子类内部字段在源码中未进一步展开,具体载荷以 SDK 回调为准。
状态转移总览
stateDiagram-v2
[*] --> IDLE: 初始化
IDLE --> START: 下发升级指令
START --> WORKING: 固件传输开始
WORKING --> RECONNECT: 链路断开/切换
RECONNECT --> WORKING: 回连成功,续传
RECONNECT --> STOP: 回连失败/超时
WORKING --> END: 传输完成 100%
END --> IDLE: 设备复位重启
STOP --> [*]
状态转移规则(依据源码可验证的部分):
- 初始为
IDLE;用户选择固件文件并发起升级后进入START。 START阶段 UI 创建UpgradeInfo并置为STATE_WORKING(DialogUpgradeDevice第 188 行setState(UpgradeInfo.STATE_WORKING))。- 传输中若底层链路断开(如 BLE 与 SPP 双模切换),进入
STATE_RECONNECT;UpgradeProgressAdapter对STATE_RECONNECT渲染"重连中"文案。 - 回连成功回到
WORKING续传;回连失败或升级完成则进入结束/停止分支。 - 升级结束(
STATE_OTA_STOP/STATE_IDLE)后,MultiOTAEnd事件通知 UI 清理该设备的升级条目。
核心流程:从发起升级到界面展示
下图描述了广播盒场景下一次 OTA 升级从用户操作到 UI 刷新的完整时序(基于 DialogUpgradeDevice 的观察回调与 UpgradeInfo 列表渲染逻辑):
sequenceDiagram
participant U as 用户
participant D as DialogUpgradeDevice
participant VM as ViewModel
participant SDK as SDK (BleManager/Spp)
participant DEV as 目标设备
participant L as 升级列表+UpgradeProgressAdapter
U->>D: 选择固件文件,点击升级
D->>SDK: 下发升级指令 (OTA_START)
SDK->>DEV: 发送启动帧
DEV-->>SDK: 应答
SDK-->>VM: 上报 MultiOTAStart (STATE_START)
VM-->>D: multiOtaStateMLD 回调
D->>D: 强转 MultiOTAStart,创建 UpgradeInfo(STATE_WORKING)
D->>L: add(upgradeInfo)
L-->>U: 列表出现新设备条目
loop 固件分包传输
SDK->>DEV: 数据帧 (progress++)
DEV-->>SDK: 确认
SDK-->>VM: 上报 MultiOTAWorking (STATE_WORKING)
VM-->>D: 回调
D->>L: 更新 progress
end
alt 链路断开
SDK-->>VM: 上报 MultiOTAReconnect (STATE_OTA_RECONNECT)
VM-->>D: 回调
D->>L: 置 UpgradeInfo.STATE_RECONNECT
L-->>U: 显示"重连中"
SDK->>DEV: 回连
DEV-->>SDK: 回连成功
end
SDK-->>VM: 上报 MultiOTAEnd (STATE_IDLE / STATE_OTA_STOP)
VM-->>D: 回调
D->>L: 移除/结束条目
L-->>U: 升级完成提示
关键控制流说明:
- 状态上报统一走 LiveData:SDK 传输层不直接触碰 UI,而是通过 ViewModel 的
multiOtaStateMLD推送MultiOTAState事件;DialogUpgradeDevice在addObserver()中observe该 LiveData 并switch分发(源码见 DialogUpgradeDevice.java)。 - 列表条目在 START 时创建、END 时清理:收到
STATE_START事件才创建UpgradeInfo,避免空跑条目;升级结束事件负责收尾。 - 进度与状态分离渲染:
UpgradeProgressAdapter按info.getState()分支渲染——STATE_WORKING显示进度百分比,STATE_RECONNECT显示重连文案(源码见 UpgradeProgressAdapter.java)。
Usage Examples
示例 1:链式构造 UpgradeInfo(单设备状态快照)
UpgradeInfo 的 setter 全部返回 this,UI 层在收到开始事件后可用链式写法快速组装条目,并将状态置为 WORKING:
UpgradeInfo upgradeInfo = new UpgradeInfo(device.getAddress())
.setDeviceName(device.getName())
.setFilename(info.getSelectFile().getName())
.setState(UpgradeInfo.STATE_WORKING);
list.add(upgradeInfo);
Source: DialogUpgradeDevice.java
设计意图:deviceAddress 在构造时固定,equals/hashCode 仅依据它——因此即使同一设备被重复上报 START 事件,列表去重(List.contains / remove)也按 MAC 精确命中,不会出现重复行。
示例 2:按状态渲染升级列表
UpgradeProgressAdapter 根据 UpgradeInfo.getState() 决定文案,是状态模型的直接消费者:
switch (info.getState()) {
case UpgradeInfo.STATE_WORKING: {
String title;
// 传输中:显示进度百分比
...
}
case UpgradeInfo.STATE_RECONNECT: {
tvState.setText(String.format(Locale.getDefault(), "(%s)",
getContext().getString(R.string.reconnecting)));
...
}
}
Source: UpgradeProgressAdapter.java
示例 3:定义多设备状态事件子类
业务侧扩展新的事件类型时,继承 MultiOTAState 并在构造器传入对应状态常量即可,基类保证 state 不可变:
public class MultiOTAWorking extends MultiOTAState {
public MultiOTAWorking() {
super(STATE_WORKING);
}
// 携带进度、设备地址等载荷字段
}
说明:此示例依据基类 MultiOTAState.java 的构造函数约定(
MultiOTAState(int state))推演而成;各子类的具体载荷字段请以 SDK 实际实现为准。
API Reference
UpgradeInfo(com.jieli.broadcastbox.model.UpgradeInfo)
单设备升级状态快照,升级列表的最小数据单元。源码:UpgradeInfo.java
| 方法签名 | 说明 |
|---|---|
UpgradeInfo(String deviceAddress) | 构造器,deviceAddress(设备 MAC)为唯一必填项,创建后不可变 |
String getDeviceAddress() | 返回设备 MAC |
UpgradeInfo setDeviceName(String) | 链式设置设备名 |
UpgradeInfo setFilename(String) | 链式设置固件文件名 |
UpgradeInfo setProgress(int) | 链式设置进度(0~100) |
UpgradeInfo setUpgradeType(int) | 链式设置升级类型(0=bootloader,1=fw) |
UpgradeInfo setState(int) | 链式设置状态(STATE_STOP/STATE_WORKING/STATE_RECONNECT) |
UpgradeInfo setError(int) | 链式设置错误码 |
UpgradeInfo setMessage(String) | 链式设置错误消息 |
int getProgress() / int getUpgradeType() / int getState() / int getError() / String getMessage() | 对应字段读取器 |
boolean equals(Object) / int hashCode() | 仅基于 deviceAddress,用于列表去重与查找 |
Throws / 约束:
deviceAddress不允许为空——它是equals/hashCode的唯一依据,空值会导致哈希运算抛NullPointerException。progress的语义为 OTA 百分比(0~100),由上层 SDK 回调写入。
MultiOTAState(com.jieli.broadcastbox.model.ota.MultiOTAState)
多设备 OTA 事件的抽象基类。源码:MultiOTAState.java
| 成员 | 说明 |
|---|---|
static final int STATE_IDLE = 0 | 空闲(升级结束回落到该状态) |
static final int STATE_START = 1 | 开始事件 |
static final int STATE_WORKING = 2 | 传输中事件 |
static final int STATE_OTA_RECONNECT = 3 | 回连事件 |
static final int STATE_OTA_STOP = 4 | 停止/结束事件 |
MultiOTAState(int state) | 受保护语义的构造器(抽象类),子类必须传入状态常量 |
int getState() | 返回不可变状态值 |
设计约束:
- 基类为
abstract,禁止直接实例化;state字段为final,事件对象创建后状态不可变,保证 LiveData 分发过程中不会被篡改。 - 状态常量同时承担"类型标签"职责:消费者按
getState()强转对应子类取载荷,因此子类与状态常量必须一一对应,新增状态需同时扩展常量与子类。
Configuration Options
状态模型本身不读取配置文件,其"可配置项"体现在数据字段的取值约定上:
| 字段/常量 | 可选值 | 默认值 | 说明 |
|---|---|---|---|
UpgradeInfo.upgradeType | 0 = bootloader 升级;1 = fw 固件升级 | 0 | 决定底层升级指令协议分支 |
UpgradeInfo.state | STATE_STOP=0、STATE_WORKING=1、STATE_RECONNECT=2 | STATE_STOP | 单设备升级状态 |
UpgradeInfo.progress | 0~100 | 0 | OTA 传输进度百分比 |
UpgradeInfo.error | 由 SDK 错误码表定义 | 0 | 升级失败时的错误码 |
MultiOTAState.state | STATE_IDLE=0~STATE_OTA_STOP=4 | 无(构造器必填) | 多设备事件类型标签 |
注:
upgradeType的 0/1 含义直接来自源码注释// ota type: 0->bootloader, 1->fw upgrade(UpgradeInfo.java)。错误码的具体数值表在 SDK 传输层定义,本页不展开。
失败模式、边界情况与并发
失败模式
| 失败场景 | 状态模型中的体现 | 处理路径 |
|---|---|---|
| 固件传输中断(BLE 掉线/切换 SPP) | UpgradeInfo.STATE_RECONNECT / MultiOTAReconnect | UI 显示"重连中";SDK 尝试回连后续传 |
| 回连失败/升级超时 | 结束类事件(STATE_OTA_STOP) | DialogUpgradeDevice 收到结束事件后清理条目 |
| 升级失败(校验错误、设备拒绝) | UpgradeInfo.error + message | 错误码与可读文案分离存储,UI 可同时展示 |
| 重复上报开始事件 | equals/hashCode 基于 deviceAddress | 列表按 MAC 去重,避免重复条目 |
边界情况
- 设备地址唯一性:
UpgradeInfo的相等性完全由deviceAddress决定,同一设备的不同升级会话(如先升 bootloader 再升 fw)会命中同一条目,UI 需自行区分upgradeType或覆盖旧条目。 - STATE_IDLE 双关语义:
MultiOTAState.STATE_IDLE同时是初始状态和单台设备升级结束(MultiOTAEnd)的标签,消费者必须在"升级已开始"的上下文里收到它才表示结束,不能仅凭常量值判断。 - 进度非单调保证:回连续传时
progress可能从低于 100 的断点继续,UI 渲染不应假设进度严格递增。
并发与一致性
- UI 线程约束:
multiOtaStateMLD是 LiveData,回调默认在主线程派发;DialogUpgradeDevice在观察回调中直接操作升级列表与UpgradeInfo,无需额外加锁。 - 事件对象不可变:
MultiOTAState.state为final,多线程共享事件对象时状态值不会漂移;可变载荷(如UpgradeInfo.progress)由持有方(UI 列表)单线程更新。 - 单线程列表更新:升级列表的增删(START 添加 / END 移除)都发生在 LiveData 回调(主线程)中,天然串行,不存在竞态窗口。
性能与运维
- 状态事件频率:
WORKING事件随固件分包传输高频产生(每包或每几个包一次)。UI 层若直接在每个事件中刷新整个列表可能造成丢帧,建议在UpgradeProgressAdapter中仅更新变更行的视图(复用ViewHolder+ 按deviceAddress定位),这也是状态字段按设备隔离的原因。 - 日志观测点:
BroadcastBoxActivity中通过JL_Log输出设备连接状态变化(如>>>> deviceConnectionMLD >> ...),排查升级中断时可结合该日志与error/message字段定位链路层还是应用层问题。 - 回连窗口:
STATE_RECONNECT期间 UI 处于等待态,回连是否成功由 SDK 传输层决定;建议运维侧为回连设置超时告警,避免界面长期停留在"重连中"。
扩展点
- 新增升级事件类型:继承
MultiOTAState,在基类中补充状态常量,并同步扩展DialogUpgradeDevice的switch分发与UpgradeProgressAdapter的渲染分支。 - 扩展升级类型:
UpgradeInfo.upgradeType目前只有 bootloader(0)/fw(1),新增类型需在 SDK 指令层与 UI 文案层协同扩展。 - 替换传输层:状态模型与 BLE/SPP 传输解耦(通过 ViewModel + LiveData 桥接),接入新的传输通道(如 TCP)时只需在 SDK 层把新通道的回调映射为
MultiOTAState事件,UI 层零改动。
测试
SDK 的测试目录(otasdk/src/test)中的 OtaDemo.java 演示了完整调用链:以 BluetoothProfile.STATE_* 驱动连接状态切换,并将结果映射为 StateCode.CONNECTION_OK / CONNECTION_CONNECTING 等状态码。该示例验证了"连接状态 → 状态码 → UI"的映射模式,与升级状态模型遵循同一设计思想(OtaDemo.java)。针对 UpgradeInfo 的单元测试建议覆盖:equals/hashCode 的 MAC 唯一性、链式 setter 返回值、以及 STATE_WORKING/STATE_RECONNECT 分支渲染。
Related Links
- UpgradeInfo.java(单设备状态模型)
- MultiOTAState.java(多设备事件基类)
- DialogUpgradeDevice.java(状态事件消费端)
- UpgradeProgressAdapter.java(状态渲染适配器)
- OtaDemo.java(调用链演示/测试示例)
相关页面导航:
- 底层传输与连接管理:参见 BLE 管理器(
tool/ota/ble/BleManager)与 SPP 连接(tool/ota/spp/ConnectionSppThread)相关文档。 - 升级文件选择与校验:参见文件选择对话框(
DialogUpgradeFilePicker)与固件解析相关文档。 - 升级 UI 容器:参见
BroadcastBoxActivity/UpgradeFragment页面。