杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目概述与功能特性
    • 工程结构与运行环境
  • 快速开始

    • SDK 集成步骤
    • 连接方式选择指南
  • 核心 SDK 架构

    • SDK 框架组成
    • JL_OTAManager 升级管理 API
    • 设备认证与广播解析
  • 蓝牙连接与设备发现

    • 设备扫描与广播发现
    • 原生 CoreBluetooth 连接
    • JL_BLEKit SDK 连接
    • JL_Assist 自定义连接
    • GATT Over BR/EDR 经典蓝牙升级
  • OTA 升级工作流

    • 标准升级流程
    • 自动化测试与批量升级
    • 广播音箱升级
    • 升级文件管理
  • 示例工程

    • 完整示例应用
    • 迷你示例工程
    • 第三方依赖与工具
  • 开发支持与版本发布

    • 文档中心与 API 说明
    • SDK 版本与构建产物
    • 调试技巧与日志辅助

完整示例应用

本页介绍 iOS-JL_OTA 仓库中的完整示例应用(code/JL_OTA/JL_OTA),它是杰理(Jieli)蓝牙 OTA 升级 SDK 的官方参考实现,演示了从应用启动、BLE 设备连接、OTA 固件升级到多语言、日志与崩溃上报的完整工程集成方式。

Purpose and Scope

本页覆盖完整示例应用 code/JL_OTA/JL_OTA 的端到端实现:

  • 应用入口与启动流程(main.m、AppDelegate.m)
  • 主界面模块划分(JLMainViewController、BroadcastMainViewController、StatementViewController、WebViewController 等)
  • 蓝牙管理模块:经典中心模式 JLBleManager 与辅助升级模式 JLBleAssistManager
  • 与 JL_OTAManager(OTA 管理器)的集成方式
  • 多语言、日志(JLLogHelper)、崩溃上报(Bugly)、OTA 文件导入等工程化能力

以下内容属于其他页面,不在本页展开:

  • JL_OTA SDK 核心源码与 JL_OTAManager 内部升级算法 → 参见 SDK 核心库相关页面
  • code/MiniDemo/JLAssistOTADemo 精简示例 → 参见"精简示例应用"页面
  • DFUnits.framework 与 JL_AdvParse 等二进制依赖的 API 细节 → 参见框架依赖相关页面

Overview

完整示例应用是一个可直接编译运行的 iOS 工程(Objective-C),其存在意义有两层:

  1. 作为 SDK 的权威使用范本:工程中的 JLBleManager 实现了 JL_OTAManagerDelegate、JLHashHandlerDelegate、SingleSendDelegate 等协议,展示了 OTA 升级每个阶段(设备信息读取、固件特征查询、升级流程驱动、进度回调)应该如何处理。
  2. 作为商业化 App 的骨架参考:除 OTA 核心逻辑外,工程还集成了多语言切换(中文/英文/韩文)、本地日志落盘、Bugly 崩溃统计、网络可达性监听、外部文件(升级固件)导入等生产级能力。

示例应用支持两种蓝牙交互模式:

  • 中心模式(Central):App 作为 BLE Central 直接连接设备,由 JLBleManager 管理 CBCentralManager 生命周期;
  • 辅助升级模式(Assist):App 通过已连接的设备(如耳机)中转,使用 JLBleAssistManager 驱动 mAssist.mCmdManager.mOTAManager 完成对另一设备的升级。

Architecture

下图展示了完整示例应用的整体架构与组件依赖关系(基于实际源码验证):

