
LiveActivity.png
一、 技术准入与环境支持
1. 开发语言与架构
- 核心框架:基于 Swift 5.7+ 与 SwiftUI。
-
工程模式:支持 Objective-C (OC) 混编。通过
Bridging-Header桥接层,OC 业务代码可顺滑驱动 Swift 编写的实时活动管理类。 - 必要组件:必须添加 Widget Extension 目标,灵动岛在系统底层被视作小组件(Widget)家族的特殊成员。
2. 硬件与系统要求
-
适配设备:
- 灵动岛形态:仅限具备“药丸屏”的设备,如 iPhone 14 Pro / 15 / 16 系列及更高机型。
- 锁屏卡片形态:所有支持 iOS 16.1+ 的 iPhone 机型(刘海屏或非全面屏设备将在锁屏显示卡片)。
- 注:iPadOS 目前仅在锁屏界面支持实时活动。
-
系统版本:
- 推荐基准:iOS 16.2+。虽然 16.1 已引入,但 16.2 提供了更稳定的 API 和更完善的生命周期控制。
- 交互分水岭:iOS 17.0+。iOS 16 仅支持“只读”展示;从 iOS 17 开始,支持按钮、开关等交互式小组件 (Interactive Widgets) 功能。
二、 生命周期、权限与用户交互
1. 权限问询机制(系统“隐私巡检”)
-
触发时机:
-
首次请求:应用第一次点亮灵动岛时,系统会弹出
Allow Live Activities?询问。
首次请求 -
持续确认:iOS 16 引入了类似“隐私城管”的巡检机制,对于长时间或频繁运行的实时活动,系统会在锁屏界面询问
Do you want to continue to allow...。
持续确认
-
首次请求:应用第一次点亮灵动岛时,系统会弹出
拒绝处理:若用户点击“不允许”,API 调用将静默失败。建议在业务逻辑中加入权限检查,并引导用户跳转系统设置手动开启。
2. 展示时长限制
- 驻岛时长 (Active):灵动岛形态最多保持 8 小时 活跃更新。
- 锁屏停留 (Dismissed):活动结束或超时后,卡片最多在锁屏停留 4 小时(总计 12 小时生命周期),随后由系统彻底清除。
-
断线重连:当应用杀死重启后,可通过
Activity.activities静态属性重新接管属于本应用的活跃状态,实现“无缝断线重连”。
3. UI 表现形式
- 继承性:锁屏卡片会继承 Widget 系统的设计语言。
- 多样性:支持 紧凑型 (Compact)、极简型 (Minimal) 和 展开型 (Expanded) 三种灵动岛形态。

紧凑型

展开态

极简态
锁屏时

