WWDC Quick Look 💓 By SwiftGGTeam
Connect Bluetooth devices to Apple Watch

Connect Bluetooth devices to Apple Watch

观看原视频

Highlight

watchOS 8 允许应用在 Background App Refresh 期间连接蓝牙设备,这让 complications 可以持续从蓝牙配件获取数据并更新,无需用户主动打开应用。

核心内容

以前的问题:蓝牙只能在应用前台连接

00:31

在 watchOS 7 及之前,应用只能在两种状态下连接蓝牙设备:前台运行时,或后台会话期间。这意味着如果用户想看 complication 上显示蓝牙设备的最新数据,必须先打开应用建立连接。

这对很多场景都不够友好。比如一个温度传感器,用户希望抬腕就能看到当前温度,而不是每次都要打开应用。

watchOS 8 的解决方案:后台刷新期间连接蓝牙

00:57

watchOS 8 允许应用在 Background App Refresh 期间连接蓝牙配件。这带来了两个核心能力:

  1. 更新 complications:从蓝牙设备获取数据,直接更新表盘上的 complication
  2. 发送本地通知:比如蓝牙设备电量低时提醒用户

要使用这个功能,只需要在 Info.plist 中添加 bluetooth-centralUIBackgroundModes

连接的生命周期

02:04

蓝牙连接的生命周期分几个阶段:

首次配对(前台)

  • 用户打开应用
  • 应用扫描并发现蓝牙设备
  • 建立初始连接,可能需要配对
  • 用户退出应用,蓝牙连接断开

后台刷新期间(watchOS 8 新增)

  • 系统触发 Background App Refresh
  • 应用发起对已配对设备的连接请求
  • 设备发送 advertisement,Apple Watch 建立连接
  • 应用获取数据,处理完成后主动断开连接

02:36

配件设计的最佳实践

02:55

Background App Refresh 可能在任何时间发生,所以配件应该在有重要更新时尽可能频繁地广播。如果配件为了省电很少广播,可能在后台刷新期间收不到 advertisement,导致连接失败。

一个策略是在配件上缓冲传感器数据。当缓冲区快满时,提高广播频率以增加与 Apple Watch 重新连接的机会。

03:37

在理想的射频条件下,建议至少每两秒广播一次。如果在更具挑战性的射频环境中使用,应该更频繁地广播。

三种用户流程

04:02

Session 展示了三种可能的连接流程:

流程一:正常连接和断开

  • 应用在后台刷新期间发起连接
  • 收到 advertisement,建立连接
  • 获取数据,调用 cancelPeripheralConnection 主动断开
  • 这是最理想的流程

流程二:配件不在附近或未广播

  • 应用发起连接,但没有收到 advertisement
  • expiration handler 被调用
  • 应用取消连接请求,准备下一次后台刷新
  • 建议主动在 expiration handler 之前断开以节省电量

流程三:未在后台刷新结束前断开

  • 应用建立连接,但在后台刷新结束前没来得及断开
  • Core Bluetooth 会在后台刷新结束时自动断开连接
  • 下一次后台刷新时,应用收到 didDisconnectPeripheral 回调
  • 应用可以重新发起连接或等待下一次刷新

05:28

详细内容

前台发现设备

07:35

当用户在前台使用应用时,发现设备并连接:

func centralManager(_ central: CBCentralManager,
                    didDiscover peripheral: CBPeripheral,
                    advertisementData: [String: Any],
                    rssi RSSI: NSNumber) {
    // 添加到已发现设备列表,然后连接
    central.connect(peripheral, options: nil)
}

关键点:

  • didDiscoverPeripheral 在检测到 advertisement 时调用
  • 将发现的设备保存到数组中供后续使用
  • central.connect 发起连接请求

后台刷新期间连接

07:55

在 Extension Delegate 的后台任务处理中发起蓝牙连接:

func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
    for task in backgroundTasks {
        if let refreshTask = task as? WKApplicationRefreshBackgroundTask {
            // 开始后台工作:连接蓝牙设备
            central.connect(peripheral, options: nil)

            refreshTask.expirationHandler = {
                // 后台时间即将用完,取消连接
                if let peripheral = self.bluetoothReceiver.connectedPeripheral {
                    self.central.cancelPeripheralConnection(peripheral)
                }
                refreshTask.setTaskCompletedWithSnapshot(false)
            }
        }
    }
}

关键点:

  • WKApplicationRefreshBackgroundTask 中发起蓝牙连接
  • expirationHandler 是 watchOS 8 新增 API,在后台时间即将用完时调用
  • 在 expiration handler 中取消连接并标记任务完成
  • 主动在 expiration handler 之前断开连接可以节省电量

断开连接的处理

08:51

当蓝牙设备断开时,系统会调用 delegate 方法:

func centralManager(_ central: CBCentralManager,
                    didDisconnectPeripheral peripheral: CBPeripheral,
                    error: Error?) {
    connectedPeripheral = nil
    delegate?.didCompleteDisconnection(from: peripheral)
}

关键点:

  • didDisconnectPeripheral 在前后台都可能被调用
  • 清空 connectedPeripheral 引用
  • 通知 delegate 断开完成

在 Extension Delegate 中完成后台任务

09:08

func didCompleteDisconnection(from peripheral: CBPeripheral) {
    if let refreshTask = currentRefreshTask {
        refreshTask.setTaskCompletedWithSnapshot(false)
        currentRefreshTask = nil
    }
}

关键点:

  • 确认当前处于 Background App Refresh 状态
  • 标记后台任务完成
  • 这会终止 Background App Refresh,让应用回到挂起状态
  • currentRefreshTask 设为 nil 避免重复完成

Info.plist 配置

要使用后台蓝牙连接,需要在 WatchKit Extension 的 Info.plist 中添加:

<key>UIBackgroundModes</key>
<array>
    <string>bluetooth-central</string>
</array>

01:35

这也可以显示为 “Required background modes” 和 “App communicates using CoreBluetooth”。

平台支持配置对比

01:50

不同平台上蓝牙低功耗的支持配置有所不同。在 watchOS 上,当前支持的配置包括前台连接和后台刷新期间的连接。后台扫描新设备只在 iOS 上支持,watchOS 不支持在后台扫描新设备。

首次设备设置的要求

09:26

首次设置蓝牙设备时,应用必须在前台运行。用户需要主动使用应用,此时可以扫描新设备并建立初始连接。一旦设备被发现并配对,后续就可以在后台刷新期间重新连接。

应用退出前台后不能扫描新设备,只能请求连接已发现的设备。连接后获取数据,然后应该主动断开以保存电量。

核心启发

  • 做一个蓝牙温度计 complication:连接蓝牙温度传感器,在表盘 complication 上实时显示当前温度。利用后台刷新每小时最多四次更新数据。入口 API 是 CBCentralManager + WKApplicationRefreshBackgroundTask

  • 做一个运动 GPS 追踪器配套应用:连接蓝牙 GPS 追踪器,在 complication 上显示配速和距离。运动结束后自动断开以省电。入口 API 是 CBPeripheraldidUpdateValueFor + HKWorkoutSession

  • 做一个医疗设备数据监测应用:连接蓝牙血氧仪或血压计,在后台刷新期间读取数据并记录到 HealthKit。数据异常时发送本地通知提醒。入口 API 是 HKHealthStore.save + UNUserNotificationCenter

  • 做一个蓝牙设备电量监控工具:在后台连接时读取蓝牙设备的电池电量,电量低于阈值时在 complication 上显示警告图标。入口 API 是 CBCharacteristic 的电池服务 UUID 0x180F

  • 做一个智能家居手表遥控器:连接蓝牙门锁或灯光控制器,在 complication 上显示设备状态,点击快速控制。入口 API 是 CBPeripheral.writeValue + 自定义 GATT 服务。

关联 Session

评论

GitHub Issues · utterances