flowchart TD
    subgraph sg_Entry["入口层"]
        Main["main.m<br/>UIApplicationMain"]
        AppDelegate["AppDelegate"]
    end

    subgraph sg_UI["界面层"]
        StatementVC["StatementViewController<br/>用户声明页"]
        MainVC["JLMainViewController<br/>主控制器"]
        BroadcastVC["BroadcastMainViewController<br/>广播模式"]
        WebVC["WebViewController<br/>Web 页面"]
        AutoVC["JLAutoViewsController<br/>自动化视图"]
    end

    subgraph sg_BLE["蓝牙管理层"]
        BleManager["JLBleManager<br/>中心模式"]
        AssistManager["JLBleAssistManager<br/>辅助升级模式"]
        OTAManager["JL_OTAManager<br/>SDK OTA 管理器"]
    end

    subgraph sg_Infra["基础设施"]
        LogMgr["JLLogManager<br/>日志落盘"]
        Bugly["Bugly<br/>崩溃上报"]
        Reachability["AFNetworkReachabilityManager"]
        DFUnits["DFUnits.framework<br/>工具库"]
        OtaFileMgr["JLOtaFileManager<br/>OTA 文件管理"]
    end

    Main --> AppDelegate
    AppDelegate --> StatementVC
    AppDelegate --> MainVC
    AppDelegate --> BroadcastVC
    AppDelegate --> WebVC
    AppDelegate --> AutoVC
    MainVC --> BleManager
    BroadcastVC --> BleManager
    BleManager --> OTAManager
    AssistManager --> OTAManager
    OTAManager --> DFUnits
    AppDelegate --> LogMgr
    AppDelegate --> Bugly
    AppDelegate --> Reachability
    AppDelegate --> OtaFileMgr

各组件职责说明:

  • main.m:标准 iOS 入口,通过 UIApplicationMain 指定 AppDelegate 为应用代理类。
  • AppDelegate:启动总调度。负责屏幕常亮、日志初始化、Bugly 初始化、系统语言检测与切换、网络监听、UI 构建与开屏动画,并实现 openURL 处理外部传入的升级固件文件。
  • JLBleManager:中心模式蓝牙管理器,实现 CBCentralManagerDelegate、CBPeripheralDelegate、JL_OTAManagerDelegate、JLHashHandlerDelegate、SingleSendDelegate,持有 JL_OTAManager 单例(通过 [JL_OTAManager getOTAManager] 获取)。
  • JLBleAssistManager:辅助升级模式管理器,通过 mAssist.mCmdManager.mOTAManager 获取 OTA 管理器实例。
  • JL_OTAManager:SDK 提供的 OTA 升级核心,管理升级流程、固件特征与进度回调。
  • DFUnits.framework:SDK 自带工具框架(AES、CRC16、Gzip、Http、文件管理等),被 SDK 与示例共同依赖。

应用启动流程

入口点:main.m

与绝大多数 iOS 工程一致,示例应用以 main.m 为入口,唯一的特殊之处是显式指定 AppDelegate 作为应用代理类:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

Source: main.m

didFinishLaunchingWithOptions 启动序列

AppDelegate 在 application:didFinishLaunchingWithOptions: 中按固定顺序完成以下初始化(每一步都有明确的工程目的):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /*--- 设置屏幕常亮 ---*/
    [UIApplication sharedApplication].idleTimerDisabled = YES;
    
    /*--- 记录NSLOG ---*/
    [JLLogManager setLog:true IsMore:false Level:JLLOG_COMPLETE];
    [JLLogManager saveLogAsFile:true];
    [JLLogManager logWithTimestamp:true];
    [JLLogManager clearLog];
    
//    [[ToolsHelper share] openLogTextFile:1*1024*1024];
    [Bugly startWithAppId:@"292cbf624f"];
    
    // 初始化本地OTA升级文件,取消注释后可转移安装本地项目目录的ota升级文件到APP沙盒
//    [JLOtaFileManager initializeOtaFile];
    
    /*--- 检测当前语言 ---*/
    if ([kJL_GET hasPrefix:@"zh-Hans"]) {
        kJL_SET("zh-Hans");// 设置APP语言为中文
    } else if([kJL_GET hasPrefix:@"ko"]) {
        kJL_SET("ko");// set APP's language to ko-cn
    }else{
        kJL_SET("en");// set APP's language to English
    }

    
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    
    [self setupUI];
    
    /*--- 开启动画 ---*/
    [OpenShowView startOpenAnimation];
    
    return YES;
}

