iOS Live Activity开发指南

更新时间:2025-03-25 11:59:49

一、简介

灵动岛(Dynamic Island) 是苹果在 iOS 16.1 引入的交互设计,通过「实时活动」(Live Activity)实现在锁屏和主屏幕显示动态信息(如运动赛事、导航状态等)。开发者需使用 ActivityKit 框架实现功能。

本文档指导开发者如何结合阿里云移动推送SDKiOS应用中集成和管理灵动岛(Live Activity)。介绍如何使用灵动岛管理接口完成启动令牌上报、推送令牌上报及活动状态同步。介绍如何通过阿里云推送启动、更新和结束灵动岛。

说明

灵动岛系统限制

  • 在一个App中最多同时创建5个实时活动

  • 一个活动最多持续8个小时,活动结束后在锁屏界面最多显示4个小时

  • 灵动岛背景颜色只能是黑色

  • 灵动岛APNs推送仅支持P8证书

二、开发环境要求

  1. macOS Ventura及以上

  2. Xcode 14.1+

  3. iOS 16.1+

  4. iPhone 14 Pro 及以上系列

三、核心开发步骤

1. 创建 Widget Extension

在 Xcode 主工程中新增 Live Activity Widget:

  1. 选择 File > New > Target,添加 Widget Extension 模板

  2. 勾选 Include Live Activity 选项

2. 配置 Info.plist

在主工程的 Info.plist 中添加以下键值:

<key>NSSupportsLiveActivities</key>
<true/>

此配置声明应用支持灵动岛功能。如果您需要频繁更新灵动岛状态,还需继续添加以下键值:

<key>NSSupportsLiveActivitiesFrequentUpdates</key>
<true/>

3. 定义数据模型

通过 ActivityAttributes 协议定义主工程与 Widget 的数据交互模型:

struct MyActivityAttributes: ActivityAttributes {
    public typealias ContentState = State

    public struct State: Codable, Hashable {
        var progress: Double // 动态更新的数据
    }

    var title: String // 初始静态数据
}

4. 实现 UI 界面

使用 SwiftUI 构建两种视图模式:

struct MyLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: MyActivityAttributes.self) { context in
            // 锁屏/灵动岛默认状态
            VStack {
                Text(context.attributes.title)
                ProgressView(value: context.state.progress)
            }
        } dynamicIsland: { context in
            // 灵动岛扩展视图(长按后显示)
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                    Text("进度:\(Int(context.state.progress * 100))%")
                }
            } compactLeading: {
                // 紧凑模式-左侧区域
                Text(context.attributes.title)
            } compactTrailing: {
                // 紧凑模式-右侧区域
                ProgressView(value: context.state.progress)
                    .progressViewStyle(.circular)
            } minimal: {
                // 极小模式
                ProgressView(value: context.state.progress)
                    .progressViewStyle(.circular)
            }
        }
    }
}

5. 在App本地管理活动

在本地通过 ActivityKit 管理活动的生命周期,参考以下demo实现启动、更新和结束活动。

// 启动 Live Activity
let attributes = MyActivityAttributes(title: "下载任务")
let initState = MyActivityAttributes.State(progress: 0.0)
do {
    let activity = try Activity<MyActivityAttributes>.request(
        attributes: attributes,
        contentState: init(state: initState, staleDate: nil),
        pushType: .token
    )
    // 保存 activity 对象以供后续操作
    self.currentActivity = activity 
} catch {
    print("启动失败:\(error)")
}

// 更新 Live Activity(模拟异步任务)
Task {
    guard let activity = self.currentActivity else { return }
    for progress in 0...100 {
        try? await Task.sleep(nanoseconds: 1_000_000_000) // 模拟延迟
        let newState = MyActivityAttributes.State(progress: Double(progress)/100)
        await activity.update(.init(state: newState, staleDate: nil))
    }
}

// 结束 Live Activity
if let activity = self.currentActivity {
    let finalState = MyActivityAttributes.State(progress: 100.0)
    await activity.end(
        .init(state: finalState, staleDate: nil),
        dismissalPolicy: .immediate // 立即关闭
    )
    self.currentActivity = nil
}

6. 使用APNs远程管理活动

iOS 通过 APNs 推送实现 Live Activity 的远程控制,开发者需结合 ActivityKit 异步监听机制 阿里云推送服务 对实时活动完成全生命周期管理。

Live Activity涉及两种token:

  1. 启动令牌(Start Token):用于远程启动Live Activity

  2. 推送令牌(Push Token):用于更新已存在的Live Activity

