JL_AdvParse 广播包解析
JL_AdvParse 是杰理(Jieli)iOS 蓝牙 SDK 中用于解析 BLE 广播包的独立 XCFramework,负责将 CoreBluetooth 扫描到的原始广播字典(advertData)解析为结构化结果,并识别设备类型(音箱、TWS 耳机、充电仓、声卡、手表等),同时提供 OTA 升级回连场景下从广播包中提取蓝牙地址的辅助能力。
Purpose and Scope
本页面完整介绍 JL_AdvParse.xcframework 的公共 API 契约、类型体系、设备子模块划分与解析架构,覆盖:
- 框架的交付形态(XCFramework、支持平台、依赖关系)
JL_AdvType/JL_DeviceType两种设备类型枚举及其设计差异JLAdvParse入口类的全部公开方法(广播解析、RSSI 扩展、OTA 回连辅助)- 8 个设备类型子模块(
JLDevicesAdv、JLTwsAdv、JLSoundBoxAdv、JLSoundCardAdv、JLChargingBoxAdv、JLEarphoneAdv、JLWatchAdv、JLOtaAdv)的职责划分 - 解析流程、失败模式、线程安全与扩展方式
边界说明:本页面只覆盖广播包解析这一能力。JLAdvParse 框架内部的解析算法以编译后二进制形式交付,本仓库仅含头文件,因此本文档基于公开头文件契约展开,内部实现细节在源码中不可见时会明确标注。与此相邻但不在本页面范围内的能力包括:蓝牙连接管理(JL_BLE)、设备管理(JL_Manager)与 OTA 升级(JL_OT)等,请参见对应目录的独立页面。
概述
在 BLE 设备接入流程中,App 通过 CBCentralManager 扫描到设备后会拿到一个广播字典(包含 kCBAdvDataManufacturerData、kCBAdvDataLocalName 等键值)。不同品类的杰理设备(AI 音箱、TWS 耳机、充电仓、声卡、手表、传统设备)在广播包中编码的厂商数据格式各不相同,如果由每个业务 App 自行解析,会产生大量重复且易错的代码。
JLAdvParse 的设计意图正是把广播包解析从业务层抽离为单一职责的公共组件:
- 统一入口:App 只需调用
+bluetoothAdvParse:AdvData:或+bluetoothAdvData:RSSI:,传入 CoreBluetooth 的广播字典,即可拿到结构化结果; - 类型分发:框架内部根据厂商数据判定设备类型,并分发到对应的子模块(
JLTwsAdv、JLSoundBoxAdv等)做品类定制化解析; - OTA 回连支持:OTA 升级过程中设备会短暂断连,App 需要依据广播包中
JLOTA标识的厂商数据里的蓝牙地址完成回连,框架为此提供otaBleMacAddressFromCBAdvDataManufacturerData:等专门方法; - 无状态设计:所有 API 均为类方法,无实例状态,天然适合在扫描回调中高频调用。
架构
flowchart TD
subgraph sg_App["App 层 (CoreBluetooth)"]
CB["CBCentralManager<br/>扫描得到的 advertData 字典"]
end
subgraph sg_Framework["JL_AdvParse.xcframework"]
Entry["JLAdvParse 入口<br/>bluetoothAdvParse:AdvData:<br/>bluetoothAdvData:RSSI:"]
subgraph sg_Modules["设备类型子模块"]
DevAdv["JLDevicesAdv"]
TwsAdv["JLTwsAdv"]
SoundBox["JLSoundBoxAdv"]
SoundCard["JLSoundCardAdv"]
ChargingBox["JLChargingBoxAdv"]
Earphone["JLEarphoneAdv"]
Watch["JLWatchAdv"]
Ota["JLOtaAdv"]
end
Result["NSDictionary<br/>结构化解析结果"]
TypeEnum["JL_AdvType / JL_DeviceType<br/>设备类型枚举"]
OtaHelper["OTA 回连辅助<br/>otaBleMacAddress 系列方法"]
end
subgraph sg_Dep["外部依赖"]
Log["JLLogHelper"]
CBKit["Foundation / CoreBluetooth"]
end
CB -->|"广播字典"| Entry
Entry --> DevAdv
Entry --> TwsAdv
Entry --> SoundBox
Entry --> SoundCard
Entry --> ChargingBox
Entry --> Earphone
Entry --> Watch
Entry --> Ota
Entry --> Result
Entry --> TypeEnum
Entry --> OtaHelper
Entry -.->|"日志"| Log
Entry -.-> CBKit
架构说明:
- 入口层
JLAdvParse:对外暴露的全部能力集中在 JLAdvParse.h 中,包括两个解析入口和两个 OTA 辅助方法。所有方法均为+类方法,调用方无需持有实例。 - 设备类型子模块:伞头文件 JL_AdvParse.h 依次导入
JLDevicesAdv、JLTwsAdv、JLSoundBoxAdv、JLSoundCardAdv、JLChargingBoxAdv、JLEarphoneAdv、JLWatchAdv、JLOtaAdv八个头文件,每个子模块对应一类设备的广播数据模型,是"按品类扩展"的天然扩展点。 - 结果形态:解析结果统一以
NSDictionary返回,便于与既有KVC/字典桥接代码集成;设备类型通过枚举常量表达,避免魔法数字。 - 依赖:框架依赖
JLLogHelper(日志组件,见 JLAdvParse.h 的 import 语句)与 Foundation 框架;广播字典的键名遵循 CoreBluetooth 的kCBAdvData*约定。
交付形态:仓库中 JL_AdvParse.xcframework 包含 ios-arm64(真机)、ios-arm64_x86_64-simulator(模拟器)与 macos-arm64_x86_64(macOS)三个平台切片,头文件位于各切片的 Headers/ 目录下;主版本号通过 JL_AdvParseVersionNumber / JL_AdvParseVersionString 导出。
核心类型体系
JL_AdvType 广播类型枚举
JL_AdvType 定义在 JLAdvParse.h 第 14-22 行,是一个 UInt8 基础类型的枚举,描述广播包层面可识别的设备品类:
typedef NS_ENUM(UInt8,JL_AdvType) {
JL_AdvTypeSoundBox = 0, //音箱类型
JL_AdvTypeChargingBin = 1, //充电仓类型
JL_AdvTypeTWS = 2, //TWS耳机类型
JL_AdvTypeHeadset = 3, //普通耳机类型
JL_AdvTypeSoundCard = 4, //声卡类型
JL_AdvTypeWatch = 5, //手表类型
JL_AdvTypeTradition = 6, //传统设备类型
};
Source: JLAdvParse.h
该枚举取值紧凑(0-6),适合作为解析结果的类型标签写入字典。注意它不含 Dongle 类型——Dongle 只在设备级枚举 JL_DeviceType 中出现。
JL_DeviceType 设备类型枚举
JL_DeviceType 定义在 JLAdvParse.h 第 25-34 行,基础类型为 NSInteger,语义上表示设备整体类型:
typedef NS_ENUM(NSInteger,JL_DeviceType) {
JL_DeviceTypeSoundBox = 0, //AI音箱类型
JL_DeviceTypeChargingBin = 1, //充电仓类型
JL_DeviceTypeTWS = 2, //TWS耳机类型
JL_DeviceTypeHeadset = 3, //普通耳机类型
JL_DeviceTypeSoundCard = 4, //声卡类型
JL_DeviceTypeWatch = 5, //手表类型
JL_DeviceTypeDongle = 6, //Dongle
JL_DeviceTypeTradition = -1, //传统设备类型
};
Source: JLAdvParse.h
两种枚举的设计差异(WHY)
| 维度 | JL_AdvType | JL_DeviceType |
|---|---|---|
| 基础类型 | UInt8(1 字节) | NSInteger(平台字长) |
| 品类覆盖 | 7 种(0-6),无 Dongle | 8 种(-1、0-6),含 Dongle |
| 传统设备取值 | 6(正数,可纳入位段/序列化) | -1(哨兵值,表示"无法归类") |
| 设计意图 | 广播包解析阶段的紧凑类型标签 | 设备管理阶段的完整类型判定 |
两个枚举并存并非冗余:广播包解析阶段,JL_AdvType 作为紧凑的 UInt8 标签,便于编码进厂商数据结构或作为字典键;而 JL_DeviceType 面向设备管理语义,用 -1 作为"传统设备"哨兵、用 6 表示 Dongle,避免与广播标签语义混淆。App 在拿到解析结果后,应依据业务场景选择对应枚举做类型判断。
JLAdvParse 入口 API 详解
JLAdvParse 是框架唯一对外暴露的入口类(JLAdvParse.h 第 38 行起),全部为类方法。
版本查询
/// 打印当前SDK的版本
+(void)sdkVersion;
Source: JLAdvParse.h
+sdkVersion 在控制台打印框架版本,用于集成排查。版本号同时以 JL_AdvParseVersionNumber(double)与 JL_AdvParseVersionString(const unsigned char[])两个符号导出(JL_AdvParse.h 第 12-15 行)。
广播包解析(基础入口)
/// 解析设备广播包内容
/// @param key 过滤码(只针对传统设备生效,默认填空即可)
/// @param advertData 蓝牙广播字典
+(NSDictionary*)bluetoothAdvParse:(NSData *_Nullable)key AdvData:(NSDictionary*_Nonnull)advertData;
Source: JLAdvParse.h
设计要点:
advertData为_Nonnull——调用方必须传入 CoreBluetooth 扫描回调(didDiscoverPeripheral)中的advertisementData字典,该字典包含kCBAdvDataManufacturerData、kCBAdvDataLocalName、kCBAdvDataServiceUUIDs等键;key为_Nullable过滤码,仅对传统设备(JL_AdvTypeTradition)生效。传统设备没有标准厂商 ID 头,需要靠业务方传入自定义匹配码来识别;对现代品类设备传nil(默认填空)即可;- 返回
NSDictionary*:解析成功时返回包含设备类型、厂商数据等结构化信息的字典;无法识别时返回空字典(非 nil),调用方需自行判空。
广播包解析(含 RSSI)
/// 解析蓝牙广播包
/// @param advertData 蓝牙广播报字典
/// @param rssi 蓝牙信号强度
+(NSDictionary *)bluetoothAdvData:(NSDictionary *)advertData RSSI:(NSNumber *)rssi;
Source: JLAdvParse.h
该重载在基础解析之上附加 rssi(信号强度,NSNumber)。设计意图是:扫描阶段 App 往往需要"解析结果 + 信号强度"一起做连接决策(如就近选机、自动回连),一次调用同时拿到两者可减少重复扫描与状态同步成本。rssi 同样来自 didDiscoverPeripheral 回调的 RSSI 参数。
OTA 回连辅助方法
+ (NSString * _Nullable)otaBleMacAddressFromCBAdvDataManufacturerData:(NSData *)kCBAdvDataManufacturerData;
+ (Boolean)otaBleMacAddress:(NSString *)otaBleMacAddress isEqualToCBAdvDataManufacturerData:(NSData *)kCBAdvDataManufacturerData;
Source: JLAdvParse.h
这两个方法专门服务于 OTA 升级回连场景:升级过程中设备会重新上电并广播,其广播包 kCBAdvDataManufacturerData 中带有 JLOTA 标识及蓝牙地址。App 在扫描到新广播时,可以用:
otaBleMacAddressFromCBAdvDataManufacturerData:从厂商数据中提取JLOTA标识对应的蓝牙地址字符串;提取不到时返回nil;otaBleMacAddress:isEqualToCBAdvDataManufacturerData:判断"OTA 升级中设备的蓝牙地址"是否与当前广播包厂商数据中的地址一致,返回Boolean(true/false),用于精确匹配回连目标。
这一对方法将"OTA 重连目标识别"这一高频易错逻辑收敛进框架,避免各业务 App 重复实现厂商数据字节解析。
设备类型子模块
伞头文件 JL_AdvParse.h 按品类导入了 8 个数据模型头文件:
| 头文件 | 对应品类 | 与枚举的映射 |
|---|---|---|
JLDevicesAdv.h | 通用设备广播数据基类/通用字段 | 各品类共用 |
JLTwsAdv.h | TWS 真无线耳机 | JL_AdvTypeTWS / JL_DeviceTypeTWS |
JLSoundBoxAdv.h | AI 音箱 | JL_AdvTypeSoundBox / JL_DeviceTypeSoundBox |
JLSoundCardAdv.h | 声卡 | JL_AdvTypeSoundCard / JL_DeviceTypeSoundCard |
JLChargingBoxAdv.h | 充电仓 | JL_AdvTypeChargingBin / JL_DeviceTypeChargingBin |
JLEarphoneAdv.h | 普通耳机 | JL_AdvTypeHeadset / JL_DeviceTypeHeadset |
JLWatchAdv.h | 手表 | JL_AdvTypeWatch / JL_DeviceTypeWatch |
JLOtaAdv.h | OTA 广播数据 | 供回连辅助方法使用 |
这些子模块各自封装对应品类的厂商数据布局(电量、名称、左右耳状态、配对状态等字段),由 JLAdvParse 按识别出的类型分发解析。各子模块的字段级定义位于各自头文件中,本仓库以编译后二进制交付,字段明细请以 XCFramework 内对应 Headers/*.h 为准。
核心解析流程
广播解析时序
sequenceDiagram
participant App as App (CBCentralManager)
participant JL as JLAdvParse
participant Sub as 设备类型子模块
participant Dict as NSDictionary 结果
App->>JL: bluetoothAdvParse:key AdvData:advertData
activate JL
JL->>JL: 读取 kCBAdvDataManufacturerData 厂商数据
JL->>JL: 判定设备类型 (JL_AdvType 0-6)
alt 传统设备 (JL_AdvTypeTradition)
JL->>JL: 用 key 过滤码匹配识别
end
JL->>Sub: 按类型分发到对应子模块
Sub-->>JL: 品类字段结构化结果
JL-->>Dict: 返回解析字典
deactivate JL
App->>JL: bluetoothAdvData:advertData RSSI:rssi
activate JL
JL-->>App: 附带 RSSI 的解析字典
deactivate JL
App->>JL: otaBleMacAddressFromCBAdvDataManufacturerData:data
activate JL
JL-->>App: JLOTA 标识对应的蓝牙地址 或 nil
deactivate JL
设备类型判定流程
flowchart TD
Start([扫描到广播]) --> Data["advertData 字典"]
Data --> HasMfr{"kCBAdvDataManufacturerData<br/>存在?"}
HasMfr -->|"否"| Unknown["返回空字典<br/>无法识别类型"]
HasMfr -->|"是"| OtaMark{"含 JLOTA 标识?"}
OtaMark -->|"是"| Ota["提取 OTA 蓝牙地址<br/>otaBleMacAddress 系列"]
OtaMark -->|"否"| Classify{"厂商数据判定类型"}
Classify -->|"0"| SoundBox["SoundBox 音箱"]
Classify -->|"1"| Charging["ChargingBin 充电仓"]
Classify -->|"2"| Tws["TWS 耳机"]
Classify -->|"3"| Headset["普通耳机"]
Classify -->|"4"| SoundCard["声卡"]
Classify -->|"5"| Watch["手表"]
Classify -->|"6 / 无法判定"| Tradition{"传统设备?"}
Tradition -->|"是"| KeyMatch{"key 过滤码匹配?"}
KeyMatch -->|"匹配"| Trad["传统设备解析成功"]
KeyMatch -->|"不匹配"| Unknown
Tradition -->|"否"| Unknown
SoundBox --> Done["按品类返回结构化字典"]
Charging --> Done
Tws --> Done
Headset --> Done
SoundCard --> Done
Watch --> Done
Trad --> Done
流程要点:
- 厂商数据优先:解析的核心输入是
kCBAdvDataManufacturerData,缺少该键时无法完成类型判定,返回空字典——这解释了为什么advertData被标记为_Nonnull; - 类型分发:框架从厂商数据字节中判定
JL_AdvType,再路由到对应子模块做字段级解析; - 传统设备兜底:无法从厂商数据判定标准品类时,进入传统设备分支,依赖调用方传入的
key过滤码完成匹配——这是key参数"只针对传统设备生效"的原因; - OTA 特判:
JLOTA标识的厂商数据不走品类解析,而是走回连地址提取路径,保证升级场景的快速重连。
使用示例
说明:
JL_AdvParse以二进制 XCFramework 交付,本仓库未包含 Demo 调用点(对bluetoothAdvParse/bluetoothAdvData的.m/.mm/.swift引用检索无结果)。以下示例取自框架公开头文件本身的 API 声明,调用方式按头文件注释契约说明。
基础广播解析(扫描回调中)
在 CBCentralManagerDelegate 的 didDiscoverPeripheral 回调中,将系统提供的广播字典直接传入:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
RSSI:(NSNumber *)RSSI
{
// key 仅对传统设备生效,现代品类设备传 nil 即可
NSDictionary *advInfo = [JLAdvParse bluetoothAdvParse:nil
AdvData:advertisementData];
// advInfo 包含设备类型等信息,nil 判断由调用方处理
if (advInfo.count > 0) {
// 按 JL_AdvType / JL_DeviceType 枚举值处理不同品类
}
}
对应入口声明见 JLAdvParse.h。
携带 RSSI 的解析(连接决策场景)
// 同时获得解析结果与信号强度,用于就近选机或自动回连
NSDictionary *advData = [JLAdvParse bluetoothAdvData:advertisementData
RSSI:RSSI];
对应入口声明见 JLAdvParse.h。
OTA 升级回连匹配
// 1) 从新扫描到的厂商数据中提取 JLOTA 标识的蓝牙地址
NSString *mac = [JLAdvParse otaBleMacAddressFromCBAdvDataManufacturerData:
advertisementData[CBAdvertisementDataManufacturerDataKey]];
// mac 可能为 nil(非 OTA 广播)
// 2) 与正在 OTA 的设备地址精确比对,决定是否发起回连
Boolean matched = [JLAdvParse otaBleMacAddress:otaDeviceMac
isEqualToCBAdvDataManufacturerData:
advertisementData[CBAdvertisementDataManufacturerDataKey]];
对应声明见 JLAdvParse.h。
API 参考
+ (void)sdkVersion
打印当前框架版本到控制台。无参数、无返回值。用于集成与排障。
+ (NSDictionary *)bluetoothAdvParse:(NSData * _Nullable)key AdvData:(NSDictionary * _Nonnull)advertData
解析设备广播包内容(基础入口)。
参数:
key(NSData?):过滤码,仅对传统设备生效,默认传nil;advertData(NSDictionary,非空):CoreBluetooth 广播字典(advertisementData)。
返回: 结构化解析结果字典;无法识别时返回空字典(调用方需判空)。
约束:
advertData违反_Nonnull契约(传nil)属未定义行为,应避免。
+ (NSDictionary *)bluetoothAdvData:(NSDictionary *)advertData RSSI:(NSNumber *)rssi
解析蓝牙广播包并附加信号强度。
参数:
advertData(NSDictionary):蓝牙广播字典;rssi(NSNumber):信号强度(来自扫描回调的RSSI)。
返回: 附带 RSSI 信息的解析结果字典。
+ (NSString * _Nullable)otaBleMacAddressFromCBAdvDataManufacturerData:(NSData *)kCBAdvDataManufacturerData
从广播包 kCBAdvDataManufacturerData 中提取 JLOTA 标识的蓝牙地址。
参数:
kCBAdvDataManufacturerData(NSData):广播包厂商数据。
返回: 蓝牙地址字符串;非 OTA 广播或提取失败时返回 nil。
+ (Boolean)otaBleMacAddress:(NSString *)otaBleMacAddress isEqualToCBAdvDataManufacturerData:(NSData *)kCBAdvDataManufacturerData
判断给定蓝牙地址是否等于广播包厂商数据中的地址。
参数:
otaBleMacAddress(NSString):OTA 升级中设备的蓝牙地址;kCBAdvDataManufacturerData(NSData):广播包厂商数据。
返回: true(匹配)/ false(不匹配或数据缺失)。
失败模式与边界情况
基于公开 API 契约,以下边界情况需要调用方特别注意:
| 场景 | 表现 | 处理建议 |
|---|---|---|
advertData 为 nil | 违反 _Nonnull 契约,行为未定义 | 扫描回调中系统保证非空;自行构造字典时先判空 |
广播包无 kCBAdvDataManufacturerData 键 | 无法判定类型,返回空字典 | 用 advInfo.count > 0 判空后再使用结果 |
| 非杰理设备的广播 | 类型无法识别,走传统设备兜底 | 不传 key 时大概率返回空字典,属预期行为 |
传统设备未传 key 或 key 不匹配 | 匹配失败,返回空字典 | 按头文件注释"默认填空即可",仅传统设备场景才需传入过滤码 |
非 OTA 广播调用 otaBleMacAddressFrom...: | 返回 nil | 调用方必须先判空再使用返回值 |
otaBleMacAddress:isEqualTo...: 输入地址与厂商数据地址不一致 | 返回 false | 用于精确匹配回连目标,比对失败不应直接断连现有链路 |
| 模拟器/平台切片不匹配 | 链接失败或运行时崩溃 | 按运行目标选择 ios-arm64(真机)、ios-arm64_x86_64-simulator(模拟器)或 macos-arm64_x86_64 切片 |
并发与线程安全
- 无状态设计:
JLAdvParse全部为类方法,无实例变量、无可变共享状态,因此天然线程安全——可在CBCentralManagerDelegate回调线程(不一定为主线程)中直接调用,无需加锁; - 高频调用友好:扫描回调可能以较高频率触发,解析为纯内存字典操作,无 I/O 阻塞,可放心在回调中同步调用;
- 调用方职责:返回的
NSDictionary若被多个线程共享使用,仍由调用方自行保证读写互斥(与框架无关)。
性能与运维
- 时延:广播解析是纯 CPU 内存操作,单次开销极小(微秒级),扫描回调中同步调用不会造成明显卡顿;
- 内存:解析结果仅为小字典对象,无缓存、无长生命周期对象,不会产生内存增长问题;
- 日志:框架依赖
JLLogHelper(见 JLAdvParse.h 第 10 行),集成时需同时引入该日志组件,否则链接失败; - 版本核对:通过
+sdkVersion或导出符号JL_AdvParseVersionNumber/JL_AdvParseVersionString确认集成版本,避免与固件广播格式不匹配导致解析异常; - 分发:以 XCFramework 形式同时覆盖 iOS 真机、模拟器与 macOS,配合 Swift Package / CocoaPods 均可集成。
扩展点
- 品类子模块扩展:伞头文件(JL_AdvParse.h)按品类导入
JLDevicesAdv、JLTwsAdv、JLSoundBoxAdv、JLSoundCardAdv、JLChargingBoxAdv、JLEarphoneAdv、JLWatchAdv、JLOtaAdv,新品类设备接入时可在对应子模块中扩展字段解析; - 过滤码机制:
key参数为传统设备提供业务侧自定义匹配入口,适配非标准广播格式时无需改动框架; - 结果字典桥接:统一
NSDictionary返回便于与KVC、JSON 序列化、Swift 桥接无缝衔接,业务层可在此基础上二次封装为强类型模型; - OTA 回连协议:
JLOTA标识与地址提取逻辑封装在框架内,固件侧若调整标识字节,仅需升级框架版本,业务代码无需变更。
相关链接
- 框架伞头文件:JL_AdvParse.h
- 核心解析 API:JLAdvParse.h
- 相关页面:蓝牙连接管理(
JL_BLE)、设备管理(JL_Manager)、OTA 升级(JL_OT)——详见 SDK 框架目录下对应文档