iOS 实时活动(Live Activity)开发指南

iOS 实时活动(Live Activity)开发指南

基于 LiveActivityDemo 实战项目整理。目标:让第一次接触 Live Activity 的开发者,在半天内跑通「本地创建 → 远程推送更新 → 结束」的完整链路,并避开最常见的联调坑。


目录

  1. 它是什么,适合做什么
  2. 系统架构一图看懂
  3. 限制与前置条件
  4. 从零到跑通:开发流程
  5. 数据模型设计
  6. [UI 开发:锁屏 + 灵动岛](#UI 开发:锁屏 + 灵动岛 "#6-ui-%E5%BC%80%E5%8F%91%E9%94%81%E5%B1%8F--%E7%81%B5%E5%8A%A8%E5%B2%9B")
  7. 本地生命周期管理
  8. [远程推送:三种 Token 与完整链路](#远程推送:三种 Token 与完整链路 "#8-%E8%BF%9C%E7%A8%8B%E6%8E%A8%E9%80%81%E4%B8%89%E7%A7%8D-token-%E4%B8%8E%E5%AE%8C%E6%95%B4%E9%93%BE%E8%B7%AF")
  9. [APNs 推送格式与联调要点](#APNs 推送格式与联调要点 "#9-apns-%E6%8E%A8%E9%80%81%E6%A0%BC%E5%BC%8F%E4%B8%8E%E8%81%94%E8%B0%83%E8%A6%81%E7%82%B9")
  10. [本 Demo 的调试工具](#本 Demo 的调试工具 "#10-%E6%9C%AC-demo-%E7%9A%84%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7")
  11. 踩坑清单(联调必读)
  12. 参考资料

1. 它是什么,适合做什么

Live Activity(实时活动,简称 LA) 是 Apple 在 iOS 16.1 引入的系统级能力。它让 App 能在锁屏和**灵动岛(Dynamic Island)**上持续展示「正在进行中」的任务状态------外卖配送进度、比赛比分、打车 ETA 等。

和传统通知的区别:

维度 普通推送通知 Live Activity
展示时长 弹出后很快消失 最长在灵动岛保留 8 小时,锁屏最长 12 小时
信息密度 标题 + 正文 自定义 SwiftUI 布局,可展示结构化数据
更新方式 每次一条新通知 同一条活动原地刷新,系统做过渡动画
用户操作 点开进 App 可点击灵动岛/锁屏卡片直接跳转

Apple 官方设计原则:只展示最关键的信息,保持简洁 ;更多细节留给用户点击后进入 App 查看。(参见 Human Interface Guidelines - Live Activities

本 Demo 以足球比赛实时比分为场景:主队/客队队徽、比分、比赛时钟、赛事标题,覆盖锁屏卡片和灵动岛三种形态(紧凑 / 展开 / 最小)。


2. 系统架构一图看懂

Live Activity 横跨两个 Target,理解这个分工是避免「改了代码但界面不变」的第一步。

flowchart TB subgraph MainApp[&#34;主 App Target(LiveActivityDemo)&#34;] AK[&#34;ActivityKit API<br/>request / update / end&#34;] Token[&#34;Token 监听 & 上报服务器&#34;] Cache[&#34;TeamBadgeCache<br/>网络图片预下载&#34;] Delegate[&#34;AppDelegate<br/>APNs / silent push&#34;] end subgraph WidgetExt[&#34;Widget Extension Target(MyWidgetExtension)&#34;] Attr[&#34;MyWidgetAttributes<br/>数据模型定义&#34;] UI[&#34;MyWidgetLiveActivity<br/>锁屏 + 灵动岛 SwiftUI&#34;] end subgraph System[&#34;iOS 系统&#34;] LA[&#34;Live Activity 渲染引擎&#34;] DI[&#34;Dynamic Island&#34;] LS[&#34;Lock Screen&#34;] end subgraph Server[&#34;你的后端&#34;] APNs[&#34;APNs&#34;] end AK -->|&#34;创建/更新 ContentState&#34;| System Token -->|&#34;上报 push token&#34;| Server Server -->|&#34;liveactivity 推送&#34;| APNs APNs -->|&#34;event: update/end/start&#34;| System Cache -->|&#34;App Group 共享文件&#34;| WidgetExt WidgetExt -->|&#34;ActivityConfiguration&#34;| LA LA --> DI LA --> LS Delegate -->|&#34;silent push 唤醒&#34;| AK

关键认知:

  • ActivityKit 的调用代码 (启动、更新、结束)写在主 App里。
  • UI 代码ActivityConfiguration)写在 Widget Extension里,系统渲染时只加载扩展进程。
  • Live Activity 虽然复用 WidgetKit,但不是 Widget :没有 Timeline,不走 TimelineProvider,而是靠 Activity.update 或 APNs 推送驱动刷新。
  • Widget Extension 不能访问网络 ;需要网络资源(如队徽图片)必须由主 App 预先下载到 App Group 共享目录。

3. 限制与前置条件

3.1 系统与设备

项目 说明
最低版本 iOS 16.1(本地创建/更新);iOS 17.2+ 支持 push-to-start(App 未运行也能创建)
设备 iPhone;灵动岛仅 iPhone 14 Pro 及以上「药丸屏」机型
证书 远程推送更新必须使用 APNs Auth Key(.p8),不支持旧式 push 证书

3.2 运行时限制

限制 详情
数据大小 每次传给 LA 的 payload(本地或 APNs)不超过 4KB
网络 Extension 进程不能发网络请求
动画 开发者不能自定义过渡动画,由系统控制
并发数量 设备同时活跃的 LA 数量有限(系统决定,超额会创建失败)
存活时长 灵动岛最多 8 小时 ;结束后锁屏最多再保留 4 小时(合计最长约 12 小时)
启动时机 仅 App 在前台 时才能本地 Activity.request;前后台均可 update / end

3.3 权限开关

swift 复制代码
// 检查用户是否允许 Live Activity
ActivityAuthorizationInfo().areActivitiesEnabled

// 监听权限变化
for await enabled in ActivityAuthorizationInfo().activityEnablementUpdates { ... }

用户可在 设置 → 通知 → 你的 App → 实时活动 中关闭。高频推送场景还需开启「更频繁的实时活动更新」,对应 Info.plist 的 NSSupportsLiveActivitiesFrequentUpdates = YES


4. 从零到跑通:开发流程

下面是从零接入的完整 Checklist,按顺序做基本不会走弯路。

flowchart LR A[&#34;① 配置 Info.plist&#34;] --> B[&#34;② 创建 Widget Extension&#34;] B --> C[&#34;③ 定义 ActivityAttributes&#34;] C --> D[&#34;④ 编写 UI(锁屏+灵动岛)&#34;] D --> E[&#34;⑤ 主 App 调用 request/update/end&#34;] E --> F[&#34;⑥ 配置 APNs + Token 上报&#34;] F --> G[&#34;⑦ 联调推送&#34;]

步骤 ①:主 App Info.plist 配置

xml 复制代码
<!-- 必须 -->
<key>NSSupportsLiveActivities</key>
<true/>

<!-- 需要高频推送更新时(如比赛时钟每分钟刷新) -->
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
<true/>

<!-- 需要后台 silent push 唤醒时 -->
<key>UIBackgroundModes</key>
<array>
    <string>remote-notification</string>
</array>

步骤 ②:创建 Widget Extension

Xcode → File → New → Target → Widget Extension ,勾选 Include Live Activity

创建后你会得到:

  • MyWidgetBundle.swift --- 扩展入口,注册 Widget 和 Live Activity
  • MyWidgetLiveActivity.swift --- Live Activity UI 模板

确保 MyWidgetAttributes 类型对主 App Target 和 Widget Extension Target 都可见 (本 Demo 将模型定义在 Extension 里,主 App import MyWidgetExtension)。

步骤 ③:配置 App Group(如需共享资源)

如果 LA 需要展示网络图片、共享缓存等:

  1. Apple Developer 后台注册 App Group(如 group.com.gate.liveactivitydemo
  2. 主 App 和 Widget Extension 的 Signing & Capabilities 都勾选同一个 Group
  3. 主 App 负责下载写入,Extension 只读

步骤 ④~⑦

见下文各章节。本 Demo 对应文件:

职责 文件
数据模型 + UI MyWidget/MyWidgetLiveActivity.swift
共享视图组件 LiveActivityDemo/ViewUtils.swift
ActivityKit 调用收敛 LiveActivityDemo/LiveActivityUtils.swift
App 启动 & 推送入口 LiveActivityDemo/LiveActivityDemoApp.swift
调试页面 LiveActivityDemo/Home.swift

5. 数据模型设计

所有 Live Activity 的数据模型都遵循 ActivityAttributes 协议,分为两层:

swift 复制代码
public struct MyWidgetAttributes: ActivityAttributes {

    // 可变状态:每次 update / APNs 推送都会更新这部分
    public struct ContentState: Codable, Hashable {
        public var homeScore: Int
        public var awayScore: Int
        public var minuteText: String      // 如 "75′"
        public var aggregateLine: String   // 如 "首回合 4-5"
    }

    // 固定属性:创建时写入,之后一般不变
    public var appName: String
    public var matchStageTitle: String
    public var homeTeamName: String
    public var awayTeamName: String
    public var homeTeamLogoURL: String   // 只传 URL 字符串,不传图片 Data
    public var awayTeamLogoURL: String
}

设计原则:

放哪里 放什么 原因
ContentState 比分、时钟、进度百分比等频繁变化的数据 推送 payload 的 content-state 只更新这部分
Attributes 队名、赛事标题、订单号等创建后不变的信息 push-to-start 的 attributes 字段对应这里
不放 LA 里 图片二进制、大段文本 4KB 限制 + Extension 不能联网

ContentState 的字段名必须与 APNs payload 里 content-state 的 key 完全一致(区分大小写),且类型要能被 JSON 正确反序列化。


6. UI 开发:锁屏 + 灵动岛

6.1 整体结构

swift 复制代码
ActivityConfiguration(for: MyWidgetAttributes.self) { context in
    // ① 锁屏 / 通知中心 的大卡片
    lockScreenView(context)

} dynamicIsland: { context in
    DynamicIsland {
        // ② 长按展开后的分区布局
        DynamicIslandExpandedRegion(.leading) { ... }
        DynamicIslandExpandedRegion(.trailing) { ... }
        DynamicIslandExpandedRegion(.bottom) { ... }
    } compactLeading: {
        // ③ 紧凑态 · 左侧
    } compactTrailing: {
        // ④ 紧凑态 · 右侧
    } minimal: {
        // ⑤ 多个 LA 共存时的最小态
    }
}

6.2 灵动岛五种形态

flowchart TB subgraph Compact[&#34;紧凑态(未长按)&#34;] CL[&#34;compactLeading<br/>药丸左侧&#34;] CR[&#34;compactTrailing<br/>药丸右侧&#34;] end subgraph Expanded[&#34;展开态(长按)&#34;] EL[&#34;leading · 摄像头左侧&#34;] ET[&#34;trailing · 摄像头右侧&#34;] EB[&#34;bottom · 底部全宽&#34;] end subgraph Minimal[&#34;最小态&#34;] M[&#34;多个 LA 时只显示一个关键数字&#34;] end Compact -->|&#34;长按&#34;| Expanded
形态 典型用途 Demo 示例
锁屏卡片 信息最全 完整比分板:队徽 + 比分 + 时钟 + 赛事标题
展开态 长按后详情 leading 放 App 名,trailing 放赛事标题,bottom 放比分
紧凑态 药丸常驻 左:主队徽+比分;右:比分+客队徽
最小态 多活动共存 2-1

6.3 UI 注意事项

  1. 灵动岛背景恒为黑色 ,建议对子视图强制 .environment(\.colorScheme, .dark),避免浅色模式下白底白字。
  2. iOS 17+ 锁屏 必须加 .containerBackground(for: .widget),否则控制台会持续报 widget background view is missing
  3. 不要放过多控件,HIG 要求简洁;交互按钮优先用 App Intents(iOS 17+)。
  4. 网络图片 :主 App 预下载到 App Group,Extension 用 UIImage(contentsOfFile:) 读取。
  5. 点击跳转:.widgetURL(URL(string: "yourscheme://path")),主 App 处理 URL Scheme。

7. 本地生命周期管理

本地模式适合 App 在前台、或不需要服务端推送的场景。三个核心 API:

stateDiagram-v2 [*] --> Active: Activity.request() Active --> Active: activity.update() Active --> Ended: activity.end() Ended --> [*]: 系统移除 UI Active --> Dismissed: 用户侧滑删除 Dismissed --> [*]

7.1 创建(仅前台)

swift 复制代码
let activity = try Activity.request(
    attributes: attributes,
    contentState: initialState,
    pushType: .token   // 需要远程推送时必须传 .token
)

7.2 更新

swift 复制代码
// 静默更新(只刷新 UI)
await activity.update(using: newState, alertConfiguration: nil)

// 带横幅提醒(如进球)
let alert = AlertConfiguration(
    title: "比分更新",
    body: "1 - 0 · 45′",
    sound: .default
)
await activity.update(using: newState, alertConfiguration: alert)

7.3 结束

swift 复制代码
await activity.end(dismissalPolicy: .immediate)   // 立即消失
// 或 .default:保留最终状态一段时间后消失

7.4 本 Demo 的收敛设计

LiveActivityUtils 把所有 ActivityKit 调用集中管理,并维护「同一时刻最多一个活跃 LA」的不变量:

方法 调用时机
observePushToStartTokens() App 启动,监听 push-to-start token 轮换
observeNewActivities() App 启动,监听系统新创建的 Activity
reconcile() App 启动 / silent push 唤醒后,收敛到单个 active
request() 用户点击「开启实时活动」
update() 本地刷新比分
end() 用户点击「结束」

原则:用时打开,不用就关。 App 退出后若 LA 未正确结束,可能导致异常;业务结束时务必调用 end()


8. 远程推送:三种 Token 与完整链路

远程推送是 Live Activity 最有价值的部分------App 被杀掉后,服务端仍能刷新锁屏和灵动岛。但这套链路比普适 APNs 复杂,核心原因是 Token 有三种,各司其职。

8.1 Token 三兄弟

Token 来源 长度(约) 用途
device_token didRegisterForRemoteNotificationsWithDeviceToken 32 字节 hex silent push 唤醒 App
push_to_start_token Activity<>.pushToStartTokenUpdates(iOS 17.2+) 较长 APNs event: startApp 未运行也能创建 LA
activity_push_token activity.pushTokenUpdates ~96 字节 hex APNs event: update / event: end,操作已有 LA

后两种 token 都会定期轮换,App 端必须循环监听并实时上报服务器。

8.2 完整生命周期

sequenceDiagram participant App as 主 App participant Server as 后端 participant APNs as Apple APNs participant System as iOS 系统 Note over App: App 启动 App->>Server: 上报 device_token App->>Server: 上报 push_to_start_token Note over Server: 用户订阅比赛 Server->>APNs: event=start → push_to_start_token APNs->>System: 创建 Live Activity alt App 在前台 System->>App: activityUpdates 流触发 App->>App: observe() → 拿到 activity_push_token else App 在后台/被杀 Server->>APNs: silent push → device_token APNs->>App: 唤醒 ~30s App->>App: reconcile() → observe() end App->>Server: 上报 activity_push_token loop 比赛进行中 Server->>APNs: event=update → activity_push_token APNs->>System: 刷新 LA(App 无需运行) end Server->>APNs: event=end → activity_push_token APNs->>System: 结束 LA App->>Server: 清理 activity_push_token

8.3 两种远程创建方案

方案 机制 优点 注意
A:push-to-start APNs event: start 直接创建 App 完全不需要运行 iOS 18 存在不分配 activity push token 的 bug;需再发 silent push 唤醒 App 拿 token
B:silent push + 本地创建(本 Demo 推荐) silent push 携带 la_create payload → App 后台 Activity.request 稳定拿到 activity push token 需要 App 安装过且用户授权过通知;唤醒窗口约 30s

方案 B 的 App 端入口:

swift 复制代码
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    guard let payload = userInfo["la_create"] as? [String: Any] else {
        completionHandler(.noData)
        return
    }
    Task {
        await LiveActivityUtils.createFromSilentPush(payload)
        completionHandler(.newData)
    }
}

8.4 Token 上报时机(生产环境 checklist)

  • App 启动 → 监听并上报 push_to_start_token
  • 每次创建 LA → 监听 pushTokenUpdates,上报 activity_push_token
  • Token 轮换时 → 重新上报(旧 token 会 410 失效)
  • LA 结束(本地 end / 用户删除 / APNs end)→ 通知服务器清理 token
  • 服务器收到 APNs 410 → 主动清理对应 token

9. APNs 推送格式与联调要点

9.1 请求头

Header Live Activity 推送 Silent Push
apns-push-type liveactivity background
apns-topic <BundleID>.push-type.liveactivity <BundleID>
apns-priority 10(关键更新)/ 5(静默 tick) 5
authorization bearer <JWT> 同左

apns-topic 是最常见的联调错误:Live Activity 推送不是 普通 Bundle ID,必须加 .push-type.liveactivity 后缀。

9.2 Payload 示例

更新比分(event: update):

json 复制代码
{
  "aps": {
    "timestamp": 1719750000,
    "event": "update",
    "content-state": {
      "homeScore": 1,
      "awayScore": 0,
      "minuteText": "45′",
      "aggregateLine": "首回合 4-5"
    },
    "alert": {
      "title": "比分更新",
      "body": "1-0 · 45′"
    }
  }
}

静默刷新时钟(无 alert,priority 5):

json 复制代码
{
  "aps": {
    "timestamp": 1719750060,
    "event": "update",
    "content-state": {
      "homeScore": 1,
      "awayScore": 0,
      "minuteText": "46′",
      "aggregateLine": "首回合 4-5"
    }
  }
}

结束(event: end):

json 复制代码
{
  "aps": {
    "timestamp": 1719753600,
    "event": "end",
    "dismissal-date": 1719753660,
    "content-state": {
      "homeScore": 2,
      "awayScore": 1,
      "minuteText": "终场",
      "aggregateLine": "结束"
    }
  }
}

关键字段说明:

字段 必填 说明
timestamp 必须是当前 Unix 时间戳,用旧值会推送失败
event start / update / end
content-state update/end 必填 key 必须与 ContentState 属性名一致
attributes start 必填 对应 MyWidgetAttributes 的固定字段
attributes-type start 必填 类型名,如 MyWidgetAttributes
alert 有则触发横幅 + 声音 + 灵动岛短暂展开
dismissal-date end 时建议 锁屏消失时间(Unix 秒)

9.3 环境匹配

App 安装方式 APNs Host
Xcode 直接安装(Debug) api.development.push.apple.com
TestFlight / App Store api.push.apple.com

Debug 包装的开发 token 打到生产环境会返回 400 BadDeviceToken,反之亦然。

9.4 常见 HTTP 响应码

状态码 原因 处理
200 成功 ---
400 BadDeviceToken token 错误或环境不匹配 重新拿 token;检查 development/production
400 TopicDisallowed apns-topic 错误 确认 .push-type.liveactivity 后缀
403 InvalidProviderToken JWT 签名错误 检查 TEAM_ID / KEY_ID / .p8 文件
410 Unregistered token 已失效 重启 App 拿新 token;服务器清理旧记录

10. 本 Demo 的调试工具

项目 demo-tools/ 目录提供两套独立可用的联调工具,避免每次手动拼 curl。

10.1 方式一:后端 Dashboard(推荐)

bash 复制代码
cd demo-tools/server
./run.sh
# 浏览器打开 http://<Mac局域网IP>:8000

流程:

css 复制代码
打开 App(真机)→ token 自动出现在 Dashboard
→ 点 [start] 创建 LA
→ 输入比分点 [update] 刷新
→ 点 [end] 结束

真机调试时把 Info.plistLADemoServerBaseURL 改成 Mac 局域网 IP。

10.2 方式二:push.sh(轻量无服务器)

bash 复制代码
# 一次性配置 .p8 证书和 TEAM_ID / KEY_ID / BUNDLE_ID

# 方案 B:silent push 创建(推荐)
./push.sh <device-token> silent-create 0 0 "0′"

# 更新比分
./push.sh <activity-push-token> update 1 0 "45′"

# 静默时钟刷新
./push.sh <activity-push-token> tick 1 0 "46′"

# 结束
./push.sh <activity-push-token> end

10.3 日志过滤

bash 复制代码
idevicesyslog | grep -E "LiveActivityDemo|MyWidgetExtension"

关键日志:

日志 含义
push-to-start token: push-to-start token 刷新
activity push token 拿到 activity push token,可以 update/end 了
reconcile: N activities found 收敛检查
sync activity → 200 token 上报服务器成功

11. 踩坑清单(联调必读)

开发阶段

问题 原因 解决
创建失败无 UI 用户关闭了实时活动权限 检查 areActivitiesEnabled;引导用户去设置开启
request 报错 App 不在前台 确保在前台调用;远程创建走 push-to-start 或 silent push
改了 UI 没变化 改的是主 App 代码 UI 必须在 Widget Extension 里改
锁屏背景警告 缺少 containerBackground iOS 17+ 加 .containerBackground(for: .widget)
队徽显示空白 Extension 不能联网 主 App 预下载到 App Group;push-to-start 创建时 App 没运行则首次可能空白,发 update 或打开 App 后恢复

推送联调

问题 原因 解决
200 但手机无反应 用了过期的 activity_push_token token 轮换后必须重新上报;从日志/Dashboard 确认最新 token
start 后无法 update 没拿到 activity_push_token App 被杀时需等 silent push 唤醒(~30s);检查 observeNewActivities 是否在启动时调用
TopicDisallowed apns-topic 写错 必须是 com.xxx.app.push-type.liveactivity
BadDeviceToken 开发/生产环境不匹配 对照安装方式和 APNS_HOST
更新很慢 未开启高频更新 Info.plist 设 NSSupportsLiveActivitiesFrequentUpdates;用户设置里开启
timestamp 推送失败 用了旧时间戳 每次请求用 date +%s 生成当前值

架构建议

  1. 集中管理 :把 request/update/end 和 token 监听收敛到一个 Utils 类,避免散落在各处。
  2. 幂等监听pushTokenUpdatesactivityStateUpdates 用 Set 去重,防止 reconcile 重复挂 Task。
  3. Token 同步读 + 流observe 时先读 activity.pushToken 保底,再订阅 pushTokenUpdates 处理轮换。
  4. 结束即清理:无论本地 end、用户删除还是推送 end,都要通知服务器清 token。
  5. 单活动约束:业务上同时只展示一场比赛时,多余 active 应主动 end,避免占满系统配额。

12. 参考资料

官方文档

社区文章

本项目


附录:快速决策树

「我该用哪种方式更新 Live Activity?」

flowchart TD Q1{&#34;App 是否在前台?&#34;} Q1 -->|是| Local[&#34;本地 Activity.update()&#34;] Q1 -->|否| Q2{&#34;LA 是否已创建?&#34;} Q2 -->|是| PushUpdate[&#34;APNs event=update<br/>→ activity_push_token&#34;] Q2 -->|否| Q3{&#34;需要 App 未安装也能创建?&#34;} Q3 -->|是| PushStart[&#34;APNs event=start<br/>→ push_to_start_token<br/>+ silent push 拿 activity token&#34;] Q3 -->|否| SilentCreate[&#34;silent push + la_create<br/>→ App 本地 request()&#34;]

文档版本:2026-06,基于 LiveActivityDemo 项目实践整理。

相关推荐
ZZH_AI项目交付3 天前
同一套前摄心率算法,为什么 iPhone 16 Pro 稳,iPhone XR 会失准?
ios·app·ai编程
凡泰AI4 天前
如何借助MCP打通企业APP内部服务:从统一调用到小程序承接
小程序·uni-app·app·mpaas·mcp·小程序容器
私人珍藏库9 天前
[Android] 会计快题库 -财会职称考试刷题学习
android·人工智能·学习·app·软件·多功能
私人珍藏库9 天前
[Android] SimpleLive-聚合抖B虎鱼网络直播
智能手机·app·工具·软件·多功能
私人珍藏库9 天前
[Android] 抖音短视频 手表版-适配手机 极简体积无广告
智能手机·app·工具·软件·多功能
ZZH_AI项目交付10 天前
端侧大模型跑进 iPhone XR:我为什么没把它做成聊天助手
人工智能·app·ai编程
私人珍藏库10 天前
[Android] Stay浏览器-无限制访问任何网站
智能手机·app·工具·软件·多功能
qq_2573795911 天前
如何卸载Edge浏览器
前端·windows·git·python·edge·app·浏览器
私人珍藏库12 天前
[Android] 美颜通 -实时视频通话直播美颜
智能手机·app·音视频·软件·多功能
私人珍藏库13 天前
[Android] 聊天记录微备份 -聊天记录备份+本地数据备份
android·人工智能·app·软件·多功能