6.1 启动令牌监听与上报

  • 系统版本要求17.2及以上,17.2以下设备只能通过本地启动活动。

  • 工作原理:通过 pushToStartTokenUpdates 异步流持续监听启动令牌,通过 oldToken 缓存实现增量上报,避免冗余请求。

  • 多活动适配 :每个 ActivityAttributes 类型需独立监听(如 MyActivityAttributesAnotherAttributes 需分别创建监听任务)。

  • 获取时机

    • 应用启动时

    • 应用从后台恢复到前台时

    • 系统可能在任意时间更新token

  • 上报建议

    • 应用启动时立即开启监听

    • 建议在AppDelegateSceneDelegate中启动监听

    • 使用oldToken缓存机制避免重复上报

// 建议在应用启动时就开启监听
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    observeActivityTokenAndState()
    return true
}

func observeActivityTokenAndState() {
    Task {
        var oldToken: String = ""
        if #available(iOS 17.2, *) {
            for await tokenData in Activity<MyActivityAttributes>.pushToStartTokenUpdates {
                let token = tokenData.map { String(format: "%02x", $0) }.joined()
                if oldToken != token {
                    oldToken = token
                    // 调用阿里云SDK注册启动令牌
                    CloudPushSDK.registerLiveActivityStartToken(
                        tokenData,
                        forActivityAttributes: "MyActivityAttributes"
                    ) { result in
                        print("启动令牌上报结果:\(result.success) - 模型:MyActivityAttributes")
                    }
                }
            }
        }
    }
}

6.2 推送令牌与状态同步

  • Live Activity启动之后,每个Activity会分配一个推送令牌和活动ID。如果您是使用ActivityKit从本地启动,则需要在启动活动时指定pushType.token类型才能分配令牌。令牌用于后续通过推送来更新Live Activity的状态。

  • 通过嵌套异步任务监听活动实例的 pushTokenUpdatesactivityStateUpdates,将活动的令牌和ID上报给阿里云服务器。

  • 获取时机:

    • Live Activity创建成功后

    • 活动生命周期内token可能发生变化

  • 上报建议:

    • Activity创建后立即开启监听

    • 每个Activity实例都需要独立监听

    • 使用oldPushToken缓存机制避免重复上报

    • 活动结束时自动停止监听

class LiveActivityMonitor {
    private var taskHandle: Task<Void, Never>?
    private var monitoredActivities = Set<String>()
    
    func startMonitoring() {
        taskHandle = Task {
            if #available(iOS 16.2, *) {
                // 监听所有活动更新
                for await activity in Activity<HelloAttributes>.activityUpdates {
                    print("检测到活动:\(activity.id)")
                    
                    // 避免重复监听同一个activity
                    guard !monitoredActivities.contains(activity.id) else { continue }
                    monitoredActivities.insert(activity.id)
                    
                    // 监听推送令牌变化
                    Task {
                        var oldPushToken = ""
                        for await tokenData in activity.pushTokenUpdates {
                            let token = tokenData.map { String(format: "%02x", $0) }.joined()
                            if oldPushToken != token {
                                oldPushToken = token
                                // 上报推送令牌到阿里云
                                CloudPushSDK.registerLiveActivityPushToken(
                                    tokenData,
                                    forActivityId: activity.id
                                ) { result in
                                    print("推送令牌更新结果:\(result.success) - 活动ID:\(activity.id)")
                                }
                            }
                        }
                    }
                    
                    // 监听活动状态变化
                    Task {
                        for await state in activity.activityStateUpdates {
                            // 同步状态到阿里云
                            CloudPushSDK.syncLiveActivityState(
                                "\(state)",
                                forActivityId: activity.id
                            ) { result in
                                print("状态同步结果:\(result.success) - 状态:\(state) - 活动ID:\(activity.id)")
                            }
                            
                            // 如果活动结束,从监听列表中移除
                            if state == .ended {
                                monitoredActivities.remove(activity.id)
                            }
                        }
                    }
                }
            }
        }
    }
    
    func stopMonitoring() {
        taskHandle?.cancel()
        monitoredActivities.removeAll()
    }
}

6.3 通过OpenAPI启动、更新和结束活动

  • 通过接口Push - 高级推送完成活动的生命周期管理。

  • 活动仅支持按照设备粒度进行管理。

  • 在启动活动时需要指定数据模型ActivityAttributes的类名,在本示例中为MyActivityAttributes

  • 在更新和结束活动时需要指定活动的ActivityID,在本示例中为C7D9BFE6-0350-4761-827B-BA280EA3E878

重要

在进行推送之前,请确认您已在EMAS推送控制台配置p8证书。参考文档iOS配置推送证书指南

