固件空中升级流程
本文档介绍 JL Health 手表应用(Android-JL_Health)中固件空中升级(OTA)的完整实现:从固件文件准备、蓝牙连接与重连、OTAManager 升级驱动,到网络模块(4G)OTA 的扩展流程,以及贯穿全程的状态机与回调机制。
Purpose and Scope
本页覆盖"固件空中升级"这一能力的端到端实现,包括:
- 升级前置条件与固件文件约定(
.ufw/.zip包) OTAManager对蓝牙 OTA SDK(jl_bt_ota)的封装与配置- 升级状态机
OtaState与回调链IUpgradeCallback - 升级过程中的蓝牙回连/地址映射(BLE/EDR)机制
- 网络模块(4G)OTA 的查询、选包与升级流程
不在此页范围内的相邻主题:
- 蓝牙连接与设备管理的底层封装(
BluetoothHelper)属于蓝牙连接能力,请参见相关蓝牙页面 - 升级包的云端下发/HTTP 下载机制(
OtaFileMsg、jl_health_http)属于服务器交互能力,本页仅引用其结果 - 升级 UI 页面(进度条、动画等)属于 UI 层,不在本页深入展开
Overview
固件空中升级(Firmware Over-The-Air, OTA)是智能手表 App 的核心能力之一。本仓库(Jieli-Tech/Android-JL_Health)基于杰理科技的 jl_bt_ota 外接库(libs/JL/jl_bt_ota_V1.11.0_11015-release.aar)实现:
- 普通固件升级:通过
BluetoothOTAManager的子类OTAManager驱动,将.ufw固件文件经蓝牙(BLE/SPP)发送到手表端完成烧录。 - 资源/双备份判断:
isSingleOTA()依据设备信息TargetInfoResponse.isSupportDoubleBackup()判断设备是否支持双备份,从而决定升级策略。 - 网络模块(4G)升级:通过
NetworkOpImpl执行,支持从 zip 包中按sdkMap.json匹配与设备版本一致的固件文件后再升级。
升级流程整体分为四个阶段:准备(PREPARE)→ 下载(DOWNLOAD)→ 升级(UPGRADE)→ 工作(WORKING)/停止(STOP),中间可能穿插 重连(RECONNECT) 步骤。OtaState 用一组整数常量完整刻画了这些阶段。
Architecture
flowchart TD
subgraph sg_App["应用层 (HealthAide)"]
UI["升级 UI / ViewModel"] --> OtaState["OtaState 状态模型"]
OtaState --> OTAManager["OTAManager<br/>(extends BluetoothOTAManager)"]
OTAManager --> CB["CustomUpgradeCallback<br/>(IUpgradeCallback 包装)"]
end
subgraph sg_SDK["杰理 OTA SDK (jl_bt_ota aar)"]
BOM["BluetoothOTAManager"] --> Cfg["BluetoothOTAConfigure"]
BOM --> RCSP["RCSP 指令协议"]
end
subgraph sg_BT["蓝牙层 (BluetoothHelper)"]
BE["BluetoothEventListener"] -->|"onConnection/onReceiveData/onBleMtuChange"| OTAManager
OTAManager -->|"getConnectedDevice/getConnectedBluetoothGatt"| BH["BluetoothHelper"]
end
subgraph sg_Net["网络模块升级 (4G)"]
NVM["NetworkOtaViewModel"] --> NOP["NetworkOpImpl"]
NOP --> NOL["OnNetworkListener / OnNetworkOTACallback"]
end
OTAManager --> BOM
BOM --> BE
OTAManager -->|"watchManager.getCmdSnGenerator()"| SN["SN 生成器(共享)"]
NVM --> WatchMgr["WatchManager"]
SN --> WatchMgr
架构要点说明:
OTAManager继承BluetoothOTAManager,把 SDK 需要的设备连接、数据收发、MTU 变化等能力桥接到应用自有的BluetoothHelper单例上,这样 SDK 无需感知应用层的连接管理方式(源码见 OTAManager.java)。BluetoothEventListener把蓝牙层的三类事件(连接状态、数据接收、MTU 变化)转发给 OTA SDK,onConnection中插入 300ms 延时,等待设备其他模块初始化完成后再上报连接状态。- SN(命令序号)生成器从
WatchManager取出并注入 OTA 配置,保证两个库(RCSP 与 OTA)使用同一个命令序号生成器,避免协议层序号冲突(源码见 OTAManager.java)。 - 网络模块 OTA 走独立通道
NetworkOpImpl,与普通固件升级解耦,但同样依赖WatchManager提供的设备连接与配置信息。
升级状态机与核心流程
状态定义
OtaState 用连续的整数常量定义升级的完整生命周期(源码见 OtaState.java):
| 常量 | 值 | 含义 |
|---|---|---|
OTA_STATE_IDLE | 0 | 空闲,无升级任务 |
OTA_STATE_PREPARE | 1 | 升级准备(检查固件、配置等) |
OTA_STATE_DOWNLOAD | 2 | 下载固件包(网络获取) |
OTA_STATE_UPGRADE | 3 | 进入升级流程 |
OTA_STATE_START | 4 | OTA 已启动(SDK 回调 onStartOTA) |
OTA_STATE_WORKING | 5 | 升级进行中(进度回调) |
OTA_STATE_STOP | 6 | 升级结束(成功/失败/取消) |
OTA_STATE_RECONNECT | 7 | 等待/执行设备重连(双模切换) |
状态转换图:
stateDiagram-v2
[*] --> IDLE: 初始化
IDLE --> PREPARE: 开始升级任务
PREPARE --> DOWNLOAD: 需要下载固件
PREPARE --> UPGRADE: 固件已就绪
DOWNLOAD --> UPGRADE: 下载完成
UPGRADE --> START: SDK onStartOTA
START --> WORKING: onProgress
WORKING --> RECONNECT: 需切换连接方式
RECONNECT --> WORKING: 重连成功继续
WORKING --> STOP: onStopOTA / onCancelOTA / onError
STOP --> IDLE: 清理资源
RECONNECT --> STOP: 重连失败
端到端时序
sequenceDiagram
participant UI as 升级UI/ViewModel
participant OM as OTAManager
participant SDK as BluetoothOTAManager (SDK)
participant BH as BluetoothHelper
participant DEV as 手表设备
UI->>OM: startOTA(callback)
OM->>OM: checkDevice(device) 同步MTU
OM->>SDK: super.startOTA(CustomUpgradeCallback)
SDK-->>UI: onStartOTA()
SDK->>DEV: 发送升级指令/固件数据
SDK-->>UI: onProgress(index, progress)
SDK->>OM: onNeedReconnect(addr, isNewADV)
OM->>BH: 更新HistoryRecord(BLE映射地址)
OM->>BH: connectDeviceWithoutRecord(BLE)
BH-->>OM: onConnection(CONNECTED)
OM->>SDK: onBtDeviceConnection(OK)
SDK->>DEV: 继续发送固件数据
SDK-->>UI: onProgress(...) 直到100%
SDK-->>UI: onStopOTA()
OM->>OM: 删除本地固件文件(.ufw)
UI->>OM: release() 移除监听器
关键流程说明:
- 启动前同步 MTU:
checkDevice()在升级前读取当前 GATT 连接的 MTU,并以mtu + 3调用onMtuChanged通知 SDK,让 SDK 按实际 MTU 分包(源码见 OTAManager.java)。 - 回连地址映射:
onNeedReconnect回调中,若设备历史记录不是 BLE 连接,则把 BLE 地址写入历史记录(setConnectType(BLE)、setAddress(bleAddress)、setMappedAddress(edrAddress)),再交由上层回调触发重连。这是双模(BLE/EDR)手表升级前切 BLE 通道的关键一步(源码见 OTAManager.java)。 - 升级结束清理:
onStopOTA中删除本地固件文件,避免残留占用存储(源码见 OTAManager.java)。
核心实现详解
OTAManager 的构造与配置
OTAManager 构造时注册蓝牙事件监听并调用 configureOTA() 完成 SDK 配置(源码见 OTAManager.java):
private void configureOTA() {
BluetoothOTAConfigure configure = BluetoothOTAConfigure.createDefault();
int connectWay = mBluetoothHelper.getBluetoothOp().getBluetoothOption().getPriority();
if (mBluetoothHelper.isConnectedDevice()) {
BluetoothDevice device = mBluetoothHelper.getConnectedBtDevice();
connectWay = mBluetoothHelper.getBluetoothOp().isConnectedSppDevice(device) ? BluetoothConstant.PROTOCOL_TYPE_SPP : BluetoothConstant.PROTOCOL_TYPE_BLE;
}
configure.setPriority(connectWay)
.setNeedChangeMtu(false)
.setMtu(BluetoothConstant.BLE_MTU_MIN)
.setUseAuthDevice(false)
.setUseReconnect(false)
.setBleScanMode(2);
String otaDir = HealthUtil.createFilePath(HealthApplication.getAppViewModel().getApplication(), HealthConstant.DIR_UPDATE);
File dir = new File(otaDir);
boolean isExistDir = dir.exists();
if (!isExistDir) {
isExistDir = dir.mkdir();
}
if (isExistDir) {
String otaFilePath = HealthUtil.obtainUpdateFilePath(otaDir, OTA_FILE_SUFFIX);
if (null == otaFilePath) {
otaFilePath = otaDir + File.separator + OTA_FILE_NAME;
}
configure.setFirmwareFilePath(otaFilePath);
}
//保证两个库的SN生成器是同一个
final CmdSnGenerator snGenerator = WatchManager.getInstance().getCmdSnGenerator();
configure.setSnGenerator(snGenerator::getRcspCmdSeq);
configure(configure);
if (mBluetoothHelper.isConnectedDevice()) {
final BluetoothDevice connectedDev = mBluetoothHelper.getConnectedBtDevice();
onBtDeviceConnection(connectedDev, StateCode.CONNECTION_OK);
checkDevice(connectedDev);
}
}
Source: OTAManager.java
设计意图解读:
- 连接方式自适应:优先使用蓝牙配置中的默认优先级;若已连接设备是 SPP 设备则使用 SPP 协议,否则使用 BLE。这保证升级时选择与当前连接一致的物理通道。
- 固定 MTU 与关闭重连:
setNeedChangeMtu(false)+setMtu(BLE_MTU_MIN)避免升级过程中再次协商 MTU 引入不确定性;setUseReconnect(false)表示 SDK 不自行重连,重连完全交由应用层BluetoothHelper控制——这是为了让应用层统一管理连接状态与 UI 反馈。 - 固件路径约定:固件存放在应用私有目录
DIR_UPDATE下,优先查找已有.ufw文件,找不到则使用默认名update.ufw。 - SN 生成器共享:RCSP 指令与 OTA 指令共用
WatchManager的CmdSnGenerator,避免两个库各自计数导致指令序号冲突。
回调包装 CustomUpgradeCallback
startOTA() 把外部回调包装为 CustomUpgradeCallback,在转发给上层的同时附加资源清理与地址映射逻辑:
onStopOTA():清除 OTA 映射地址、删除固件文件后转发;onCancelOTA():清除otaAddress后转发;onError(BaseError):若错误码为SUB_ERR_NEED_UPDATE_RESOURCE(需要更新资源),清除映射地址;随后清除otaAddress并转发错误(源码见 OTAManager.java)。
蓝牙事件桥接
mBluetoothEventListener 是连接 BluetoothHelper 与 OTA SDK 的桥梁:
onBleMtuChange→onMtuChanged(gatt, mtu, status),保持 SDK 对 MTU 的感知;onConnection:设备已连接时先SystemClock.sleep(300)等待其他模块初始化,再用HealthUtil.convertOtaConnectStatus(status)把应用层连接状态码转换为 OTA SDK 的状态码后上报;onReceiveData→onReceiveDeviceData(device, data),把蓝牙数据透传给 SDK 解析。
配置选项
OTA 相关的可配置项集中在 BluetoothOTAConfigure 与文件路径约定中:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
priority | int | 蓝牙选项优先级 | 连接协议优先级(SPP/BLE),已连接设备时按实际类型取 |
needChangeMtu | boolean | false | 升级前是否需要 SDK 主动改 MTU |
mtu | int | BLE_MTU_MIN | 使用的 MTU 下限值 |
useAuthDevice | boolean | false | 是否使用认证设备流程 |
useReconnect | boolean | false | SDK 是否自行重连(false 表示由应用层接管重连) |
bleScanMode | int | 2 | BLE 扫描模式(2 为低功耗扫描) |
firmwareFilePath | String | DIR_UPDATE/update.ufw | 固件文件路径,优先复用已有 .ufw 文件 |
snGenerator | CmdSnGenerator | WatchManager 的生成器 | RCSP/OTA 共用的命令序号生成器 |
OTA_FILE_SUFFIX | String | .ufw | 固件文件后缀 |
OTA_ZIP_SUFFIX | String | .zip | 网络模块固件压缩包后缀 |
常量定义见 OTAManager.java,配置逻辑见 OTAManager.java。
网络模块(4G)OTA
除主控固件外,部分手表带有 4G 网络模块,其升级由 NetworkOpImpl 独立驱动。NetworkOTADemo 给出了标准调用序列(源码见 NetworkOTADemo.java):
@Test
public void startNetWorkOTA(String filePath) {
//Step0. WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
WatchManager watchManager = WatchManager.getInstance();
//Step1. 初始化网络模块升级操作对象
NetworkOpImpl networkOp = NetworkOpImpl.instance(watchManager);
if (networkOp.isNetworkOTA()) {
//正在OTA
return;
}
//执行开始网络OTA
//filePath --- 4G模块升级文件路径
networkOp.startNetworkOTA(watchManager.getConnectedDevice(), new OTAParam(filePath), new OnNetworkOTACallback() {
@Override
public void onStart() {
//回调OTA开始
}
@Override
public void onProgress(int progress) {
//回调OTA进度
}
@Override
public void onCancel() {
//回调OTA被取消
}
@Override
public void onStop() {
//回调OTA成功
}
@Override
public void onError(int code, String message) {
//回调OTA异常
//code --- 错误码
//message --- 错误描述
}
});
}
Source: NetworkOTADemo.java
网络模块升级前置检查
升级前需要确认设备支持网络模块:读取缓存的手表配置 WatchConfigure.getFunctionOption().isSupportNetworkModule(),若配置未缓存可调用 requestDeviceConfigure() 主动请求(源码见 NetworkOTADemo.java)。
压缩包解压与版本匹配
NetworkOtaViewModel.startOTA() 处理 zip 包:解压到临时目录 tmp,读取包内 sdkMap.json,把 map 列表中的每个固件版本号与设备当前网络模块版本号(networkInfo.getVersion())比对,版本一致即为要升级的固件包(源码见 NetworkOtaViewModel.java):
final List<SdkMapInfo.MapDTO> mapList = sdkMapInfo.getMap();
if (null != mapList) {
for (SdkMapInfo.MapDTO map : mapList) {
long mapVersion = convertVersionCode(networkInfo.getVid(), map.getVersion());
long firmwareVersion = convertVersionCode(networkInfo.getVid(), networkInfo.getVersion());
if (mapVersion == firmwareVersion) {
otaFilePath = AppUtil.obtainUpdateFilePath(otaDirPath, map.getPakage());
break;
}
}
}
Source: NetworkOtaViewModel.java
convertVersionCode() 使用正则 \d+ 提取版本字符串中的全部数字拼接为 long,如 "1.0.3" → 103,用于版本比较。升级结束或出错时删除解压的临时目录,避免垃圾文件残留;onCancel 错误码为 WatchError.ERR_CANCEL_OP。
网络模块升级异常监听
设备在升级失败后会主动上报异常,App 通过 OnWatchCallback.onNetworkModuleException 获知并提示用户执行强制升级(源码见 NetworkOTADemo.java)。
API 参考
OTAManager(继承 BluetoothOTAManager)
| 方法 | 返回 | 说明 |
|---|---|---|
OTAManager(Context context) | - | 构造,注册蓝牙监听并执行 configureOTA() |
getConnectedDevice() | BluetoothDevice | 返回当前连接的设备 |
getConnectedBluetoothGatt() | BluetoothGatt | 返回当前连接设备的 GATT |
connectBluetoothDevice(BluetoothDevice) | void | 记录 OTA 映射地址后连接设备(不写入历史) |
disconnectBluetoothDevice(BluetoothDevice) | void | 断开指定设备 |
sendDataToDevice(BluetoothDevice, byte[]) | boolean | 向设备发送数据 |
startOTA(IUpgradeCallback) | void | 包装回调并启动升级 |
release() | void | 释放资源并移除蓝牙监听 |
isSingleOTA() | boolean | 设备是否不支持双备份(单备份升级) |
OtaState
| 方法 | 返回 | 说明 |
|---|---|---|
getState()/setState(int) | int / OtaState | 升级阶段状态(见状态表) |
getMessage()/setMessage(OtaFileMsg) | OtaFileMsg / OtaState | 升级文件消息(云端下发信息) |
getOtaFilePath()/setOtaFilePath(String) | String / OtaState | 本地固件文件路径 |
getOtaType()/setOtaType(int) | int / OtaState | 升级类型(固件/资源/网络模块) |
getOtaProgress()/setOtaProgress(float) | float / OtaState | 升级进度 0~1 |
getStopResult()/setStopResult(int) | int / OtaState | 结束结果(成功/失败/取消) |
getError()/setError(BaseError) | BaseError / OtaState | 错误信息 |
getOtaTotal()/getOtaIndex()/getOtaFileInfo() | 见左 | 文件总数、当前序号、文件信息 |
NetworkOpImpl(网络模块 OTA)
| 方法 | 说明 |
|---|---|
instance(WatchManager) | 获取单例(绑定 WatchManager) |
queryNetworkInfo(device, callback) | 查询网络模块基础信息(VID/版本/是否强制升级) |
startNetworkOTA(device, OTAParam, OnNetworkOTACallback) | 启动网络模块升级 |
cancelNetworkOTA(device, callback) | 取消升级 |
isNetworkOTA() | 是否正在升级 |
addOnNetworkListener(OnNetworkListener) | 注册网络信息/升级状态监听 |
removeOnNetworkListener(OnNetworkListener) | 移除监听 |
失败模式、边界情况与并发
常见失败模式
| 场景 | 处理方式 | 源码依据 |
|---|---|---|
| 固件文件缺失 | 配置回退到默认路径 update.ufw;网络模块 zip 找不到匹配固件时以 SUB_ERR_FILE_NOT_FOUND 结束 | OTAManager.java、NetworkOtaViewModel.java |
| 需要更新资源 | onError 中 SUB_ERR_NEED_UPDATE_RESOURCE 触发清除 OTA 映射地址,交由上层进入资源更新流程(OTA_TYPE_OTA_UPDATE_RESOURCE) | OTAManager.java |
| 升级中断开连接 | 通过 onNeedReconnect 更新历史记录地址并交由应用层重连;重连失败进入 STOP | OTAManager.java |
| 4G 模块升级异常 | 设备上报 onNetworkModuleException,App 提示强制升级(NetworkInfo.isMandatoryOTA()) | NetworkOTADemo.java |
边界情况与并发注意
- 重复启动防护:
NetworkOpImpl.isNetworkOTA()在启动前检查,防止重复触发升级;startOTA()还校验当前状态非IDLE/STOP才允许继续。 - 连接状态转换:
onConnection中 300ms 延时是为了等待设备端其他模块(如外设信息上报)初始化完成,避免 OTA 指令与初始化流程竞争;状态码需经convertOtaConnectStatus()从应用层语义转换到 OTA SDK 语义。 - 地址映射竞态:
updateHistoryRecord仅在历史记录存在、且映射地址非法或与目标地址不一致时才更新,避免反复写历史记录;onStopOTA/onError会清除otaAddress,保证下一次升级从干净状态开始。 - SN 序号并发:RCSP 与 OTA 两个库共享同一个
CmdSnGenerator,指令序号全局递增,防止并发指令序号冲突导致设备端丢弃指令。 - 文件清理:升级结束(成功/取消/失败)都会删除本地固件或临时解压目录;网络模块 zip 解压的
tmp目录在回调结束时统一删除。
性能与运维注意事项
- MTU 策略:升级使用
BLE_MTU_MIN且禁止 SDK 动态改 MTU,分包大小固定可预测,降低大固件传输时的协商开销与失败概率;升级前由应用层上报实际 MTU(mtu + 3)。 - 大文件传输:
.ufw固件文件存放在应用私有目录DIR_UPDATE,升级完成后立即删除,避免占用存储;下载阶段(OTA_STATE_DOWNLOAD)依赖jl_health_http云服务,进度通过OtaState的otaProgress上报 UI。 - 进度粒度:
onProgress(int index, float progress)同时携带文件序号与总体进度,支持多文件(固件+资源)升级场景下 UI 展示"第 x/y 个文件"(otaTotal/otaIndex)。 - 重连开销:双模设备升级需从 EDR 切到 BLE,
onNeedReconnect中的地址映射与历史记录更新保证切换后能快速回连;300ms 延时是经验值,用于平衡"等待初始化"与"连接状态及时性"。
扩展点
- 升级类型扩展:
OtaState.OTA_TYPE_*已预留固件(1)、资源(2)、4G 模块(3)、网络模块(4)四类;新增升级目标时扩展该常量并在上层分发即可。 - 回调链定制:
OTAManager.startOTA()内部通过CustomUpgradeCallback包装外部回调,可在不侵入 SDK 的前提下插入资源清理、地址映射等横切逻辑——这是自定义行为的推荐位置。 - SDK 替换/升级:业务代码只依赖
jl_bt_ota的公开接口(BluetoothOTAManager、BluetoothOTAConfigure、IUpgradeCallback),升级 SDK 版本时只需适配configureOTA()中的配置项。 - 网络模块升级策略:
NetworkOtaViewModel的 zip 解析 +sdkMap.json版本匹配逻辑独立于 UI,可复用为后台静默升级服务。