UI延续
三、 APNs 远程驱动方案
1. 隔离的推送管道
-
与 NSE 的关系:灵动岛更新与通知服务扩展(NSE)是两套完全隔离的逻辑。NSE 无法感知灵动岛推送,
mutable-content字段对此无效。 -
推送类型标识:必须在 HTTP Header 中严格设置
apns-push-type: liveactivity。
2. 统计与中台对接
- 到达率统计:由于无法通过 NSE 拦截,到达率需通过 “服务端响应 + 客户端 UI 渲染回调 (onAppear) + 共享存储 (App Group)” 的组合方案进行回传统计。
-
中台适配 (FCM):
- 限制:Firebase (FCM) 网页后台无法直接发送灵动岛推送,因其不支持自定义特定的 HTTP Header。
-
解决:必须通过服务端调用 FCM V1 接口,在
apns配置项中手动构造符合标准的liveactivity负载。
四、 深度 UI 定制与交互逻辑
1. 开发与调试
- 模拟器支持:可在模拟器上完成大部分 UI 布局与动态数值测试。
-
渲染引擎:完全基于 SwiftUI。支持
ProgressView等组件实现平滑的进度条更新。
2. 深度交互实现 (iOS 17+)
- 技术栈:AppIntent + App Group + IPC (进程间通讯)。
-
逻辑闭环:
- 用户点击卡片按钮触发
AppIntent。 -
Intent在独立进程运行,通过 App Group 修改共享存储。 - 通过音频后台模式(Audio Background Mode)或系统指令唤醒主 App 进程执行业务(如播放/暂停)。
- App 进程更新数据,灵动岛 UI 随之响应重绘。
- 用户点击卡片按钮触发
-
灵动岛与App同时结束方案
- 苹果提供的灵动岛end api是async,所以我们写代码时,其实是异步调用。
- 往往terminate时来不及关闭灵动岛,应用就没了。
- 苹果建议APNs推送结合实时活动来使用。实际有些应用有被杀死即关闭实时活动的需求
- 点名:某团、某音、等app,app结束时,灵动岛长时间停留系统,重启app也没有接管灵动岛并关闭。导致用户想关关不掉!!!差评。(难道为了反向提高了用户留存?我觉得不是,因为重启app灵动岛还是上次的直播间内容,哈哈)
- 曲线救G方案(我觉得最重要的小技巧,所以全文只贴以下这段代码):
/**
* 【关闭/结束灵动岛】异步
*/
@objc public func endLiveActivity() {
self.endLiveActivity(isSync: false)
}
/**
* 【终极方案:App 强退时同步关闭】
*
* ⚠️目前terminate 为 同步,因为要阻塞至命令发送给系统,确保执行完
*/
@objc public func endLiveActivity(isSync: Bool) {
guard let activeActivity = activity else { return }
if (!isSync)
{
print("⚡ 执行终极异步关闭方案")
Task {
await activeActivity.end(nil, dismissalPolicy: .immediate)
currentActivity = nil
print("⚡ 执行终极异步关闭方案 done")
}
}
else
{
print("⚡ 执行终极同步关闭方案")
let semaphore = DispatchSemaphore(value: 0)
let activityToEnd = activeActivity
// 发起高优先级后台任务
Task(priority: .userInitiated) {
print("⚡ 正在发送结束指令...")
await activityToEnd.end(nil, dismissalPolicy: .immediate)
print("✅ 指令已送达系统")
semaphore.signal()
}
// 策略1: 信号量等待
let result = semaphore.wait(timeout: .now() + 0.3)
var isDone = (result == .success)
if !isDone {
print("⏳ 信号量超时,尝试 RunLoop 轮询补救")
let deadline = Date().addingTimeInterval(0.3)
while !isDone && Date() < deadline {
if semaphore.wait(timeout: .now()) == .success {
isDone = true
break
}
RunLoop.current.run(until: Date().addingTimeInterval(0.01))
}
}
// 清理引用
currentActivity = nil
print("⚡ 执行终极同步关闭方案 结束状态: \(isDone ? "成功" : "超时")")
}
}
五、FAQ
- 点击锁屏卡片/灵动岛进入应用,并不会让锁屏卡片消失
- 支持灵动岛的设备一定支持锁屏卡片,支持锁屏卡片的设备不一定有灵动岛
- 小组件和主app是2个进程,往往代码不生效需要单独编译对应的target安装到手机上,看到app转菊花完毕则代表安装上了(这个情况在develop后台创建了小组件的bundleID时,并勾选了AppGroup此情况得以改善,直接运行app小组件代码及时生效)
- 如果没及时生效,也可能是ActivityAttributes发生了改变,通信数据结构对应不上导致的)
- 某Q音乐和其他音乐类播放软件的灵动岛音浪如何实现?本质上它们是Now Playing + System Media UI,而不是LiveActivity。它们是系统UI在为app服务,UI不能修改。(注意苹果的这句话:“Now Playing 只属于“用户主动感知的媒体播放行为”,不要去硬套,审核风险会变高)
调研结论
灵动岛不仅是 UI 的延展,更是用户驻留率的核心战场。对于 OC 老项目,采用 “Swift 桥接 + 共享模型文件” 的架构是目前最稳健的集成路径。建议优先针对 iOS 17 做交互适配,以获得最佳的用户体验。
附件为“Swift 桥接demo工程实例”
https://gitee.com/XXViper/oc--swift-live-activity
如果本文对你带来了一丁点收获请不吝点赞