Source: AppDelegate.m

启动序列的设计意图:

  1. 屏幕常亮(idleTimerDisabled = YES):OTA 升级往往耗时数分钟,若系统自动锁屏会中断 BLE 连接与升级流程,因此示例应用在启动时即禁用自动锁屏。
  2. 日志系统(JLLogManager):打开完整日志级别(JLLOG_COMPLETE)、落盘保存、附加时间戳并在启动时清空历史日志——保证每次运行的日志从干净状态开始,便于 OTA 问题排查时导出。
  3. 崩溃上报(Bugly):使用腾讯 Bugly 统计崩溃,AppId 为 292cbf624f,这是商业化 App 的标准做法。
  4. 语言切换(kJL_GET/kJL_SET):依据系统语言前缀(zh-Hans → 中文,ko → 韩文,其余 → 英文)设置 App 内部语言,配合 Localizable 多语言表。
  5. 网络监听(AFNetworkReachabilityManager):启动网络可达性监听,用于后续检查 OTA 固件下载等网络依赖场景。
  6. UI 构建与开屏动画(setupUI + OpenShowView):先构建根视图结构,再播放品牌开屏动画。

多语言宏定义

AppDelegate 顶部定义了一组多语言宏,是整个工程国际化机制的入口:

/*--- 多语言 ---*/
#define kJL_GET         [DFUITools systemLanguage]                              //获取系统语言
#define kJL_SET(lan)    [DFUITools languageSet:@(lan)]                          //设置系统语言
#define kJL_TXT(key)    [DFUITools languageText:@(key) Table:@"Localizable"]    //多语言转换,"Localizable"根据项目的多语言包填写。

Source: AppDelegate.m

三个宏分别对应 DFUITools 的三个能力:读取系统语言、强制设置 App 语言、按 Localizable 表翻译文本。所有界面文案都应通过 kJL_TXT(key) 取词,从而支持中/英/韩三语切换。

界面模块划分

应用启动后,setupUI 会构建导航结构并依次展示以下界面:

flowchart TD
    Start(["应用启动"]) --> Statement["StatementViewController<br/>用户协议声明页"]
    Statement -->|"同意协议"| MainVC["JLMainViewController<br/>功能主菜单"]
    MainVC --> Tab1["BLE 连接 / OTA 升级<br/>JLBleManager"]
    MainVC --> Tab2["广播模式<br/>BroadcastMainViewController"]
    MainVC --> Tab3["辅助升级<br/>JLBleAssistManager"]
    MainVC --> Tab4["Web 页面<br/>WebViewController"]
    MainVC --> Tab5["自动化视图<br/>JLAutoViewsController"]
  • StatementViewController:用户协议声明页,实现 StatementViewControllerDelegate 协议回调(AppDelegate 遵循该协议并在类扩展中持有 statementNvc 导航控制器),只有用户同意后才进入主界面——这是合规要求。
  • JLMainViewController:功能主菜单,汇总所有演示入口,包括经典 BLE 连接、广播模式、辅助升级、WebView 等。
  • BroadcastMainViewController:广播模式(Broadcast)主界面,用于不支持标准连接流程的广播型设备的 OTA。
  • WebViewController:内嵌 Web 页面,通常用于展示使用说明或产品介绍。
  • JLAutoViewsController:自动化/批量测试视图,供产线或自动化验证使用。

蓝牙连接与 OTA 集成

中心模式:JLBleManager

JLBleManager 是完整示例应用的核心蓝牙管理类,它同时扮演两个角色:

  1. 系统 BLE 层:实现 CBCentralManagerDelegate 与 CBPeripheralDelegate,负责扫描、连接、发现服务与特征;
  2. SDK OTA 回调层:实现 JL_OTAManagerDelegate、JLHashHandlerDelegate、SingleSendDelegate,接收 SDK 的升级事件与数据发送请求。
