注册
iOS

货拉拉用户 iOS 端灵动岛实践总结

1. 前言


实时活动是iOS 16.1及以上版本中新增的功能,它允许应用在锁屏界面显示实时数据,能够帮助用户实时查看当前订单的进展,而无需解锁手机。用户在货拉拉APP上下单后,可以将手机放置一旁,开始其他工作。当用户想要查询订单状态时,只需从锁定屏幕或灵动岛上轻松操作即可。实时活动的出现不仅省去了用户解锁手机的步骤,更为用户节省了时间和精力。目前货拉拉APP适配“灵动岛”的最新6.7.68版本已正式上线,欢迎大家升级体验。在适配过程中,货拉拉App也踩过很多“坑”,在此汇总为实战经验分享给大家。


2. Live Activity&灵动岛的介绍


Live Activity的实现需要使用Apple的ActivityKit框架。通过使用ActivityKit,开发者可以轻松地创建一个Live Activity,这是一个动态的、实时更新的活动,可以在用户的设备上显示各种信息。此外,ActivityKit还提供了推送通知的功能,开发者可以通过服务器向用户的设备发送更新;这样,即使应用程序没有运行,用户也可以接收到最新的信息。


灵动岛是Live Activity的一种展示形式,灵动岛有三种展示形式:Compact紧凑、Minimal最小化,Expanded扩展。开发时必须实现这三种形式,以确保灵动岛在不同的场景下都能正常展示。



同时还需要实现锁屏下的实时活动UI,设备处于锁屏状态下,也能查看实时更新的内容。以上功能的实现,都是使用WidgetKit和SwiftUI完成开发。


2.1 技术难点及策略


实时活动,主要是APP在后台时,主动更新通知栏和灵动岛的数据,为用户展示最新实时订单状态。如何及时刷新实时活动的数据,是一个重点、难点。


更新方式有3种:



  1. 通过APP内订单状态的变化刷新实时活动和灵动岛。此方法开发量小,但是APP退到后台30s后或者进程杀掉,会停止数据的更新。
  2. 让APP配置支持后台运行模式,通过本地现有的订单状态变化逻辑,在后台发起网络请求,获取订单的数据后刷新实时活动。此方法开发量小,但求主App进程必须存在,进程一旦杀掉就无法更新。
  3. 通过接受远程推送通知来更新实时活动。此方法需要后端配合,此方式比较灵活,无需App进程存在,数据更新及时。也是业界常见的方案。

通过对数据刷新的三种方案进行评估后,选择了用户体验最佳的第三种方式。通过后端发生push,端上接受push数据来更新实时活动。


3. Live Activity&灵动岛的实践


3.1 实现方案流程图


实现流程图:


image.png


3.2 实现代码


创建Live Activities的准备:



  • Xcode需要14.1以上版本
  • 在主工程的 Info.plist 文件中添加一个键值对,key 为 NSSupportsLiveActivities,value 为 YES
  • 使用ActivityKit在Widget Extension 中创建一个Live Activity

需要实现锁屏状态下UI、灵动岛长按展开的UI、灵动岛单个UI、多个实时活动时的minimalUI


import SwiftUI
import WidgetKit

@main
struct TestWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: TestAttributes.self) { context in
// 锁屏状态下的UI
} dynamicIsland: { context in
DynamicIsland {
//灵动岛展开后的UI
} compactLeading: {
// 未被展开左边UI
} compactTrailing: {
// 未被展开右边UI
} minimal: {
// 多任务时,右边的一个圆圈区域
}
.keylineTint(.cyan)
}
}
}

灵动岛主要分为StartUpdateEnd三种状态,可由ActivityKit远程推送控制其状态。


开启Live Activity


        let state = TestAttributes.ContentState()
let attri = TestAttributes(value: 100)
do {
let current = try Activity.request(attributes: attri, contentState: state, pushType: .token)
Task {
for await state in current.contentStateUpdates {
//监听state状态
}
}
Task {
for await state in current.activityStateUpdates {
//监听activity状态
}
}
} catch(let error) {
}

更新Live Activity


   Task {
guard let current = Activity<TestAttributes>.activities.first else {
return
}
let state = TestAttributes.ContentState(value: 88)
await current.update(using: state)
}

结束Live Activity


    Task {
for activity in Activity<TestAttributes>.activities {
await activity.end(dismissalPolicy: .immediate)
}
}

4. 使用ActivityKit推送通知


ActivityKit提供了接收推送令牌的功能,我们可以使用这个令牌来通过ActivityKit推送通知从我们的服务器向Apple Push Notification service (APNs)发送更新。


推送更新Live Activity的准备:




  • 在开发者后台配置生成p8证书,替换原来的p12证书




  • 通过pushTokenUpdates获取推送令牌PushToken




  • 向后端注册PushToken




代码展示:


//取得PushToken
for await tokenData in current.pushTokenUpdates {
let mytoken = tokenData.map { String(format: "x", $0) }.joined()
//向后端注册
registerActivityToken(mytoken)
}

4.1 模拟器push验证测试


环境要求:


Xcode >= 14.1 MacOS >= 13.0


准备工作:



  1. 通过pushTokenUpdates获取推送需要的token
  2. 根据开发者TeamID、p8证书本地路径、BuidleID等进行脚本配置

脚本示例:


