API 26 Release 对 Live View Kit 的导出符号进行了明确调整:除 liveViewManagerCardInfo 外,还导出了锁屏扩展与卡片扩展相关的 Ability、Context。这个变化看似只是 d.ts 导出列表,实际上提醒开发者:实况窗不是一条“发通知”接口,而是由应用进程、扩展生命周期、系统展示和服务端 Push 共同维护的长期任务。

本文聚焦第十八篇未展开的方向:如何围绕 LiveViewLockScreenExtensionAbilityLiveViewCardExtensionAbility 等扩展入口,设计生命周期、状态持久化、幂等更新、终态回收和 Release 迁移。

HarmonyOS 7 新特性(四十五)封面

一、先理解四类运行主体

一个实况窗至少涉及业务服务端、应用主进程、系统 Live View 服务和扩展 Ability。它们不会永远同时存活。

interface LiveViewOwnership {
  businessServer: 'AUTHORITATIVE_STATE'
  appProcess: 'CREATE_AND_INTERACT'
  extension: 'RENDER_AND_HANDLE_SYSTEM_ENTRY'
  system: 'DISPLAY_AND_SCHEDULE'
}

业务状态的单一事实源通常在服务端。扩展不能只读取主页面内存,否则主进程回收后就失去内容。

二、Release 导出变化要显式迁移

不要继续依赖内部路径或隐式全局符号。统一从 Kit 的公开导出入口引用,并固定 SDK 版本。

import {
  liveViewManager,
  LiveViewLockScreenExtensionAbility,
  LiveViewLockScreenExtensionContext,
  LiveViewCardExtensionAbility,
  LiveViewCardExtensionContext,
  CardInfo
} from '@kit.LiveViewKit'

上面名称来自 API 26 Release 差异文档;具体可继承方法、回调参数和设备支持范围仍应以目标 SDK 的类型声明为准。

三、用稳定 ID 连接四端状态

每项活动需要业务 ID、实况窗 ID、用户范围和状态版本。数据库不要只保存“是否创建”。

interface LiveActivityRecord {
  activityId: string
  liveViewId: string
  accountHash: string
  event: string
  stateVersion: number
  status: 'CREATING' | 'ACTIVE' | 'ENDING' | 'ENDED'
  updatedAt: number
}

activityId 由业务生成且幂等;liveViewId 用于调用 Live View;stateVersion 防止乱序更新。

四、创建采用两阶段提交

创建实况窗成功但本地未保存映射,或者本地写入成功但系统创建失败,都会留下孤儿。使用“准备—创建—确认”的状态迁移。

async function createActivity(input: CreateInput) {
  const record = await store.prepare(input.activityId)
  if (record.status === 'ACTIVE') return record

  const result = await liveViewGateway.create(input)
  return store.confirmCreated(input.activityId, result.liveViewId)
}

重试先按业务 ID 查询已有记录,不重复创建多个实况窗。

HarmonyOS 7 新特性(四十五)核心链路

五、扩展启动时只做快速恢复

系统可能在锁屏或卡片入口单独拉起扩展。回调中避免执行重型初始化、长网络请求和大图片解码。

interface ExtensionSnapshot {
  activityId: string
  version: number
  title: string
  subtitle: string
  progress?: number
  terminal: boolean
}

async function restoreForExtension(id: string): Promise<ExtensionSnapshot> {
  return snapshotStore.read(id) ?? fallbackSnapshot(id)
}

先用本地最小快照渲染,再通过系统支持的更新通道刷新。快照不保存令牌和完整订单敏感信息。

六、锁屏与卡片共享状态、分离表现

不同展示面空间、交互和隐私要求不同。共享业务状态模型,但由各自的 Presenter 生成展示数据。

interface ActivityState {
  phase: string
  etaMinutes?: number
  progress?: number
  sensitiveDetail?: string
}

function toLockScreen(s: ActivityState): CardInfoModel {
  return { title: s.phase, detail: `${s.etaMinutes ?? '--'} 分钟` }
}

function toCard(s: ActivityState): CardInfoModel {
  return { title: s.phase, detail: '点击查看详情' }
}

锁屏默认隐藏姓名、地址、手机号和订单金额等敏感内容。

七、所有更新必须版本化和幂等

移动网络会让消息重试、延迟、重复和乱序。新状态只能覆盖旧状态,终态不能被运行态复活。