@interface JLBleManager() <CBCentralManagerDelegate, CBPeripheralDelegate,JL_OTAManagerDelegate,JLHashHandlerDelegate,SingleSendDelegate>

Source: JLBleManager.m

在初始化流程中,示例应用通过 SDK 提供的单例方法获取 OTA 管理器,并打印 SDK 版本号:

/*--- JLSDK ADD ---*/
_otaManager = [JL_OTAManager getOTAManager];
[JL_OTAManager logSDKVersion];

Source: JLBleManager.m

设计意图:JL_OTAManager 以单例形式存在,保证整个 App 生命周期内只有一份升级状态机;JLBleManager 作为其宿主,将系统 BLE 的读写结果转发给 SDK,并将 SDK 的回调事件(如设备特征读取完成)转发给 UI 层。示例中实现了 otaFeatureResult: 回调来接收设备 OTA 特征查询结果:

-(void)otaFeatureResult:(JL_OTAManager *)manager{

Source: JLBleManager.m

辅助升级模式:JLBleAssistManager

对于需要"A 设备升级 B 设备"的场景(例如通过充电盒升级耳机),示例应用提供 JLBleAssistManager。该模式下 OTA 管理器不再直接对应系统 BLE 外设,而是通过辅助连接的命令管理器获取:

JL_OTAManager *otaManager = self.mAssist.mCmdManager.mOTAManager;

Source: JLBleAssistManager.m

关键差异:中心模式的 JL_OTAManager 直接驱动目标外设的读写通道,而辅助模式经由 mAssist.mCmdManager 把 OTA 数据包封装进宿主设备的命令通道中转发。示例同时保留两种模式,向集成方展示了在不同产品形态下的接入方式。

OTA 升级端到端流程

sequenceDiagram
    participant UI as 主界面<br/>(JLMainViewController)
    participant BM as JLBleManager
    participant SDK as JL_OTAManager<br/>(SDK 单例)
    participant BLE as 系统 CoreBluetooth
    participant Dev as 目标设备

    UI->>BM: 连接设备
    BM->>BLE: CBCentralManager connect
    BLE-->>BM: 连接成功 / 服务发现
    BM->>SDK: 配置设备信息<br/>(JL_OTAManager getOTAManager)
    SDK->>BLE: 读取 OTA 特征
    BLE-->>SDK: 特征数据
    SDK-->>BM: otaFeatureResult: 回调
    BM-->>UI: 刷新升级能力 UI
    UI->>SDK: 发起 OTA 升级
    SDK->>BLE: 写入固件数据包
    BLE-->>SDK: 写入结果 / 进度
    SDK-->>BM: 进度回调
    BM-->>UI: 更新进度条

流程要点:

  1. UI 层只与 JLBleManager 交互,不直接接触 SDK——分层清晰,便于替换 UI;
  2. 连接与特征发现完成后,SDK 通过 otaFeatureResult: 告知 App 设备支持的升级能力(如是否支持 hash 校验、单包发送模式);
  3. 升级过程中 SDK 以协议回调(进度、状态)驱动 UI,App 不需要自行解析固件包格式。

OTA 升级文件导入(openURL)

示例应用支持通过"文件 App → 打开方式"把外部 .ota 固件导入 App。AppDelegate 实现 application:openURL:options: 完成导入逻辑:

NSData *data = [NSData new];
if (url.isFileURL){
    data = [NSData dataWithContentsOfURL:url];
}
if ([url startAccessingSecurityScopedResource]){
    data = [NSData dataWithContentsOfURL:url];
    [url stopAccessingSecurityScopedResource];
}
if (data == nil) {
    kJLLog(JLLOG_ERROR, @"Open URL Failed. %@",url.absoluteString);
    return NO;
}

NSString *fname = url.path.lastPathComponent;
NSString *basicPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstObject;
basicPath = [basicPath stringByAppendingPathComponent:@"upgrade"];
NSFileManager *fm = [NSFileManager new];
BOOL isDir = FALSE;
BOOL isDirExist = [fm fileExistsAtPath:basicPath isDirectory:&isDir];

if(!(isDirExist && isDir)) {
    BOOL bCreateDir = [fm createDirectoryAtPath:basicPath
                             withIntermediateDirectories:YES
                                              attributes:nil
                                                   error:nil];
    if(!bCreateDir){
        kJLLog(JLLOG_DEBUG, @"Create upgrade Directory Failed.");
    }
}
NSString *docPath = [basicPath stringByAppendingPathComponent:fname];
if ([fm fileExistsAtPath:docPath]) {
    NSArray *arr = [fname componentsSeparatedByString:@"."];
    NSDateFormatter *dfm = [NSDateFormatter new];
    dfm.dateFormat = @"yyyyMMddHHmmss";
    NSString *newDateStr = [dfm stringFromDate:[NSDate new]];
    fname = [NSString stringWithFormat:@"%@_%@.%@",arr[0],newDateStr,arr[1]];
}
docPath = [basicPath stringByAppendingPathComponent:fname];
[fm createFileAtPath:docPath contents:data attributes:nil];

[[NSNotificationCenter defaultCenter] postNotificationName:@"REFRESH_FILE" object:nil];
return YES;

Source: AppDelegate.m

该实现的工程细节与边界处理:

  • 安全作用域资源:通过 startAccessingSecurityScopedResource/stopAccessingSecurityScopedResource 成对访问,正确处理 iOS 文件选择器(UIDocumentPicker)的沙盒外文件权限;
  • 目录懒创建:Documents/upgrade 目录不存在时以 withIntermediateDirectories:YES 递归创建,并在失败时记录 debug 日志;
  • 同名文件去重:目标文件已存在时,用 yyyyMMddHHmmss 时间戳重命名(如 fw_20250101120000.ota),避免覆盖旧固件,同时保留扩展名;
  • UI 刷新解耦:写入完成后通过 REFRESH_FILE 通知中心广播,文件列表界面订阅该通知自动刷新——导入逻辑与 UI 完全解耦。

配置选项

完整示例应用的可配置项集中在启动阶段,均为代码内常量/宏,未使用独立配置文件:

配置项类型默认值/示例说明出处
idleTimerDisabledBOOLYES禁用系统自动锁屏,防止 OTA 过程中断AppDelegate.m#L38
JLLogManager setLog:IsMore:Level:BOOL/枚举true / false / JLLOG_COMPLETE是否开启日志、详细模式与日志级别AppDelegate.m#L41
JLLogManager saveLogAsFile:BOOLtrue日志是否落盘保存AppDelegate.m#L42
JLLogManager logWithTimestamp:BOOLtrue日志是否附加时间戳AppDelegate.m#L43
Bugly AppIdstring292cbf624f腾讯 Bugly 崩溃上报 AppIdAppDelegate.m#L47
JLOtaFileManager initializeOtaFile方法调用注释关闭是否将工程内置 ota 文件复制到沙盒AppDelegate.m#L50
语言(kJL_GET/kJL_SET)stringzh-Hans / ko / en依据系统语言自动设置 App 语言AppDelegate.m#L53-L59
多语言表名stringLocalizablekJL_TXT 宏使用的 strings 表AppDelegate.m#L23
固件导入目录stringDocuments/upgradeopenURL 导入固件的落盘目录AppDelegate.m#L91
文件刷新通知名stringREFRESH_FILE固件导入完成后的通知名称AppDelegate.m#L119

集成方修改 Bugly AppId、多语言表名与语言映射策略时,只需调整 AppDelegate 中对应的常量与宏。

API 参考

AppDelegate

方法说明
application:didFinishLaunchingWithOptions:启动总调度:屏幕常亮、日志、Bugly、语言、网络监听、UI 与开屏动画
application:openURL:options:导入外部 OTA 固件文件到 Documents/upgrade,成功后广播 REFRESH_FILE
setupUI(内部)构建根视图导航结构并展示声明页

JLBleManager(中心模式)

协议方法/能力说明
CBCentralManagerDelegate / CBPeripheralDelegate系统 BLE 扫描、连接、服务与特征读写
JL_OTAManagerDelegate(含 otaFeatureResult:)接收 SDK 的 OTA 特征查询、升级进度与结果回调
JLHashHandlerDelegate / SingleSendDelegate支持 hash 校验固件与单包发送模式的设备
[JL_OTAManager getOTAManager]获取 OTA 管理器单例并持有(_otaManager)
[JL_OTAManager logSDKVersion]启动时打印 SDK 版本,便于核对集成版本

JLBleAssistManager(辅助升级模式)

属性/方法说明
mAssist.mCmdManager.mOTAManager经辅助命令通道获取 OTA 管理器,用于 A 设备升级 B 设备

故障模式、边界情况与并发

基于源码可验证的边界处理:

  1. 文件导入失败:openURL 中 data == nil 时记录 JLLOG_ERROR 并返回 NO,避免把空数据写入沙盒造成后续升级解析崩溃。
  2. 导入目录不存在:使用 withIntermediateDirectories:YES 递归创建;创建失败仅记录 debug 日志,不中断流程(后续写文件会失败并可由 UI 感知)。
  3. 同名固件冲突:追加 yyyyMMddHHmmss 时间戳重命名,防止覆盖旧固件;这也意味着同一秒内连续导入会冲突,但实际用户操作频率远低于该粒度,风险可接受。
  4. 沙盒外文件权限:通过 startAccessingSecurityScopedResource 成对获取/释放权限,防止 iOS 安全作用域资源访问异常。
  5. OTA 中断风险(设计层面):启动即 idleTimerDisabled = YES,从源头规避系统锁屏导致的 BLE 断连——这是对升级长耗时特性的主动防护。
  6. 并发/线程模型:JL_OTAManager 为单例,BLE 读写均在系统 CoreBluetooth 队列上串行回调;示例通过 delegate 回调驱动 UI,未引入额外的多线程共享状态,避免了升级过程中的数据竞争。日志系统在启动时 clearLog,保证单次运行日志自洽。

性能与运维建议

  • 日志导出:saveLogAsFile:true 使日志落盘,遇到升级失败可引导用户导出日志文件回传分析;JLLOG_COMPLETE 级别在发布版中可考虑降级以减少 IO 开销。
  • 固件目录清理:Documents/upgrade 会随时间累积固件文件,示例未实现自动清理,生产 App 建议在合适时机(如导入成功且升级完成后)删除已用固件。
  • 网络依赖:AFNetworkReachabilityManager 已启动监听,若 OTA 固件改为从服务器下载,可直接复用该监听判断网络可用性。

扩展点

  • 新增产品形态:参照 JLBleAssistManager 实现新的命令通道,或将 JLBleManager 的 delegate 回调转发给新 UI;
  • 多语言增补:在 Localizable 表按 kJL_TXT(key) 键值对补齐新语言,并在 AppDelegate 的语言映射分支中增加对应前缀判断;
  • 固件来源扩展:openURL 之外可增加 HTTP 下载后写入 Documents/upgrade,再复用 REFRESH_FILE 通知刷新列表;
  • 测试/产线自动化:JLAutoViewsController 预留了自动化入口,可在其上扩展批量升级与结果统计。

Related Links

  • SDK 核心库(code/JL_OTA):包含 JL_OTAManager、DFUnits.framework 等核心依赖
  • 精简示例应用(MiniDemo):聚焦辅助升级的最小示例,含 JL_AdvParse 广播解析框架
  • AppDelegate.m:应用启动与文件导入实现
  • JLBleManager.m:中心模式蓝牙与 OTA 回调实现
  • JLBleAssistManager.m:辅助升级模式实现
Next
迷你示例工程