You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 16:54:42