启动灵动岛参数示例
更新灵动岛参数示例
结束灵动岛参数示例
{
    "AppKey": "233586006",
    "DeviceType": "iOS",
    "PushType": "NOTICE",
    "Target": "DEVICE",
    "TargetValue": "a1b693a9fced4c22aab3cdfe3fbe5a05",
    "iOSApnsEnv": "DEV",
    "iOSBadgeAutoIncrement": "false",
    "iOSLiveActivityAttributesType": "MyActivityAttributes",
    "iOSLiveActivityAttributes": "{\"title\":\"下载任务\"}",
    "iOSLiveActivityContentState": "{\"progress\":0.01}",
    "iOSLiveActivityEvent": "start"
}
{
    "AppKey": "233586006",
    "DeviceType": "iOS",
    "PushType": "NOTICE",
    "Target": "DEVICE",
    "TargetValue": "a1b693a9fced4c22aab3cdfe3fbe5a05",
    "iOSApnsEnv": "DEV",
    "iOSLiveActivityContentState": "{\"progress\":0.75}",
    "iOSLiveActivityEvent": "update",
    "iOSLiveActivityId": "C7D9BFE6-0350-4761-827B-BA280EA3E878"
}
{
    "AppKey": "233586006",
    "DeviceType": "iOS",
    "PushType": "NOTICE",
    "Target": "DEVICE",
    "TargetValue": "a1b693a9fced4c22aab3cdfe3fbe5a05",
    "iOSApnsEnv": "DEV",
    "iOSLiveActivityContentState": "{\"progress\":1.0}",
    "iOSLiveActivityEvent": "end",
    "iOSLiveActivityId": "C7D9BFE6-0350-4761-827B-BA280EA3E878"
}

四、成功运行验证

1. 成功上报启动令牌

打开App,观察Xcode控制台输出是否有如下日志:

启动令牌上报结果:true - 模型:MyActivityAttributes
  • 首次安装App时,至少需要打开一次并成功上报启动令牌到阿里云推送服务器,才能实现推送启动Live Activity。

  • 如果用户长时间未打开App可能导致启动令牌长期未更新从而失效。

2. 成功推送启动灵动岛

使用OpenAPI调试控制台发送启动灵动岛的通知,观察手机上是否成功运行灵动岛。

幻灯片1

  • 当应用处于后台时,屏幕会弹出灵动岛的扩展视图,几秒后视图将缩小为紧凑模式。

  • 在锁屏界面,用户可以看到默认视图。

  • 首次创建活动时,系统会弹出对话框请求用户授权实时活动。只有在用户点击“允许”之后,才能获取推送令牌,随后才能通过推送更新灵动岛的状态。

3. 成功推送更新灵动岛

您需要先获取已启动的灵动岛的ID,建议在6.2步骤时将活动ID记录到自己的服务器中。然后用这个活动ID去调用阿里云推送接口实现灵动岛状态更新。

这里我们观察Xcode控制台输出是否有以下日志:

检测到活动:764B890F-FFD3-4F21-A385-19B127490B1C
推送令牌更新结果:true - 活动ID:764B890F-FFD3-4F21-A385-19B127490B1C

接下来,复制该活动ID,在OpenAPI调试控制台发送更新灵动岛的通知,观察手机上是否成功更新灵动岛的状态。

4. 成功推送结束灵动岛

完成灵动岛活动后,你需要通过OpenAPI调试控制台发送结束指令。使用先前获取的活动ID发送结束通知后,请确认设备上灵动岛区域的内容是否已完全消失。

值得注意的是,即使灵动岛活动已结束,相关通知依然会在锁屏界面保持显示。这是因为系统默认会将结束状态的灵动岛通知在锁屏界面最多保留4小时。如果您需要自定义锁屏活动结束后的显示时长,可以通过设置 iOSLiveActivityDismissalDate 参数来控制。若要使通知立即消失,只需将该参数值设为当前时间戳即可。

五、灵动岛相关资料

图片资源来自苹果开发者官网

1. 灵动岛显示效果

iOS灵动岛

2. 灵动岛UI设计模式

iOS灵动岛1

  • 本页导读 (0)
  • 一、简介
  • 二、开发环境要求
  • 三、核心开发步骤
  • 1. 创建 Widget Extension
  • 2. 配置 Info.plist
  • 3. 定义数据模型
  • 4. 实现 UI 界面
  • 5. 在App本地管理活动
  • 6. 使用APNs远程管理活动
  • 四、成功运行验证
  • 1. 成功上报启动令牌
  • 2. 成功推送启动灵动岛
  • 3. 成功推送更新灵动岛
  • 4. 成功推送结束灵动岛
  • 五、灵动岛相关资料
  • 1. 灵动岛显示效果
  • 2. 灵动岛UI设计模式