APNs 启动 Live Activity

442 阅读2分钟

一点废话

iOS>=17.2开始允许通过APNs启动Live Activity

先说限制

  • 第一次打开app 未启动实时活动,无法获取到pushToStartToken
  • 一个ActivityAttributes类所有的 pushToStartToken 都相同

基本流程

graph TD
启动App --> 启动一个临时的ActivityAttributes --> 获取pushToStartToken --> 上传pushToStartToken --> 关闭临时ActivityAttributes --> 在需要的时候APNs打开实时活动

实现

工程设置

1. 开启 Push Notifications

  • 在 Xcode → Signing & Capabilities → 添加 Push Notifications
  • 没有这个 Capability,系统不会给 App 分配 Push-to-Start Token

2. 开启 Background Modes → Remote notifications

  • 进入 Signing & Capabilities,勾选 Background Modes → 选中 Remote notifications
  • 否则 App 在前后台都不会触发 Token 更新。

3. App ID / Provisioning Profile

  • 你的 App ID 必须启用了 Push Notifications
  • Profile 要重新生成并下载,确保包含了 aps-environment entitlement。 可以在 xxx.entitlements 文件里确认有这一项:
<key>aps-environment</key>
<string>development</string>

4. Live Activity 配置

  • 在 Xcode 的 target 里,确保 Widget Extension 的 Info.plist 里:

    <key>NSExtension</key>
    <dict>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.widgetkit-extension</string>
    </dict>
    
  • 并且主 App 和 Widget Extension 的 Bundle ID 必须属于同一个 App Group

代码部分

启动临时一个实时活动获取pushToStartToken,启动方法Activity.request(..., pushType: .token) pushType 必须用 .token ,否则无法获取到 pushToStartToken

启动后获取 pushToStartToken 上传服务端

小提示

用xcode debug 时可以不创建一个临时实时活动也能获取 pushToStartToken,而且每次重新编译 pushToStartToken 都会变

上传完成后关闭临时实时活动

    func tempActivity() {
 
        do {
            let activity = try Activity.request(
                attributes: MyWidgetAttributes(...),
                content: .init(state: MyWidgetAttributes.ContentState(...), staleDate: nil),
                pushType: .token // 必须用.token
            )
            
            Task {
                if let tokenData = await Activity<LiveActivitiesWidgetAttributes>
                    .pushToStartTokenUpdates
                    .first(where: { _ in true }) {
                    // 获取pushToStartToken 上传服务端
                    let mytoken = tokenData.map { String(format: "%02x", $0) }.joined()
                }
                self.dismissal()
            }

        } catch (let error) {
            print("请求开启实时出错: \(error.localizedDescription)")
        }

        
    }

    func dismissal() {

        for activity = Activity<LiveActivitiesWidgetAttributes>.activities {
            Task {
                await activity.end(
                ActivityContent(state: MyWidgetAttributes.ContentState(...), staleDate: nil),
                dismissalPolicy: dismissalPolicy)
            }
        }

    }

完成后可以再开启一个Task监听 pushToStartToken,每次开启一次实时活动都可以通过监听拿到 pushToStartToken

func getPushToStartToken() {

    //pushToStartToken为空。App创建LA时,会触发下面的回调。可以拿到pushToStartToken
    Task {
        for await tokenData in Activity<MyWidgetAttributes>.pushToStartTokenUpdates {
            //监听token更新 注意线程
            let mytoken = tokenData.map { String(format: "%02x", $0) }.joined()
            //TODO: 开发者上传变更后的startToken
   
        }
        // for 循环以后的代码不会执行
        // for await 会 一直挂起等待新事件,只有当序列 结束(complete/throw) 时,才会跳出循环。
    }

}

服务端发送 APNs 启动实时活动

请求头

POST https://api.sandbox.push.apple.com/3/device/{PushToStartToken}
authorization: bearer <your-auth-token>
apns-topic: <你的主App Bundle ID>.push-type.liveactivity
apns-push-type: liveactivity

请求体

{
  "aps": {
    "timestamp": 1692243600,
    "event": "start",
    "content-state": {
      "status": "配送中",
      "progress": 10
    },
    "alert": {
      "title": "订单已出发",
      "body": "骑手正在赶来 🚴"
    }
  }
}
  • event: "start" 表示 启动一个新的 Live Activity
  • content-state 是你定义的 MyWidgetAttributes.ContentState 的 JSON 映射,每个字段都要实现否则系统解码时会出错
  • alert 可选,如果提供会在锁屏/通知中心显示一条启动提示

调试工具

做好证书后可以自己发APNS 测试:apnspush.com/

推送证书用主app Bundle ID 做的 APNs 证书

系统提供的Apple Notifications Dashboard 因为不能修改apns-topic,无法启动实时活动

image.png

参考文章

docs.getui.com/getui/scene…