在 watchOS 上接收蓝牙设备的及时通知
Get timely alerts from Bluetooth devices on watchOS
2022年6月6日
一句话判断
watchOS 9 终于允许蓝牙配件在 Apple Watch 上弹出后台通知了——Background Accessories 框架让你的 BLE 设备不再需要用户主动打开 App 才能收到数据。
这场 Session 讲了什么
这场 Session 面向开发蓝牙(Bluetooth Low Energy)配件和 watchOS App 的开发者,讲了 watchOS 9 中蓝牙后台通知的新能力。
之前的痛点。 在 watchOS 9 之前,BLE 配件想要向 Apple Watch 发送通知,用户必须把你的 App 保持在前台。一旦 App 进入后台,BLE 连接可能被系统挂起,通知就收不到了。这对于健康监测、运动追踪、智能家居等场景来说是致命的——用户不可能一直盯着 App 看。
Background Accessories 框架。 watchOS 9 引入了新的 Background Accessories 框架,允许 BLE 配件在 App 处于后台时发送”及时通知”(timely alert)。这里的”及时”是关键词——系统保证通知在事件发生后几秒内送达用户,而不是像普通的后台 BLE 更新那样可能延迟几分钟。
工作原理。 你的 BLE 配件通过一个特定的 GATT characteristic 发送通知数据。watchOS 的 Background Accessories 框架会监听这个 characteristic,当收到数据时唤醒你的 App Extension(即使 App 在后台),然后在 Apple Watch 上显示一个本地通知。整个过程不需要远程推送服务器——完全基于 BLE 本地通信。
使用场景。 Session 里列举了几种典型场景:血糖仪测量完成通知、心电监测异常告警、智能家居门铃响了、运动传感器检测到异常姿态等。这些场景的共同点是:事件发生时需要立即通知用户,延迟超过几秒就没有意义了。
隐私和权限。 使用 Background Accessories 需要用户授权。你的 App 需要声明 com.apple.developer.bluetooth.background-accessories entitlement,并且在 Info.plist 里说明为什么需要后台蓝牙通知。用户首次使用时会看到一个权限弹窗,之后可以在设置里随时关闭。
电量管理。 Apple 对 Background Accessories 的电量消耗做了优化。BLE 连接在后台时使用低功耗模式,只有在收到通知数据时才短暂唤醒 CPU。系统会监控你的 App 的 BLE 活动频率——如果通知过于频繁(比如每秒发好几次),系统可能会限制你的后台访问权限。
值得深挖的点
BLE 连接的可靠性。 Background Accessories 框架会尽力维护 BLE 连接,但在某些情况下连接可能中断(比如用户离开 BLE 范围、手表进入省电模式)。你的 App 需要处理连接断开和重连的逻辑。Session 建议在 BLE 配件端实现”缓存+重发”机制——如果通知发送时 BLE 连接断了,配件应该缓存通知,等连接恢复后重新发送。
和 iOS 版 Background Bluetooth 的区别。 iOS 早就支持后台 BLE 通信,但 watchOS 的实现有一些不同。watchOS 的 Background Accessories 更注重”及时性”——系统会在收到 BLE 数据后立即唤醒 App,而不是像 iOS 那样可能延迟。这是因为 Apple Watch 的使用场景更偏向”即时响应”——用户抬手就能看到通知。
代码片段
配置 Background Accessories:
import CoreBluetooth
import BackgroundAccessories
class BLEAccessoryManager: NSObject, CBCentralManagerDelegate {
var centralManager: CBCentralManager!
var accessoryPeripheral: CBPeripheral?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
// 扫描支持你的 service UUID 的设备
centralManager.scanForPeripherals(
withServices: [CBUUID(string: "YOUR_SERVICE_UUID")],
options: nil
)
}
}
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi: NSNumber) {
accessoryPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
}
处理后台 BLE 通知并显示本地通知:
// 在 App Extension 里处理后台 BLE 数据
class NotificationHandler: UNUserNotificationCenterDelegate {
func handleBLENotification(data: Data) {
let content = UNMutableNotificationContent()
content.title = "设备通知"
content.body = parseNotificationBody(data)
content.sound = .default
// 立即显示通知
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: nil // nil = 立即显示
)
UNUserNotificationCenter.current().add(request)
}
}
BLE 配件端配置通知 characteristic:
// 配置 GATT characteristic 的属性
let notifyCharacteristic = CBMutableCharacteristic(
type: CBUUID(string: "YOUR_NOTIFY_UUID"),
properties: [.notify, .read],
value: nil,
permissions: [.readable]
)
// 当有新数据时发送通知
func sendNotification(data: Data) {
let success = notifyCharacteristic.subscribedCentrals?.allSatisfy { central in
// 发送数据到订阅的 central
peripheralManager.updateValue(
data,
for: notifyCharacteristic,
onSubscribedCentrals: [central]
)
}
}
最佳实践
BLE 通知的数据负载要尽可能小——建议不超过 20 字节(BLE 4.0 的 MTU 限制)。如果你的通知内容比较长,在 BLE 通知里只发一个标识符,然后 App 收到通知后通过 BLE 读取完整数据。
在 BLE 配件端实现心跳机制。每隔几秒发一个空的 BLE packet 保持连接活跃。如果心跳断了,说明连接可能有问题,配件应该尝试重新广播。心跳间隔不要太短(耗电),也不要太长(连接可能超时),5-10 秒是比较合理的范围。
通知内容要有意义,不要发垃圾通知。Apple 对 Background Accessories 的通知频率有监控——如果你的 App 频繁发无意义的通知,系统可能会降低你的后台优先级。只发真正需要用户立即关注的通知(比如异常告警),普通数据更新用 App 内刷新就好。
还有什么值得关注
- Background Accessories 需要在 Watch App 的 Info.plist 里声明
UIBackgroundModes包含bluetooth-central。 - watchOS 9 的 BLE 后台连接最多同时维护 2-3 个活跃连接,不要尝试同时连接太多设备。
- 如果用户在控制中心关闭了蓝牙,所有 Background Accessories 连接都会断开。你的 App 需要在蓝牙重新开启时自动重连。
- 电量优化模式下(比如 Apple Watch 的 Low Power Mode),Background Accessories 的行为可能会受到限制。你的 App 应该在电量低时减少通知频率。