export TEAM_ID=YOUR_TEAM_ID
export TOKEN_KEY_FILE_NAME=YOUR_AUTHKEY_FILE.p8
export AUTH_KEY_ID=YOUR_AUTHKEY_ID
export DEVICE_TOKEN=YOUR_PUSH_TOKEN
export APNS_HOST_NAME=api.sandbox.push.apple.com

export JWT_ISSUE_TIME=$(date +%s)
export JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
export JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
export JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
export JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
export AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"

curl -v \
--header "apns-topic:YOUR_BUNDLE_ID.push-type.liveactivity" \
--header "apns-push-type:liveactivity" \
--header "authorization: bearer $AUTHENTICATION_TOKEN" \
--data \
'{"Simulator Target Bundle": "YOUR_BUNDLE_ID",
"aps": {
"timestamp":1689648272,
"dismissal-date":0,
"event": "update",
"sound":"default",
"content-state": {
"title": "等待付款",
"content": "请尽快完成下单"
}
}}'
\
--http2 \
https://${APNS_HOST_NAME}/3/device/$DEVICE_TOKEN

其中:


apns-topic:固定为{BundleId}.push-type.liveactivity


apns-push-type:固定为liveactivity


Simulator Target Bundle:模拟器推送,设置为对应APP的BundleId


timestamp:表示推送通知的发送时间,如果timestamp字段的值与当前时间相差太大,可能会收不到推送。


event:可填入update、end,对应Live Activity的更新与结束。


dismissal-date:当event为end时有效,表示结束后从锁屏上移除Live Activity的时间。如果推送内容不包含"dismissal-date",默认结束后4小时后消失,但内容不会再发生更新。如果期望Live Activity结束后立即从锁屏上移除它,可为"dismissal-date"提供一个过去的日期。


content-state:对应灵动岛的Activity.ContentState;如果push中content-state的字段和Attributes比较:




  • 字段过多,多余的字段可能会被忽略,不会导致解析失败




  • 字段缺少,会在解析push通知时出现问题错误。错误表现为:实时活动会有蒙层,并展示loading菊花UI。




示范:


image.png


image.png


5. 踩坑记录




  • 在模拟器上无法获取到pushToken,无法进行推送模拟?


    检查电脑的系统版本号,需要13.0以上




  • 更新实时活动时,页面显示加载loadingUI,为什么?


    核对push字段和Activity.ContentState的字段是否完全一致,字段少了会解析失败




  • 在16.1系统上,无法展示实时活动,其他更高系统能展示?


    检查Widget里面iOS系统版本号的配置,设置为想要支持的最低版本




  • dismissal-date设置为10分钟后才消失,为什么Dynamic Island灵动岛立即消失了?


    Dynamic Island的显示逻辑可能会更加复杂,如果push的event=end,Dynamic Island灵动岛会立即消失。期望同时消失,可以在指定时间再发end,dismissal-date设置为过去时间,锁屏UI和Dynamic Island灵动岛会同时消失。




  • 推送不希望打扰用户,静默推送,不需要震动和主动弹出,如何设置?


    将"content-available"设置为1,"sound" 设置为: ""




"aps" = {
"content-available" : 1,
"sound" : ""
}



  • 用户系统是深色模式时,如何适配?


    可以使用@Environment(.colorScheme)属性包装器来获取当前设备的颜色模式。会返回一个ColorScheme枚举,它可以是.light.dark。在根据具体的场景进行UI适配




struct ContentView: View {
@Environment(.colorScheme) var colorScheme

var body: some View {
VStack {
if colorScheme == .dark {
Text("深夜模式")
.foregroundColor(.white)
.background(Color.black)
} else {
Text("日间模式")
.foregroundColor(.(.black)
.background(Color.white)
}
}
}
}

5.1 场景限制及建议



  1. 官方文档提示实时活动最多持续8小时,8小时后数据无法刷新,12小时后会强制消失。因此8小时后的数据不准确
  2. 实时活动的卡片上禁止定位以及网络请求,数据需要小于4KB,不能展示特别负责庞大的数据
  3. 同场景多卡片由于样式趋同且折叠,不建议同时创建多卡片。用户多次下单时,建议只处理第一个订单

6. 用户APP上线效果


用户端iOS APP灵动岛上线后的部分场景截图:







7. 总结


灵动岛功能自上线以来,经过我们的数据统计,用户实时活动使用率高达75%以上。这一数据的背后,是灵动岛强大的功能和优秀的用户体验。用户可以在锁屏页直接查看订单状态,无需繁琐的操作步骤,大大提升了用户体验。这种便捷性,使得灵动岛在用户中的接受度较高。


我们的方案不仅可以应用于当前的业务场景,后续还计划扩展到营销活动,定制化通知消息等多种业务场景。这种扩展性,使得灵动岛可以更好地满足不同用户的需求,丰富产品运营策略。


我们希望通过分享开发过程中遇到的问题和解决方案,可以帮助到更多的人。如果你有任何问题或者想法,欢迎在评论区留言。期待我们在技术的道路上再次相遇。


总的来说,灵动岛以其高效、便捷、灵活的特性,赢得了用户的广泛好评。我们将继续努力,为用户提供更优质的服务,为产品的发展注入更多的活力。


作者:货拉拉技术
来源:juejin.cn/post/7300779071390335030

0 个评论

要回复文章请先登录注册