一、简介
灵动岛(Dynamic Island) 是苹果在 iOS 16.1 引入的交互设计,通过「实时活动」(Live Activity)实现在锁屏和主屏幕显示动态信息(如运动赛事、导航状态等)。开发者需使用 ActivityKit 框架实现功能。
本文档指导开发者如何结合阿里云移动推送SDK在iOS应用中集成和管理灵动岛(Live Activity)。介绍如何使用灵动岛管理接口完成启动令牌上报、推送令牌上报及活动状态同步。介绍如何通过阿里云推送启动、更新和结束灵动岛。
灵动岛系统限制
在一个App中最多同时创建5个实时活动
一个活动最多持续8个小时,活动结束后在锁屏界面最多显示4个小时
灵动岛背景颜色只能是黑色
灵动岛APNs推送仅支持P8证书
二、开发环境要求
macOS Ventura及以上
Xcode 14.1+
iOS 16.1+
iPhone 14 Pro 及以上系列
三、核心开发步骤
1. 创建 Widget Extension
在 Xcode 主工程中新增 Live Activity Widget:
选择 File > New > Target,添加 Widget Extension 模板
勾选 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:
启动令牌(Start Token):用于远程启动Live Activity
推送令牌(Push Token):用于更新已存在的Live Activity
6.1 启动令牌监听与上报
系统版本要求:
17.2
及以上,17.2
以下设备只能通过本地启动活动。工作原理:通过
pushToStartTokenUpdates
异步流持续监听启动令牌,通过oldToken
缓存实现增量上报,避免冗余请求。多活动适配 :每个
ActivityAttributes
类型需独立监听(如MyActivityAttributes
和AnotherAttributes
需分别创建监听任务)。获取时机:
应用启动时
应用从后台恢复到前台时
系统可能在任意时间更新token
上报建议:
应用启动时立即开启监听
建议在AppDelegate或SceneDelegate中启动监听
使用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的状态。通过嵌套异步任务监听活动实例的
pushTokenUpdates
和activityStateUpdates
,将活动的令牌和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调试控制台发送启动灵动岛的通知,观察手机上是否成功运行灵动岛。
当应用处于后台时,屏幕会弹出灵动岛的扩展视图,几秒后视图将缩小为紧凑模式。
在锁屏界面,用户可以看到默认视图。
首次创建活动时,系统会弹出对话框请求用户授权实时活动。只有在用户点击“允许”之后,才能获取推送令牌,随后才能通过推送更新灵动岛的状态。
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. 灵动岛显示效果
2. 灵动岛UI设计模式
- 本页导读 (0)
- 一、简介
- 二、开发环境要求
- 三、核心开发步骤
- 1. 创建 Widget Extension
- 2. 配置 Info.plist
- 3. 定义数据模型
- 4. 实现 UI 界面
- 5. 在App本地管理活动
- 6. 使用APNs远程管理活动
- 四、成功运行验证
- 1. 成功上报启动令牌
- 2. 成功推送启动灵动岛
- 3. 成功推送更新灵动岛
- 4. 成功推送结束灵动岛
- 五、灵动岛相关资料
- 1. 灵动岛显示效果
- 2. 灵动岛UI设计模式