iOS/Swift下BLE后台运行异常 重连后通知无法正常接收问题
iOS BLE后台重连后无法接收通知的修复方案
现有代码的核心错误点
- 标识符拼写硬错误:
didFinishLaunchingWithOptions中判断的恢复标识符为coom.Imen.Identifierr,和初始化CBCentralManager时传入的com.Imen.Identifier完全不匹配,状态恢复逻辑根本不会触发。 - 初始化时序错误:在自定义BLE管理类的
init方法中直接调用scan(),此时CBCentralManager还未完成蓝牙上电流程(状态未进入.poweredOn),前台场景可能偶现正常,后台下这类提前发起的扫描、连接请求会直接被系统丢弃。 - 状态恢复逻辑错误:
willRestoreState回调中拿到系统恢复的外设列表后,重复执行peripherals.append(peripheral)往全局数组塞重复对象,且未判断外设当前连接状态就重复调用connect,后台场景下会触发系统BLE连接限流,导致重连失败。 - 权限请求逻辑错误:把通知权限请求和
CBCentralManager初始化绑定,若用户未及时完成授权,后续重连成功触发的本地通知会被系统直接拦截。 - 对系统启动参数的认知错误:
launchOptions中UIApplication.LaunchOptionsKey.bluetoothCentrals字段,仅在应用被系统杀死后、因蓝牙事件被后台唤醒的场景下才会有值;应用仅退到后台未被杀死时该字段本身就是nil,不需要强依赖该字段做BLE实例初始化。
正确实现步骤
1. 修正CBCentralManager初始化逻辑
全局只保留一个CBCentralManager实例,不要在启动方法中重复创建;所有扫描、连接操作必须等蓝牙状态进入.poweredOn后再执行,参考代码:
class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate { static let shared = BLEManager() var central: CBCentralManager! var storedPeripherals = [CBPeripheral]() // 替换成你自己设备对应的服务UUID private let targetServiceUUID = CBUUID(string: "你的设备服务UUID") private override init() { super.init() central = CBCentralManager( delegate: self, queue: .main, options: [CBCentralManagerOptionRestoreIdentifierKey: "com.Imen.Identifier"] ) } func centralManagerDidUpdateState(_ central: CBCentralManager) { switch central.state { case .poweredOn: // 蓝牙上电完成后再执行操作 if storedPeripherals.isEmpty { startScan() } else { storedPeripherals.forEach { peripheral in peripheral.delegate = self // 仅对未连接的外设发起连接 if peripheral.state != .connected { central.connect(peripheral) } else { peripheral.discoverServices([targetServiceUUID]) } } } default: // 自行处理蓝牙未开启、未授权等异常状态 break } } func startScan() { // 后台扫描必须指定目标服务UUID,传nil会被系统直接拦截扫描请求 central.scanForPeripherals(withServices: [targetServiceUUID], options: nil) } }
2. 修正状态恢复回调逻辑
willRestoreState中不要重复添加外设对象,系统恢复的外设不需要重复发起连接,仅需重新绑定代理、恢复服务监听即可:
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { guard let restoredPeripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else { startScan() return } for peripheral in restoredPeripherals { peripheral.delegate = self storedPeripherals.append(peripheral) if peripheral.state == .connected { peripheral.discoverServices([targetServiceUUID]) } } }
3. 修正App启动逻辑
不需要在didFinishLaunchingWithOptions中重复创建BLE实例,仅需尽早初始化单例保证状态恢复时实例已存在即可;通知权限请求和BLE逻辑解耦:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 尽早初始化BLE单例,不要等页面加载完成后再初始化 _ = BLEManager.shared // 单独处理通知权限请求 UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in if !granted { print("用户拒绝了通知权限") } } return true }
4. 后台重连成功后的通知触发
在连接成功回调中判断应用状态,非前台状态下触发本地通知:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { // 连接成功后停止扫描,减少后台功耗 central.stopScan() peripheral.delegate = self peripheral.discoverServices([targetServiceUUID]) // 非前台状态下推送本地通知 if UIApplication.shared.applicationState != .active { let content = UNMutableNotificationContent() content.title = "BLE设备已连接" content.body = "成功连接到设备:\(peripheral.name ?? "未知设备")" content.sound = .default let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest(identifier: "BLE_CONNECT_SUCCESS", content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) } }
注意:服务和特征发现完成后,必须对需要接收数据的特征调用peripheral.setNotifyValue(true, for: targetCharacteristic)开启通知,否则即使连接成功也收不到设备上报的数据。
额外校验项
- 确认Background Modes中勾选的是Uses Bluetooth LE accessories,不要误选外设模式。
- 后台扫描不要传空服务列表,必须指定目标设备的服务UUID,否则系统会直接拦截扫描请求。
- 不需要在后台设置过长的扫描超时,系统对后台BLE运行时长有严格限制,连接成功后立刻停止扫描。
内容的提问来源于stack exchange,提问作者Imen Mahj
相关产品推荐
相关产品推荐

