BLE广播包完整结构详解:Advertising Data与Scan Response字段深度解析
在BLE(低功耗蓝牙)开发中,广播包是设备实现“被动发现”的核心载体——智能硬件、工业模块、可穿戴设备等,均需通过广播包向周边扫描设备(手机、网关等)传递自身身份与基础信息,供其识别、筛选并建立连接。广播包的核心组成是Advertising Data(广播数据)与Scan Response(扫描响应数据),二者结构遵循GAP协议规范,但交互逻辑、使用场景存在明确差异,也是开发中“设备扫不到”“数据
在BLE(低功耗蓝牙)开发中,广播包是设备实现“被动发现”的核心载体——智能硬件、工业模块、可穿戴设备等,均需通过广播包向周边扫描设备(手机、网关等)传递自身身份与基础信息,供其识别、筛选并建立连接。广播包的核心组成是Advertising Data(广播数据)与Scan Response(扫描响应数据),二者结构遵循GAP协议规范,但交互逻辑、使用场景存在明确差异,也是开发中“设备扫不到”“数据解析异常”“自定义字段失效”等问题的核心诱因。
本文将从底层协议规范出发,拆解两个字段的核心结构、AD单元定义及高频类型,结合iOS(Swift)、Android(Kotlin)两种主流原生技术栈的实战代码,聚焦字段配置、解析逻辑、权限适配及避坑要点,兼顾理论严谨性与实战可复用性,助力开发者彻底掌握BLE广播包开发的核心逻辑,避开常见踩坑点。
一、前置认知:BLE广播包的核心规范(必记)
BLE广播包由广播者(Advertiser)周期性发送,遵循GAP(通用访问规范)协议约束,核心作用是传递设备基础信息,无需扫描者主动请求即可被动接收。其核心规范直接决定广播包的有效性,需重点掌握:
-
长度约束(硬性):Advertising Data与Scan Response的总长度均不得超过31字节,超出会直接导致广播失败(蓝牙芯片会拒绝发送违规数据包)。
-
结构统一:二者均由若干个“AD单元”组成,单个AD单元严格遵循「1字节Length + 1字节AD Type + N字节AD Data」的结构,无例外。
-
交互逻辑:Advertising Data为必选字段,广播者主动周期性发送;Scan Response为可选字段,仅在扫描者发送Scan Request后被动响应,用于承载非核心补充信息。
-
单元约束:单个广播包的AD单元数量建议不超过5个(部分iOS设备及低功耗蓝牙芯片有明确限制),避免单元过多导致解析异常。
补充:Advertising Data与Scan Response的长度独立计算,互不占用对方字节空间,可根据需求拆分核心与非核心信息,避免单字段超出31字节限制。
二、核心拆解:AD单元结构与高频类型详解
AD单元是Advertising Data与Scan Response的最小组成单元,三者(Length、AD Type、AD Data)的配合直接决定数据的有效性与可解析性,以下结合实战场景拆解各部分核心要点。
(一)Length:AD单元的尺寸标识(1字节)
Length字段的核心作用是标识当前AD单元中「AD Type + AD Data」的总字节数,取值范围为1~30(十进制),核心计算逻辑为:
$$\\text{Length} = 1\\ (\\text{AD Type字节数}) + N\\ (\\text{AD Data字节数})$$,即 $$N = \\text{Length} - 1$$
避坑要点:Length不可为0(无意义),也不可超过30(否则AD单元总长度会超出广播包31字节限制);多AD单元拼接时,需确保所有单元总长度≤31字节。
示例:AD Type=0x09(完整设备名称),AD Data为“Smart-Band”(7字节),则Length=1+7=8(十进制0x08),AD单元为「0x08 + 0x09 + 0x53 0x6D 0x61 0x72 0x74 0x2D 0x42 0x61 0x6E 0x64」。
(二)AD Type:数据的身份标识(1字节)
AD Type取值为0x00~0xFF(十六进制),由BLE协议规范定义,用于指定后续AD Data的含义与格式,开发中高频使用的类型(按使用率排序)如下,精准对应实战场景:
|
AD Type(十六进制) |
类型名称 |
实战作用 |
AD Data格式要求 |
|
0x03 |
Complete List of 16-bit Service UUIDs |
核心筛选字段,用于扫描者精准匹配目标设备 |
2字节/个UUID,多个连续拼接(如0xFFE0对应UUID:0000FFE0-0000-1000-8000-00805F9B34FB) |
|
0x09 |
Complete Local Name |
设备直观标识,便于用户识别(如“Smart-Watch-01”) |
UTF-8编码字符串,长度≤30字节(需结合Length计算) |
|
0x0A |
Manufacturer Specific Data |
厂商自定义字段,传递固件版本、序列号等私有信息 |
前2字节为蓝牙SIG分配的厂商ID,后续为自定义数据 |
|
0x11 |
Tx Power Level |
辅助判断设备距离,扫描者根据功率计算信号衰减 |
1字节,取值范围-40~+4 dBm(十进制) |
|
0x01 |
Flags |
标识设备基础属性,决定是否可连接、是否支持BLE |
1字节,常用值0x06(可连接、仅支持BLE,不支持经典蓝牙) |
(三)AD Data:核心信息载体(N字节)
AD Data的格式、长度完全由对应AD Type决定,是广播包的核心内容,结合高频AD Type给出实战示例(直接复用):
-
AD Type=0x09(完整设备名称):Length=8,AD Data=0x53 0x6D 0x61 0x72 0x74 0x2D 0x42 0x61 0x6E 0x64(对应“Smart-Band”);
-
AD Type=0x03(16位服务UUID):Length=3,AD Data=0xFF 0xE0(对应服务UUID:0000FFE0-0000-1000-8000-00805F9B34FB);
-
AD Type=0x0A(厂商自定义数据):Length=5,AD Data=0x00 0x01 0x02 0x05(厂商ID=0x0001,固件版本V2.05);
-
AD Type=0x11(发射功率):Length=2,AD Data=0x00(对应发射功率0 dBm)。
(四)Advertising Data与Scan Response的核心差异(实战必区分)
二者结构完全一致,但交互逻辑、使用场景差异显著,直接影响开发中的字段分配,具体对比如下:
|
对比维度 |
Advertising Data(广播数据) |
Scan Response(扫描响应数据) |
|
交互方式 |
主动周期性发送,扫描者被动接收,无需请求 |
被动响应,仅在扫描者发送Scan Request后触发 |
|
核心作用 |
传递核心信息(设备名称、服务UUID),供扫描者快速筛选 |
传递补充信息(固件版本、序列号),节省广播数据空间 |
|
必要性 |
必选(无则设备无法被扫描发现) |
可选(无补充信息可不配,不影响设备发现) |
|
优先级 |
高(优先分配字节,确保核心信息传递) |
低(仅在广播数据有剩余需求时配置) |
三、实战落地:iOS、Android原生完整实现(可直接复用)
结合协议规范,以下给出iOS(Swift)、Android(Kotlin)两种原生技术栈的广播者(配置)与扫描者(解析)完整代码,聚焦实战细节、权限适配及异常处理,规避常见坑点,代码可直接复制到项目中联动调试。
(一)iOS 原生(Swift):基于CoreBluetooth框架
iOS通过CoreBluetooth框架封装BLE底层接口,不允许手动拼接AD单元,需通过框架提供的键值对配置,底层自动遵循GAP协议,核心需关注权限配置与状态监听。
1. 广播者:Advertising Data与Scan Response配置
import CoreBluetooth
class iOSBLEAdvertiser: NSObject, CBPeripheralManagerDelegate {
// 广播核心管理器,负责广播配置与状态回调
private var peripheralManager: CBPeripheralManager!
// 广播数据与扫描响应数据(严格遵循31字节限制)
private var advertisingData: [String: Any] = [:]
private var scanResponseData: [String: Any] = [:]
// 目标服务UUID(与扫描者统一,确保精准匹配)
private let serviceUUID = CBUUID(string: "0000FFE0-0000-1000-8000-00805F9B34FB")
override init() {
super.init()
// 初始化广播管理器,主线程回调(确保UI更新安全)
peripheralManager = CBPeripheralManager(delegate: self, queue: DispatchQueue.main)
}
/// 配置广播包(核心:按AD Type对应框架键值对,避免手动拼接)
private func configAdvertising() {
// 1. 配置Advertising Data(必选,核心信息)
advertisingData = [
CBAdvertisementDataLocalNameKey: "iOS-BLE-Test", // AD Type=0x09(设备名称)
CBAdvertisementDataServiceUUIDsKey: [serviceUUID], // AD Type=0x03(服务UUID)
CBAdvertisementDataTxPowerLevelKey: 0 as NSNumber, // AD Type=0x11(发射功率0dBm)
CBAdvertisementDataFlagsKey: 0x06 // AD Type=0x01(可连接、仅支持BLE)
]
// 2. 配置Scan Response(可选,补充厂商自定义数据)
let manufacturerID: UInt16 = 0x0001 // 蓝牙SIG分配厂商ID
let firmwareVersion = Data([0x01, 0x02]) // 自定义固件版本V1.02
// 厂商数据格式:前2字节厂商ID + 后续自定义数据(AD Type=0x0A)
let manufacturerData = Data(bytes: &manufacturerID, count: MemoryLayout<UInt16>.size) + firmwareVersion
scanResponseData = [CBAdvertisementDataManufacturerDataKey: manufacturerData]
}
/// 启动广播(需先校验蓝牙状态与权限)
func startAdvertising() {
guard peripheralManager.state == .poweredOn else {
print("蓝牙未开启,无法启动广播")
return
}
configAdvertising()
// 启动广播,传入广播数据与扫描响应数据
peripheralManager.startAdvertising(advertisingData)
}
/// 停止广播,释放资源
func stopAdvertising() {
if peripheralManager.isAdvertising {
peripheralManager.stopAdvertising()
print("iOS BLE广播已停止")
}
}
// MARK: - CBPeripheralManagerDelegate
/// 蓝牙状态变化回调(核心:监听蓝牙开关状态)
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.state {
case .poweredOn:
startAdvertising()
print("iOS BLE广播启动成功,配置:\(advertisingData)")
case .poweredOff:
stopAdvertising()
print("蓝牙已关闭,广播停止")
case .unauthorized:
print("蓝牙权限未授予,无法启动广播")
default:
print("广播不可用,状态:\(peripheral.state.rawValue)")
}
}
/// 广播启动失败回调(捕捉字段配置错误,如长度超出)
func peripheralManager(_ peripheral: CBPeripheralManager, didFailToStartAdvertising error: Error?) {
if let error = error {
print("广播启动失败(配置异常):\(error.localizedDescription)")
}
}
}
// 使用示例
// let advertiser = iOSBLEAdvertiser()
2. 扫描者:Advertising Data与Scan Response解析
import CoreBluetooth
class iOSBLEScanner: NSObject, CBCentralManagerDelegate {
private var centralManager: CBCentralManager!
// 目标服务UUID(与广播者统一,实现精准筛选)
private let targetServiceUUID = CBUUID(string: "0000FFE0-0000-1000-8000-00805F9B34FB")
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
/// 启动扫描(校验蓝牙状态、权限,精准筛选目标设备)
func startScanning() {
guard centralManager.state == .poweredOn else {
print("蓝牙未开启,无法启动扫描")
return
}
// iOS 13+ 需始终允许蓝牙权限(否则无法扫描到设备)
guard centralManager.authorization == .allowedAlways else {
centralManager.requestAlwaysAuthorization()
print("请授予蓝牙始终允许权限")
return
}
// 仅扫描携带目标服务UUID的设备,提升扫描效率
centralManager.scanForPeripherals(withServices: [targetServiceUUID], options: nil)
print("iOS BLE扫描启动,开始解析广播包...")
}
/// 停止扫描,避免资源浪费
func stopScanning() {
if centralManager.isScanning {
centralManager.stopScan()
print("iOS BLE扫描已停止")
}
}
// MARK: - CBCentralManagerDelegate
/// 发现设备回调(核心:解析广播包数据)
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("=== 发现设备:\(peripheral.name ?? "未知设备")(地址:\(peripheral.identifier.uuidString))===")
// 1. 解析Advertising Data(核心基础信息)
// 设备名称(AD Type=0x09,对应CBAdvertisementDataLocalNameKey)
if let deviceName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
print("Advertising Data - 设备名称:\(deviceName)")
}
// 服务UUID(AD Type=0x03,对应CBAdvertisementDataServiceUUIDsKey)
if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
let uuidStr = serviceUUIDs.map { $0.uuidString }.joined(separator: ", ")
print("Advertising Data - 服务UUID:\(uuidStr)")
}
// 发射功率(AD Type=0x11,对应CBAdvertisementDataTxPowerLevelKey)
if let txPower = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber {
print("Advertising Data - 发射功率:\(txPower) dBm")
}
// 2. 解析Scan Response(补充信息,厂商自定义数据)
if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data {
// 前2字节为厂商ID,后续为自定义数据(AD Type=0x0A)
guard manufacturerData.count > 2 else {
print("Scan Response - 厂商数据格式错误")
return
}
let manufacturerID = manufacturerData.subdata(in: 0..<2).withUnsafeBytes { $0.load(as: UInt16.self) }
let firmwareData = manufacturerData.subdata(in: 2..<manufacturerData.count)
let firmwareVersion = "V\(firmwareData[0]).\(firmwareData[1])"
print("Scan Response - 厂商ID:\(manufacturerID),固件版本:\(firmwareVersion)")
}
// 信号强度(辅助判断设备距离,负值越小信号越强)
print("设备RSSI:\(RSSI) dBm\n")
}
/// 蓝牙状态变化回调
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
startScanning()
} else {
stopScanning()
}
}
}
// 使用示例
// let scanner = iOSBLEScanner()
iOS实战避坑要点
-
权限配置:Info.plist必须添加「NSBluetoothAlwaysUsageDescription」(始终允许蓝牙权限),否则无法通过App Store审核,且广播/扫描会失败。
-
字段限制:禁止手动拼接AD单元字节,必须使用CoreBluetooth提供的键值对(如CBAdvertisementDataLocalNameKey对应AD Type=0x09),否则解析异常。
-
长度管控:设备名称过长(超过20字节)时,建议拆分至Scan Response,避免Advertising Data超出31字节限制。
(二)Android 原生(Kotlin):基于BluetoothLeAdvertiser
Android对BLE广播的灵活性更高,支持Builder模式快速配置与手动拼接AD单元两种方式,核心需关注系统版本权限适配(Android 12+细粒度权限)与解析逻辑的严谨性。
1. 广播者:Advertising Data与Scan Response配置
import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.BluetoothLeAdvertiser
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
class AndroidBLEAdvertiser(private val context: Context) {
// 蓝牙核心组件(适配器、广播管理器)
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
private var bleAdvertiser: BluetoothLeAdvertiser? = null
// 目标服务UUID(与扫描者统一)
private val serviceUUID = android.bluetooth.UUID.fromString("0000FFE0-0000-1000-8000-00805F9B34FB")
/// 初始化广播配置(校验蓝牙可用性、配置广播参数)
fun initAdvertiser() {
if (bluetoothAdapter == null) {
println("设备不支持BLE功能")
return
}
if (!bluetoothAdapter.isEnabled) {
println("蓝牙未开启,建议先开启蓝牙")
return
}
// 获取广播管理器(部分设备不支持BLE广播,需判空)
bleAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser ?: run {
println("设备不支持BLE广播功能")
return
}
}
/// 启动广播(核心:权限适配、字段配置、状态监听)
fun startAdvertising() {
if (bleAdvertiser == null) {
println("广播未初始化,请先调用initAdvertiser()")
return
}
// 权限适配(Android 12+ 拆分蓝牙权限,广播需单独申请BLUETOOTH_ADVERTISE)
val requiredPermission = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
Manifest.permission.BLUETOOTH_ADVERTISE
} else {
Manifest.permission.BLUETOOTH_ADMIN
}
if (ActivityCompat.checkSelfPermission(context, requiredPermission) != PackageManager.PERMISSION_GRANTED) {
println("缺少广播权限,无法启动广播")
return
}
// 1. 配置广播设置(与字段无关,控制广播功率、模式)
val advertiseSettings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED) // 平衡功耗与扫描效率
.setConnectable(true) // 可连接广播(允许扫描者发起连接)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) // 中等发射功率
.setTimeout(0) // 无超时,持续广播
.build()
// 2. 配置Advertising Data(Builder模式,简洁高效)
val advertisingData = AdvertiseData.Builder()
.setIncludeDeviceName(true) // 包含设备名称(AD Type=0x09)
.addServiceUuid(android.bluetooth.UUID(serviceUUID)) // AD Type=0x03(服务UUID)
.setIncludeTxPowerLevel(true) // 包含发射功率(AD Type=0x11)
.build()
// 3. 配置Scan Response(手动拼接AD单元,灵活自定义,AD Type=0x0A)
// 手动拼接格式:Length(0x05) + AD Type(0x0A) + 厂商ID(0x0001) + 固件版本(0x0304)
val scanResponseData = AdvertiseData.Builder()
.addManufacturerData(0x0001, byteArrayOf(0x03, 0x04)) // 厂商ID+固件版本V3.04
.build()
// 启动广播,监听回调
bleAdvertiser?.startAdvertising(advertiseSettings, advertisingData, scanResponseData, advertiseCallback)
}
/// 停止广播,释放资源
fun stopAdvertising() {
bleAdvertiser?.stopAdvertising(advertiseCallback)
println("Android BLE广播已停止")
}
// 广播状态回调(捕捉启动成功/失败)
private val advertiseCallback = object : AdvertiseCallback() {
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
super.onStartSuccess(settingsInEffect)
println("Android BLE广播启动成功")
}
override fun onStartFailure(errorCode: Int) {
super.onStartFailure(errorCode)
val errorMsg = when (errorCode) {
ADVERTISE_FAILED_DATA_TOO_LARGE -> "广播数据超出31字节限制(字段配置错误)"
ADVERTISE_FAILED_ALREADY_STARTED -> "广播已启动,无需重复调用"
ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> "设备不支持当前广播模式"
else -> "广播失败,错误码:$errorCode"
}
println("广播启动失败:$errorMsg")
}
}
}
// 使用示例
// val advertiser = AndroidBLEAdvertiser(context)
// advertiser.initAdvertiser()
// advertiser.startAdvertising()
2. 扫描者:Advertising Data与Scan Response解析
import android.Manifest
import android.bluetooth.BluetoothManager
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.ActivityCompat
class AndroidBLEScanner(private val context: Context) {
// 蓝牙核心组件(管理器、适配器、扫描器)
private val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val bluetoothAdapter = bluetoothManager.adapter
private val bleScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner
// 目标服务UUID(与广播者统一,精准筛选)
private val targetServiceUUID = android.bluetooth.UUID.fromString("0000FFE0-0000-1000-8000-00805F9B34FB")
/// 启动扫描(权限适配、蓝牙校验、筛选配置)
fun startScanning() {
if (bluetoothAdapter == null) {
println("设备不支持BLE功能")
return
}
if (!bluetoothAdapter.isEnabled) {
println("蓝牙未开启,无法启动扫描")
return
}
if (bleScanner == null) {
println("设备不支持BLE扫描功能")
return
}
// 权限适配(Android 12+ 需申请BLUETOOTH_SCAN,低版本需定位权限)
val requiredPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(Manifest.permission.BLUETOOTH_SCAN)
} else {
arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION)
}
if (requiredPermissions.any { ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED }) {
println("缺少扫描权限,无法启动扫描")
return
}
// 配置扫描过滤(仅扫描携带目标服务UUID的设备,提升效率)
val scanFilter = ScanFilter.Builder()
.setServiceUuid(android.bluetooth.UUID(targetServiceUUID))
.build()
val scanFilters = listOf(scanFilter)
// 启动扫描,解析广播包(回调在子线程,UI更新需切换主线程)
bleScanner.startScan(scanFilters, null, scanCallback)
println("Android BLE扫描启动,开始解析广播包...")
}
/// 停止扫描,释放资源
fun stopScanning() {
bleScanner?.stopScan(scanCallback)
println("Android BLE扫描已停止")
}
// 扫描回调(核心:解析广播包与扫描响应数据)
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
val device = result.device
println("=== 发现设备:${device.name ?: "未知设备"}(地址:${device.address})===")
// 1. 解析Advertising Data(result.advertisementData直接获取)
val advertisingData = result.advertisementData
// 设备名称(AD Type=0x09)
advertisingData.deviceName?.let {
println("Advertising Data - 设备名称:$it")
}
// 服务UUID(AD Type=0x03)
advertisingData.serviceUuids?.let { uuids ->
val uuidStr = uuids.map { it.uuid.toString() }.joinToString(", ")
println("Advertising Data - 服务UUID:$uuidStr")
}
// 发射功率(AD Type=0x11,Int.MIN_VALUE表示未配置)
if (advertisingData.txPowerLevel != Int.MIN_VALUE) {
println("Advertising Data - 发射功率:${advertisingData.txPowerLevel} dBm")
}
// 2. 解析Scan Response(result.scanRecord?.scanResponseData获取)
val scanResponseData = result.scanRecord?.scanResponseData
scanResponseData?.let { data ->
parseScanResponse(data)
}
// 信号强度(辅助判断设备距离)
println("设备RSSI:${result.rssi} dBm(负值越小,信号越强)\n")
}
/// 手动解析Scan Response的AD单元(适配自定义字段,通用逻辑)
private fun parseScanResponse(data: ByteArray) {
var index = 0
while (index < data.size) {
val length = data[index].toInt() // Length字段(AD Type + AD Data总长度)
// 无效AD单元(长度为0或超出数据范围),退出循环
if (length == 0 || index + length >= data.size) break
val type = data[index + 1].toInt() // AD Type字段
val adData = data.copyOfRange(index + 2, index + 1 + length) // AD Data字段
// 解析厂商自定义数据(AD Type=0x0A)
if (type == 0x0A) {
// 厂商数据格式:前2字节厂商ID + 后续自定义数据
if (adData.size >= 2) {
val manufacturerID = (adData[0].toInt() and 0xFF) or ((adData[1].toInt() and 0xFF) shl 8)
val firmwareVersion = "V${adData[2]}.${adData[3]}"
println("Scan Response - 厂商ID:$manufacturerID,固件版本:$firmwareVersion")
}
}
index += length + 1 // 移动到下一个AD单元
}
}
override fun onScanFailed(errorCode: Int) {
super.onScanFailed(errorCode)
println("扫描失败,错误码:$errorCode(1=蓝牙未开启,2=权限不足,3=设备不支持)")
}
}
}
// 使用示例
// val scanner = AndroidBLEScanner(context)
// scanner.startScanning()
Android实战避坑要点
-
权限适配:Android 12+ 拆分蓝牙权限,广播需申请「BLUETOOTH_ADVERTISE」,扫描需申请「BLUETOOTH_SCAN」;Android 10及以下扫描需额外申请「ACCESS_FINE_LOCATION」,否则无法扫描到设备。
-
配置方式:Builder模式适合常用字段(设备名称、服务UUID),手动拼接适合自定义AD Type,拼接时需严格遵循「Length+Type+Data」结构,避免字节错位。
-
解析逻辑:Scan Response需通过「result.scanRecord?.scanResponseData」获取,手动解析时需循环遍历AD单元,通过Length判断单元边界,避免遗漏或解析错误。
-
兼容性:Android 7.0及以下设备对广播包长度限制更严格,建议单个AD单元Length不超过20字节,避免系统拒绝发送。
四、总结:核心要点与实战建议
BLE广播包的核心是Advertising Data与Scan Response的合理配置与解析,二者遵循统一的AD单元结构,但分工明确——前者承载核心识别信息,后者补充私有补充信息,共同实现设备的“被动发现”与“信息传递”。
对于iOS开发者,需重点关注CoreBluetooth框架的规范使用,避免手动拼接AD单元,做好权限配置与状态监听;对于Android开发者,可灵活选择配置方式,重点处理权限适配与解析逻辑的严谨性,兼顾不同系统版本的兼容性。
实战中,建议优先将核心信息(服务UUID、设备名称)放入Advertising Data,非核心信息(固件版本、序列号)放入Scan Response,严格控制单字段长度不超过31字节,同时做好异常处理(广播失败、扫描失败),可大幅降低开发踩坑概率。
更多推荐




所有评论(0)