自动化测试与批量升级
JL_OTA Demo 中基于 LoopUpdateManager 构建的自动化测试与批量升级能力:通过配置循环次数,将同一设备连续执行多轮固件升级(OTA),并在每次升级完成、设备重启后自动重连并进入下一轮,实现无人值守的升级稳定性压力测试与多文件批量升级。
Purpose and Scope
本页面介绍 iOS-JL_OTA 工程中"自动化测试与批量升级"(Auto Test OTA / Loop Update)完整机制,涵盖:
LoopUpdateManager升级队列的构建、推进与容错逻辑;ToolsHelper中自动化测试配置项的读写与持久化;- 升级完成后的自动重连闭环(BLE/HID);
- 相关 UI 视图(
AutoDevicesViewController、AutoFinishView、TipsFinishView、TipsProgressView)如何驱动队列启停。
以下内容属于其他页面范畴,不在本文展开:BLE 底层扫描/连接实现(见 JLBleManager 相关页面)、OTA 固件传输协议细节(见 OTA 升级流程页面)、UI 布局样式。
Overview
在耳机/音箱等 BLE 设备的量产或研发验证阶段,通常需要对固件升级链路做高强度的稳定性验证——同一台设备反复升级数十次,观察是否出现升级失败、连接丢失、设备变砖等问题。手工逐次操作既低效又易出错。
为此,JL_OTA Demo 内置了**自动化测试升级(Auto Test OTA)**功能:
- 用户通过
ToolsHelper配置循环升级次数 N 与容错参数; LoopUpdateManager将选择的固件文件按 N 次展开为升级队列;- 逐次调用
JLBleManager执行 OTA,每轮升级完成后记录设备 UUID,等待设备重启; - 设备重启后自动重连(支持 HID 与普通 BLE 两种方式),并自动开始下一轮;
- 队列耗尽或出错(达到容错上限)时停止,整个过程无需人工干预。
其本质是一个串行队列 + 自动重连循环的状态机:升级中 → 完成/失败 → 重连 → 下一轮。队列长度、容错开关、容错次数均由本地 NSUserDefaults(通过 DFTools 封装)持久化。
Architecture
flowchart TD
subgraph sg_UI["UI 层(视图/控制器)"]
AutoDevicesVC["AutoDevicesViewController"]
AutoFinishView["AutoFinishView"]
TipsFinishView["TipsFinishView"]
TipsProgressView["TipsProgressView"]
end
subgraph sg_Core["核心层"]
LoopMgr["LoopUpdateManager(单例)"]
LoopInfo["LoopInfo(进度模型)"]
end
subgraph sg_Config["配置层"]
ToolsHelper["ToolsHelper"]
end
subgraph sg_Ble["蓝牙层"]
JLBleManager["JLBleManager"]
Peripheral["BLE/HID 设备"]
end
AutoDevicesVC -->|"startLoopUpdate / toNextUpdate"| LoopMgr
AutoFinishView -->|"shouldLoopUpdate / cleanList"| LoopMgr
TipsFinishView -->|"cleanList(出错终止)"| LoopMgr
TipsProgressView -->|"shouldLoopUpdate / reConnectByAutoUpdate"| LoopMgr
LoopMgr --> LoopInfo
LoopMgr -->|"isAutoTestOta / getAutoTestOtaNumber / getFaultTolerant"| ToolsHelper
LoopMgr -->|"otaFuncWithFilePath / connectPeripheralWithUUID / findHid"| JLBleManager
JLBleManager --> Peripheral
架构说明:LoopUpdateManager 是整个机制的中枢,UI 层只负责触发与展示,不直接操作蓝牙;配置层 ToolsHelper 向队列管理器提供开关与次数;蓝牙层 JLBleManager 是唯一的传输执行者,负责固件下发(otaFuncWithFilePath:)与重连(connectPeripheralWithUUID:/findHid:)。LoopInfo 作为轻量进度模型,承载当前文件名与"第几个/共几个"的展示信息。
核心实现:LoopUpdateManager 队列管理
LoopUpdateManager 位于 Tools/LoopUpdateManager.m,采用 dispatch_once 单例模式,内部持有一个可变数组 updatePath 作为升级队列,并保留重连 UUID、完成次数、失败次数、状态等运行期信息。
单例与初始化
+(instancetype)share{
static LoopUpdateManager *loopMgr;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
loopMgr = [LoopUpdateManager new];
});
return loopMgr;
}
- (instancetype)init
{
self = [super init];
if (self) {
updatePath = [NSMutableArray new];
self.info = [LoopInfo new];
}
return self;
}
Source: LoopUpdateManager.m
设计意图:整个 App 生命周期内只需一个队列实例,所有视图(自动测试页、完成弹窗、进度弹窗)通过 [LoopUpdateManager share] 访问同一份队列状态,避免多实例导致队列不同步。初始化时创建空的 updatePath 数组并预置 LoopInfo 进度对象。
队列构建:startLoopUpdate:
-(BOOL)startLoopUpdate:(NSArray *)filePaths{
if(updatePath.count > 0){
[self toNextUpdate];
return YES;
}
self.reConnectUUID = [JLBleManager sharedInstance].mBlePeripheral.identifier.UUIDString;
kJLLog(JLLOG_DEBUG, @"self.reConnectUUID:%@",self.reConnectUUID);
_finishNumber = 0;
NSInteger number = [ToolsHelper getAutoTestOtaNumber];
if(number == 0){
return NO;
}
int count = 0;
while(count < number){
for (NSString *path in filePaths) {
NSString *filePath = [DFFile listPath:NSDocumentDirectory MiddlePath:@"upgrade" File:path];
[updatePath addObject:filePath];
count++;
if (count == number){
break;
}
}
}
kJLLog(JLLOG_DEBUG, @"auto update count:%d",updatePath.count);
[[JLBleManager sharedInstance] otaFuncWithFilePath:updatePath.firstObject];
self.info.name = [updatePath.firstObject lastPathComponent];
self.info.nowIndexStr = [NSString stringWithFormat:@"%d/%d",(int)self.finishNumber+1,(int)number];
return YES;
}
Source: LoopUpdateManager.m
关键逻辑:
- 幂等保护:若队列非空说明上一轮尚未结束,直接调用
toNextUpdate推进而不是重建队列; - 记录重连目标:把当前外设的
identifier.UUIDString保存为reConnectUUID,供设备重启后重连使用; - 队列展开:从
ToolsHelper读取循环次数number,将传入的文件列表按需重复填充进updatePath,直到总数达到 N。这意味着同一份固件可以循环升级 N 次,也支持多个文件按序批量升级; - 立即开跑:取队首文件调用
otaFuncWithFilePath:开始第一轮升级,并通过LoopInfo记录当前文件名与1/N进度。
队列推进:toNextUpdate
-(BOOL)toNextUpdate{
if([ToolsHelper getFaultTolerant]){
if([ToolsHelper getFaultTolerantTimes] > self.failedNumber){
if(_status == DeviceOtaStatusPrepare || _status == DeviceOtaStatusFinish){
@try {
[updatePath removeObjectAtIndex:0];
} @catch (NSException *exception) {
kJLLog(JLLOG_DEBUG, @"自动升级队列为空");
return NO;
} @finally {
}
}
}else{
}
}else{
@try {
[updatePath removeObjectAtIndex:0];
} @catch (NSException *exception) {
kJLLog(JLLOG_DEBUG, @"自动升级队列为空");
return NO;
} @finally {
}
}
if([ToolsHelper isAutoTestOta] && updatePath.count > 0){
kJLLog(JLLOG_DEBUG, @"toNextUpdate 剩余:%d 次", updatePath.count);
[[JLBleManager sharedInstance] otaFuncWithFilePath:updatePath.firstObject];
self.info.name = [updatePath.firstObject lastPathComponent];
NSInteger max = [ToolsHelper getAutoTestOtaNumber];
self.info.nowIndexStr = [NSString stringWithFormat:@"%d/%d",(int)_finishNumber+1,(int)max];
return true;
}
return NO;
}
Source: LoopUpdateManager.m
设计意图与容错策略:
- 容错开关(fault_tolerant):开启时,仅当累计失败次数
failedNumber小于容错阈值fault_tolerant_times,且当前状态为DeviceOtaStatusPrepare或DeviceOtaStatusFinish时才弹出队首并继续;否则停在原地不再推进,等待外部处理。关闭容错时则无条件移除队首继续,即"失败也继续下一轮"; - 空队列防护:用
@try/@catch包裹removeObjectAtIndex:0,队列为空时捕获异常并记录"自动升级队列为空",返回 NO 终止; - 推进条件:只有自动化测试开关
isAutoTestOta为真且队列仍有剩余时才调用otaFuncWithFilePath:开启下一轮,并同步更新进度文案。
自动重连:startLoopOta
-(void)startLoopOta{
if([ToolsHelper isAutoTestOta]){
if([ToolsHelper isSupportHID]){
[[JLBleManager sharedInstance] findHid:self.reConnectUUID];
}else{
[[JLBleManager sharedInstance] connectPeripheralWithUUID:self.reConnectUUID];
}
}
}
Source: LoopUpdateManager.m
设计意图:OTA 升级完成后设备会重启并断开蓝牙,必须等设备重新广播后再连接。该方法根据设备是否支持 HID 选择 findHid:(HID 重连)或 connectPeripheralWithUUID:(普通 BLE 按 UUID 重连),是"设备重启 → 自动回连 → 下一轮"闭环的入口,由进度弹窗在升级结束后调用。
队列状态查询与清理
-(BOOL)shouldLoopUpdate{
if(updatePath.count>1) return YES;
return NO;
}
-(void)cleanList{
[updatePath removeAllObjects];
}
Source: LoopUpdateManager.m
shouldLoopUpdate 用于判断"当前是否处于批量升级模式"(队列中还有 1 个以上待升级项),UI 据此决定展示"继续升级"还是普通完成提示;cleanList 用于用户主动取消或错误发生时清空队列,终止整个自动化流程。
配置层:ToolsHelper 的自动化测试设置
自动化测试相关的所有配置均通过 ToolsHelper(Tools/ToolsHelper.m)读写,底层由 DFTools 封装 NSUserDefaults 持久化,App 重启后配置依然保留。
+(BOOL)isAutoTestOta{
if([DFTools getUserByKey:@"AutoTestOta"]){
return [[DFTools getUserByKey:@"AutoTestOta"] boolValue];
}else{
return NO;
}
}
+(void)setAutoTestOta:(BOOL)status{
[DFTools setUser:[NSNumber numberWithBool:status] forKey:@"AutoTestOta"];
}
+(NSInteger)getAutoTestOtaNumber{
if([DFTools getUserByKey:@"AutoTestOtaNumber"]){
kJLLog(JLLOG_DEBUG,@"getAutoTestOtaNumber:%d",[[DFTools getUserByKey:@"AutoTestOtaNumber"] intValue]);
return [[DFTools getUserByKey:@"AutoTestOtaNumber"] intValue];
}else{
kJLLog(JLLOG_DEBUG, @"getAutoTestOtaNumber:1");
return 1;
}
}
+(void)setAutoTestOtaNumber:(NSInteger)number{
kJLLog(JLLOG_DEBUG, @"setAutoTestOtaNumber:%d",number);
[DFTools setUser:[NSNumber numberWithInt:(int)number] forKey:@"AutoTestOtaNumber"];
}
Source: ToolsHelper.m
容错配置的读写与之对称:setFaultTolerant:/getFaultTolerant 使用键 fault_tolerant(BOOL),setFaultTolerantTimes:/getFaultTolerantTimes 使用键 fault_tolerant_times(NSInteger),读取时均对"未设置"情形给出安全默认值。
Source: ToolsHelper.m
设计意图:把配置读写收敛到 ToolsHelper 单一入口,队列管理器、UI 各视图只需调用语义化方法,无需关心存储键与默认值细节;所有值用 NSNumber 包装存储,保证类型安全。
核心流程:一轮完整的多轮升级闭环
sequenceDiagram
participant VC as AutoDevicesViewController
participant LM as LoopUpdateManager
participant TH as ToolsHelper
participant BM as JLBleManager
participant DV as 目标设备
VC->>LM: startLoopUpdate:filePaths
LM->>TH: getAutoTestOtaNumber
TH-->>LM: 循环次数 N(默认 1)
LM->>LM: 展开队列 updatePath(共 N 个文件路径)
LM->>BM: otaFuncWithFilePath: 队首固件
BM->>DV: 传输并升级固件
Note over BM,DV: 升级完成,设备重启断开
BM-->>LM: 状态变为 DeviceOtaStatusFinish
LM->>LM: toNextUpdate(按容错规则移除队首)
LM->>BM: startLoopOta → findHid/connectPeripheralWithUUID
BM->>DV: 自动重连成功
LM->>BM: otaFuncWithFilePath: 下一个固件
Note over LM: 循环执行直至队列耗尽<br/>或失败次数达到容错上限
流程要点:
- 启动:自动测试设备页调用
startLoopUpdate:,队列按配置次数 N 展开,立即发起首轮 OTA; - 升级:
JLBleManager负责固件传输;期间进度弹窗TipsProgressView在升级推进时检查shouldLoopUpdate,若处于批量模式则展示自动重连提示(reConnectByAutoUpdate); - 完成/失败:升级结束后调用
toNextUpdate推进队列——容错开启时仅在状态为Prepare/Finish且失败次数未超限时继续;出错时TipsFinishView调用cleanList终止流程并提示"发生错误导致关闭自动化升级"; - 重连:
startLoopOta按isSupportHID选择 HID 或 BLE 重连到reConnectUUID对应的设备; - 循环:重连成功后自动开始下一轮,直到队列清空或用户确认终止。
UI 集成:视图如何驱动队列
自动测试设备页
AutoDevicesViewController(Views/AutoTestUpdate/AutoDevicesView/)是自动化测试的入口界面。在自动升级刚开始触发时,它直接驱动队列前进:
//当自动刚升级时触发以下方法
[[LoopUpdateManager share] toNextUpdate];
Source: AutoDevicesViewController.m
完成弹窗 AutoFinishView
升级全部完成后,若仍处于批量模式(shouldLoopUpdate 为 YES),弹窗提示"当前存在升级任务",用户确认后调用 cleanList 清空队列结束流程:
if ([[LoopUpdateManager share] shouldLoopUpdate]){
UIAlertController *alert = [UIAlertController alertControllerWithTitle:kJL_TXT("tips") message:kJL_TXT("current_update_task") preferredStyle:UIAlertControllerStyleAlert];
...
[alert addAction:[UIAlertAction actionWithTitle:kJL_TXT("confirm") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[LoopUpdateManager share] cleanList];
self.hidden = YES;
}]];
}
Source: AutoFinishView.m
错误终止 TipsFinishView
升级过程发生错误时,错误弹窗直接终止整个自动化流程:
kJLLog(JLLOG_ERROR, @"发生错误导致关闭自动化升级!!!");
[[LoopUpdateManager share] cleanList];
Source: TipsFinishView.m
进度弹窗 TipsProgressView
OTA 进度推进过程中,若处于自动化测试模式且存在批量队列,则触发自动重连视图:
if([ToolsHelper isAutoTestOta]){
if([[LoopUpdateManager share] shouldLoopUpdate]){
[self.autoView reConnectByAutoUpdate];
}
}
Source: TipsProgressView.m
设计意图:UI 层只做"查询状态 + 触发动作",所有队列状态以 LoopUpdateManager 单例为唯一事实来源,保证多个弹窗视图之间不会出现状态漂移。
用法示例
示例一:开启自动化测试并配置循环次数
在开始自动升级前,通过 ToolsHelper 打开自动化测试开关并设置升级轮数:
[ToolsHelper setAutoTestOta:YES]; // 打开自动化测试模式
[ToolsHelper setAutoTestOtaNumber:10]; // 连续升级 10 次
[ToolsHelper setFaultTolerant:YES]; // 开启容错
[ToolsHelper setFaultTolerantTimes:3]; // 最多容忍 3 次失败
Source: ToolsHelper.m
示例二:启动批量升级队列
传入固件文件名列表(文件需已放入 Documents/upgrade 目录),队列管理器会按配置次数展开并立即开始第一轮:
NSArray *fileNames = @[@"app_update_1.0.0.ufw", @"app_update_1.0.1.ufw"];
BOOL started = [[LoopUpdateManager share] startLoopUpdate:fileNames];
if (started) {
// 队列已启动,等待升级完成回调推进下一轮
}
Source: LoopUpdateManager.m
示例三:判断并终止批量流程
if ([[LoopUpdateManager share] shouldLoopUpdate]) {
// 仍有多轮待升级,提示用户
[[LoopUpdateManager share] cleanList]; // 用户确认后终止
}
Source: LoopUpdateManager.m
配置项
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
AutoTestOta | BOOL | NO | 自动化测试总开关,isAutoTestOta 为 YES 时队列才会推进与自动重连 |
AutoTestOtaNumber | NSInteger | 1 | 循环升级次数,0 时 startLoopUpdate: 直接返回 NO 不启动 |
fault_tolerant | BOOL | 未设置(读为 NO) | 容错开关;开启后仅当失败次数未达上限且状态为 Prepare/Finish 时才推进队列 |
fault_tolerant_times | NSInteger | 未设置(读为 0) | 容错次数上限,实际生效条件为 getFaultTolerantTimes > failedNumber |
所有配置通过 DFTools(NSUserDefaults 封装)持久化,App 重启后保留。getAutoTestOtaNumber 在未设置时默认返回 1,保证单次升级场景不受影响。
API 参考
LoopUpdateManager(单例)
+ (instancetype)share
- 描述:获取全局唯一队列管理器实例(
dispatch_once保证线程安全)。 - 返回:
LoopUpdateManager *。
- (BOOL)shouldLoopUpdate
- 描述:判断是否处于批量升级模式。
- 返回:
updatePath.count > 1时为 YES,否则 NO。
- (void)cleanList
- 描述:清空升级队列,立即终止自动化流程(用户取消或错误终止时调用)。
- (BOOL)startLoopUpdate:(NSArray *)filePaths
- 参数:
filePaths(NSArray<NSString>*)固件文件名列表,文件需位于Documents/upgrade目录。 - 行为:队列为空时按
getAutoTestOtaNumber展开队列、记录reConnectUUID、重置完成计数,并立即升级队首文件;队列非空时直接推进toNextUpdate。 - 返回:启动成功 YES;配置次数为 0 时返回 NO。
- 状态副作用:设置
info.name(当前文件名)、info.nowIndexStr(形如"1/N"的进度文案)。
- (BOOL)toNextUpdate
- 行为:按容错规则移除队首;若
isAutoTestOta且队列仍有剩余,则开始下一轮升级并更新进度。 - 返回:成功推进并启动下一轮 YES;队列为空或非自动测试模式返回 NO。
- 异常:
removeObjectAtIndex:0越界时由@try/@catch捕获并返回 NO。
- (void)startLoopOta
- 行为:自动化测试模式下自动重连设备;
isSupportHID为 YES 时调用findHid:,否则调用connectPeripheralWithUUID:,均使用reConnectUUID作为目标。
- (void)startLoopOta 依赖的 JLBleManager 接口(本页仅说明调用关系):
otaFuncWithFilePath:— 以指定固件路径开始一次 OTA 升级;connectPeripheralWithUUID:— 按 UUID 重连普通 BLE 设备;findHid:— 按 UUID 重连支持 HID 的设备。
ToolsHelper(类方法)
+ (BOOL)isAutoTestOta / + (void)setAutoTestOta:(BOOL)status
- 读写键
AutoTestOta,未设置时默认 NO。
+ (NSInteger)getAutoTestOtaNumber / + (void)setAutoTestOtaNumber:(NSInteger)number
- 读写键
AutoTestOtaNumber,未设置时默认 1。
+ (BOOL)getFaultTolerant / + (void)setFaultTolerant:(BOOL)status
- 读写键
fault_tolerant。
+ (NSInteger)getFaultTolerantTimes / + (void)setFaultTolerantTimes:(NSInteger)number
- 读写键
fault_tolerant_times。
LoopInfo(进度模型)
LoopInfo 为轻量数据模型(定义于 LoopUpdateManager.h,实现为空壳类),由队列管理器在运行时写入 name(当前固件文件名)与 nowIndexStr("当前轮/总轮数")供 UI 展示;LoopUpdateManager 自身维护 finishNumber(已完成数)、failedNumber(失败数)、status(DeviceOtaStatusPrepare / DeviceOtaStatusFinish 等)与 reConnectUUID 等运行期状态。
失败模式、边界情况与并发
空队列 / 越界
toNextUpdate 与 startLoopUpdate 均在 removeObjectAtIndex:0 处使用 @try/@catch 防御。队列为空时抛出 NSException,被捕获后记录日志"自动升级队列为空"并返回 NO,流程安全终止而非崩溃。这是对本机制最关键的边界保护——任何回调乱序(如升级完成后重复调用推进)都不会导致数组越界。
容错开关的两种行为
- 容错开启:仅当
getFaultTolerantTimes > failedNumber且 当前状态为DeviceOtaStatusPrepare或DeviceOtaStatusFinish时才弹出队首继续。这意味着一旦升级失败且状态落在其他分支(如失败态),队列会"卡住"停止推进,等待外部干预——这是有意设计,防止在设备异常时继续烧写造成变砖; - 容错关闭:无条件移除队首继续,即失败也照常进入下一轮,适合希望"跑完 N 轮"的压力测试场景。
状态机约束
推进操作依赖 _status 属于 DeviceOtaStatusPrepare/DeviceOtaStatusFinish 之一。状态由 JLBleManager 的 OTA 回调驱动,任何"升级中"状态下的推进都会被容错逻辑拦截,避免在传输中途切换固件。
并发与线程
LoopUpdateManager 单例本身不持有串行队列或锁,所有操作在调用线程(BLE 回调线程与 UI 线程)上直接执行。updatePath 的读写(cleanList 与 toNextUpdate 可能在不同线程触发)存在理论上的竞态:例如用户确认取消(UI 线程 cleanList)与升级完成回调(BLE 线程 toNextUpdate)同时发生时,可能出现"清空后又被追加/推进"的窗口。现有代码通过 @try/@catch 保证不崩溃,但未做显式同步;若需在生产中强化,应在 cleanList/toNextUpdate 上增加互斥。
设备重启失联
自动重连依赖 reConnectUUID。若设备重启后长时间未广播、或 UUID 因系统重置变化,findHid:/connectPeripheralWithUUID: 将无法回连,队列停留在等待重连状态——源码未包含超时重试或放弃逻辑,实际表现为自动化流程挂起,需人工介入。
性能与运维注意事项
- 串行执行:多轮升级严格按队列顺序逐次执行,天然避免多设备/多线程并发烧写的资源竞争,但单设备总耗时 = 单轮耗时 × N,压测周期较长;
- 队列内存:
updatePath仅保存文件路径字符串(非固件数据),N 很大时内存开销可忽略;固件数据由JLBleManager按轮读取; - 文件目录约定:固件文件必须预置于
Documents/upgrade目录([DFFile listPath:NSDocumentDirectory MiddlePath:@"upgrade" File:path]),路径拼接在队列构建时完成; - 日志:关键节点(重连 UUID、队列剩余次数、错误终止)均通过
kJLLog输出,可在真机日志中按auto update count、toNextUpdate 剩余关键字跟踪压测进度; - 配置持久化:循环次数与容错参数存于
NSUserDefaults,测试完成后应调用setAutoTestOta:NO关闭自动化开关,避免后续普通升级被意外带入批量模式。
扩展点
- 新增推进策略:
toNextUpdate中的容错分支(getFaultTolerant开关)即策略插槽,可按需扩展"失败重试次数""失败即终止"等规则; - 重连方式扩展:
startLoopOta根据isSupportHID二选一(HID/BLE),新协议设备的回连可在该方法中追加分支; - 进度模型:
LoopInfo目前仅承载名称与nowIndexStr,可扩展添加剩余字节、耗时等字段供 UI 增强; - 触发入口:
AutoDevicesViewController是当前唯一启动入口,startLoopUpdate:的filePaths参数使其可被其他页面复用,实现任意文件列表的批量升级。
测试覆盖情况
源码中未发现针对 LoopUpdateManager/ToolsHelper 的单元测试文件;自动化测试能力的验证主要依赖真机上的 AutoDevicesViewController 人工压测(配置次数后观察多轮升级)。@try/@catch 空队列防御、number == 0 提前返回等边界处理体现了对异常路径的显式考量,可作为后续补充 XCTest 用例的切入点(如空队列推进、容错开关分支、次数为 0 启动)。
Related Links
- LoopUpdateManager.m 源码
- ToolsHelper.m 源码
- AutoDevicesViewController.m 源码
- AutoFinishView.m 源码
- TipsFinishView.m 源码
- TipsProgressView.m 源码
- 相关主题:OTA 单次升级流程(
JLBleManager的otaFuncWithFilePath:传输细节见 OTA 升级流程页面)、BLE 连接管理(见 JLBleManager 页面)、固件配置与升级包管理(见 UfwConfig 页面)