iOS后台应用被推送唤醒后通过Core Bluetooth与其他设备交换数据如何实现?
解决方案
核心问题原因
你当前的实现存在三个关键错误导致连接失败:
- 初始化蓝牙管理器后立刻调用了
completionHandler,系统会立即将应用挂起,没有留给蓝牙扫描、广播、连接的执行时间 - CoreBluetooth管理器初始化后需要等待状态更新回调确认硬件可用后,才能执行扫描、广播操作,你当前没有等待状态就绪就结束了流程
- 后台模式下蓝牙扫描、广播有特殊限制,未按规则配置会导致操作无效
修复步骤
1. 推迟completionHandler调用,申请后台运行时长
静默推送唤醒应用后系统仅提供约30秒的运行时间,你需要申请后台任务断言延长运行时间,同时持有completionHandler直到连接流程结束或超时再调用。
2. 监听CoreBluetooth状态就绪后再执行操作
必须等centralManagerDidUpdateState、peripheralManagerDidUpdateState回调返回poweredOn状态后,再开启扫描和广播。
3. 适配后台蓝牙规则
- 后台广播仅支持携带服务UUID,不能添加自定义广播数据、本地名称等字段
- 后台扫描必须明确指定要扫描的服务UUID,不能扫描全量设备
4. 项目配置检查
- 打开
Background Modes能力,勾选Uses Bluetooth LE accessories(Central角色)和Acts as a Bluetooth LE accessory(Peripheral角色) - 后端推送必须为静默推送,payload包含
"content-available": 1字段
代码示例
首先声明全局持有属性:
// 后台任务标识 var bleBackgroundTaskID: UIBackgroundTaskIdentifier = .invalid // 持有推送完成回调 var pushCompletionHandler: ((UIBackgroundFetchResult) -> Void)?
修改推送回调逻辑:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // 持有回调,流程结束再触发 pushCompletionHandler = completionHandler // 申请后台任务,最多可获得3分钟运行时间 bleBackgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "BLE_Connection_Task") { [weak self] in guard let self = self else { return } // 后台时间耗尽,结束流程 self.pushCompletionHandler?(.failed) UIApplication.shared.endBackgroundTask(self.bleBackgroundTaskID) self.bleBackgroundTaskID = .invalid } // 初始化蓝牙管理器,带上状态恢复标识 localPeripheralManager = PeripheralManager( delegate: self, queue: nil, options: [CBPeripheralManagerOptionRestoreIdentifierKey: "YourPeripheralRestoreID"] ) localCentralmanager = CentrallManager( delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: "YourCentralRestoreID"] ) }
实现Peripheral状态回调:
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { guard peripheral.state == .poweredOn else { return } // 后台广播仅可携带服务UUID let advertiseConfig = [ CBAdvertisementDataServiceUUIDsKey: [CBUUID(string: "你的固定服务UUID")] ] peripheral.startAdvertising(advertiseConfig) }
实现Central状态回调:
func centralManagerDidUpdateState(_ central: CBCentralManager) { guard central.state == .poweredOn else { return } // 后台扫描必须指定服务UUID central.scanForPeripherals( withServices: [CBUUID(string: "你的固定服务UUID")], options: [CBCentralManagerScanOptionAllowDuplicatesKey: false] ) }
连接完成、数据交换结束后触发回调:
// 数据交换成功后执行 pushCompletionHandler?(.newData) UIApplication.shared.endBackgroundTask(bleBackgroundTaskID) bleBackgroundTaskID = .invalid
内容的提问来源于stack exchange,提问作者Kalpesh Panchasara
相关产品推荐
相关产品推荐