function acceptUpdate(current: LiveActivityRecord, incomingVersion: number,
  incomingStatus: LiveActivityRecord['status']): boolean {
  if (current.status === 'ENDED') return false
  if (incomingVersion <= current.stateVersion) return false
  if (current.status === 'ENDING' && incomingStatus === 'ACTIVE') return false
  return true
}

客户端和服务端使用同一版本语义,并记录拒绝原因。

八、Push 更新与扩展恢复协同

官方建议在应用进程结束后通过 Push Kit 更新实况窗。服务端保存 liveViewId、Push Token、event 和业务状态;扩展读取本地快照是兜底,不应与 Push 各自维护两套状态机。

interface PushEnvelope {
  activityId: string
  version: number
  event: 'UPDATE' | 'END'
  payload: Record<string, unknown>
  sentAt: number
}

收到 Push 后先验证活动、账号、版本和时效,再更新系统展示与快照。

九、终态必须一次完成

结束可能由服务端、用户取消、超时或应用恢复触发。所有入口进入统一 endOnce

async function endOnce(activityId: string, reason: string) {
  const record = await store.beginEnding(activityId)
  if (!record || record.status === 'ENDED') return

  await liveViewGateway.end(record.liveViewId, reason)
  await store.markEnded(activityId)
  await snapshotStore.remove(activityId)
}

如果系统调用失败,保留 ENDING 以便重试;不要提前删除唯一映射。

十、深链交互要重新鉴权

用户从锁屏或卡片点击进入应用时,传递的只能是稳定业务标识和动作类型。应用恢复后重新确认账号、权限和数据状态。

interface LiveViewIntent {
  activityId: string
  action: 'OPEN_DETAIL' | 'CANCEL' | 'CONTACT'
}

async function handleIntent(intent: LiveViewIntent) {
  await auth.requireUnlockedSession()
  const latest = await activityApi.get(intent.activityId)
  return router.open(resolveDestination(latest, intent.action))
}

锁屏入口不能绕过敏感操作确认。

十一、资源与超时治理

实况窗只适合具有时段性、时效性和变化性的任务。创建后长时间不更新、终态不结束,会占用系统展示资源并伤害用户信任。

interface ActivityDeadline {
  expectedEndAt: number
  hardExpireAt: number
  staleAfterMs: number
}

function needsReconcile(d: ActivityDeadline, now: number, updatedAt: number) {
  return now > d.hardExpireAt || now - updatedAt > d.staleAfterMs
}

应用启动、账号切换和定时任务时执行对账,清理孤儿记录并结束过期活动。

十二、生命周期测试矩阵

const lifecycleCases = [
  'create-then-kill-app',
  'push-update-while-killed',
  'open-from-lock-screen',
  'duplicate-update',
  'out-of-order-update',
  'account-switch',
  'expire-without-network',
  'end-retry-after-failure'
]

每个用例验证系统展示、本地记录、服务端状态和扩展快照四处一致。真机测试锁屏、通知中心、状态栏和熄屏等实际形态。

HarmonyOS 7 新特性(四十五)检查清单

十三、上线检查清单

  • 已迁移到 API 26 Release 的公开导出符号;
  • 主进程、扩展、系统和服务端职责明确;
  • 活动 ID、实况窗 ID、账号与版本映射可追踪;
  • 创建与结束操作幂等;
  • 扩展启动不依赖主进程内存;
  • 锁屏默认隐藏敏感信息;
  • Push 与本地快照使用同一状态版本;
  • 乱序、重复和终态回退被拒绝;
  • 深链进入后重新鉴权;
  • 过期、孤儿和失败结束有对账任务。

结语

API 26 Release 的导出调整让锁屏扩展和卡片扩展的角色更清晰,也把实况窗的工程难点暴露出来:真正需要管理的是跨进程、跨入口、跨网络的长期状态。以服务端为事实源,以版本化快照支撑扩展恢复,再用幂等创建、更新和结束守住生命周期,实况窗才能在应用被回收之后仍保持准确。

官方参考

  • Live View Kit API 26 Release 差异:https://developer.huawei.com/consumer/cn/doc/harmonyos-releases/js-apidiff-liveviewkit-7003
  • Live View Kit 产品介绍:https://developer.huawei.com/consumer/cn/sdk/live-view-kit
  • 通过 Push Kit 更新实况窗:https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/liveview-update-by-push
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