利用持握手状态改善单手布局与操作可达性,同时避免把瞬时感知结果当成身份或长期画像。

HarmonyOS 7(API 26)带来的价值不只是多一个接口,而是让应用把系统能力嵌入真实业务链路。本文以“大屏阅读应用根据左手、右手或双手持握状态调整翻页热区和工具栏位置”为主线,讨论 Multimodal Awareness Kit 的接入边界、状态建模、代码分层、异常恢复、性能观测与上线门禁。示例强调工程结构,不把尚未核实的 Beta 接口写成稳定承诺。

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

一、先确认版本边界,而不是直接复制代码

官方参考标注 HoldingHandStatus 从 API 20 起支持;HarmonyOS 7 文章应说明这是能力组合与工程实践,而不是声称 API 26 首次出现。

API 26 Beta2 变更清单只证明相关 Kit 在该版本存在新增或调整,不代表所有设备、地区和应用形态都支持同一组接口。接入前应固定 DevEco Studio、SDK、设备系统版本与签名环境,直接查看本地 d.ts、系统能力声明和错误码;官方页面与本地 SDK 不一致时,以目标构建环境和最新正式文档为准。

interface CapabilityGate {
  apiVersion: number
  systemCapability: boolean
  permissionGranted: boolean
  deviceEligible: boolean
  betaEnabled: boolean
}

function canEnter(gate: CapabilityGate): boolean {
  return gate.apiVersion >= 26 && gate.systemCapability &&
    gate.permissionGranted && gate.deviceEligible && gate.betaEnabled
}

能力不足不是异常分支,而是正常产品状态。入口隐藏、说明原因、跳转设置和降级方案必须由产品明确选择,不能只在控制台打印错误。

二、把业务目标和非目标写进任务卡

本例的成功标准不是“API 返回成功”,而是用户完成目标且状态可恢复。核心任务是:订阅持握手变化;失败时使用用户显式选择的左手或右手布局偏好。非目标包括绕过权限、在不支持设备强行模拟成功、将 Beta 能力作为唯一入口,以及为了方便调试上传原始隐私数据。

interface HoldingHandObserverSpec {
  status: string
  confidence: number
  observedAt: string
  layoutMode: string
  requestId: string
  createdAt: number
}

interface OperationResult<T> {
  ok: boolean
  value?: T
  code?: string
  retryable: boolean
}

稳定业务契约比直接暴露系统对象更重要。页面、领域逻辑和埋点只依赖应用自己的类型,平台适配层负责把它们转换为当前 API 26 SDK 的真实结构。

三、状态机是异步能力的骨架

Multimodal Awareness Kit 涉及权限、系统服务、设备或资源生命周期,回调可能乱序、重复或在页面离开后到达。显式状态机可以把“现在允许做什么”变成可测试规则。

type SessionState = 'UNKNOWN' | 'LEFT' | 'RIGHT' | 'BOTH' | 'NOT_HELD' | 'MANUAL_OVERRIDE'

interface SessionSnapshot {
  state: SessionState
  revision: number
  requestId: string
  updatedAt: number
  errorCode?: string
}

function acceptRevision(current: SessionSnapshot, incoming: number): boolean {
  return incoming > current.revision
}

任何终态都只能提交一次;用户取消后,晚到成功回调不得重新激活会话。状态迁移记录原因而非只记录结果,这样才能区分系统拒绝、业务取消、设备断开和超时。

四、建立平台适配层,隔离 Beta API 变化

不要让 ArkUI 页面直接导入大量系统类型。适配器暴露窄接口,并集中处理能力查询、权限、错误码翻译、资源释放和 SDK 差异。

interface HoldingHandObserverAdapter {
  probe(): Promise<CapabilityGate>
  start(spec: HoldingHandObserverSpec, signal: AbortSignal): Promise<OperationResult<string>>
  stop(sessionId: string, reason: string): Promise<void>
  release(): Promise<void>
}

class UnsupportedAdapter implements HoldingHandObserverAdapter {
  async probe(): Promise<CapabilityGate> {
    return { apiVersion: 0, systemCapability: false, permissionGranted: false,
      deviceEligible: false, betaEnabled: false }
  }
  async start(): Promise<OperationResult<string>> {
    return { ok: false, code: 'UNSUPPORTED', retryable: false }
  }
  async stop(): Promise<void> {}
  async release(): Promise<void> {}
}

真实 API 名称、枚举和值域应在适配器实现中依据目标 SDK 编写。文章中的接口是架构示意,避免读者复制一个未经目标环境验证的名称后误以为是系统承诺。

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

五、取消、超时和资源释放要成对出现

状态抖动会导致布局来回跳,长期记录感知结果还可能形成不必要的行为画像。因此每次调用都必须拥有取消信号、业务超时和最终释放路径,页面销毁只是触发取消的一个来源。

async function runWithTimeout<T>(
  task: (signal: AbortSignal) => Promise<T>, timeoutMs: number
): Promise<T> {
  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort('TIMEOUT'), timeoutMs)
  try {
    return await task(controller.signal)
  } finally {
    clearTimeout(timer)
  }
}

回调、订阅、文件句柄、纹理、数据库结果集和跨设备会话都要在 finally 或统一生命周期管理器中释放。只清理 UI 引用而不停止底层任务,会留下功耗、内存和隐私问题。

六、幂等与版本控制抵抗乱序结果

网络、跨设备和系统服务都可能重试。同一 requestId 的重复提交应返回已有结果;revision 较旧的结果不得覆盖新状态。终态操作需要原子化,不能“先更新 UI、稍后再尝试提交”。

class IdempotencyLedger {
  private completed = new Map<string, OperationResult<string>>()

  get(requestId: string): OperationResult<string> | undefined {
    return this.completed.get(requestId)
  }

  commit(requestId: string, result: OperationResult<string>): void {
    if (!this.completed.has(requestId)) this.completed.set(requestId, result)
  }
}

持久化台账只保存恢复所需的最小字段,并设置过期时间。用户退出账号、撤销授权或业务对象删除后,应同步清理对应恢复令牌。

七、性能优化必须从预算和分位数开始

核心观测指标为 layout_flip_count。同时记录成功率、取消率、超时率、资源峰值、功耗或温升,以及降级发生的原因。平均值不足以代表体验,至少观察 P50、P90、P99 和失败样本。

interface MetricSample {
  name: 'layout_flip_count'
  value: number
  deviceClass: string
  osVersion: string
  sdkVersion: string
  buildId: string
  degraded: boolean
}

function percentile(values: number[], p: number): number {
  const sorted = [...values].sort((a, b) => a - b)
  return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))] ?? 0
}

一次实验只改变一个主要变量。对照组与实验组使用相同数据、设备状态和操作脚本;性能提高但错误率、温升或耗电显著变差时,不应直接扩大灰度。

八、安全与隐私边界

日志使用短期哈希代替账号、设备和文件原始标识;事件只记录状态、错误码、版本和耗时。敏感内容不进入 URL、埋点、崩溃附加信息或截图。诊断包由用户主动导出,并在服务端和本地设置有效期。

interface SafeAuditEvent {
  event: string
  resourceHash: string
  state: SessionState
  revision: number
  errorCode?: string
  durationMs: number
  timestamp: number
}

function redact(rawId: string): string {
  return 'sha256:' + rawId.slice(0, 0) // 示例:实际使用安全哈希实现
}

高风险动作必须二次确认,确认页面展示的业务摘要与最终提交内容来自同一份不可变数据。授权只在用户主动触发时申请,拒绝后提供说明和可用替代路径。

九、UI 要表达真实状态,而不是制造“成功感”

加载态显示当前阶段和取消入口;可重试错误说明影响范围;终态展示可验证结果。不要在底层仍运行时把进度写成 100%,也不要把“不支持”和“暂时失败”混成一个提示。

interface UiProjection {
  title: string
  detail: string
  progress?: number
  action?: 'RETRY' | 'CANCEL' | 'OPEN_SETTINGS' | 'USE_FALLBACK'
  terminal: boolean
}

function toUi(state: SessionState): UiProjection {
  return { title: String(state), detail: '状态由领域快照投影', terminal: false }
}

读屏用户需要获得状态变化通知;颜色不能成为唯一信号;长任务要支持返回后恢复。跨窗、锁屏或跨设备入口共享同一领域快照,而不是分别维护一套业务状态。

十、降级路径也要达到可交付质量

主能力不可用时,使用用户显式选择的左手或右手布局偏好。降级不是 catch 中随便返回空对象,而是有明确触发条件、用户说明、功能边界、观测指标和恢复条件。

type DegradeReason =
  | 'UNSUPPORTED' | 'PERMISSION_DENIED' | 'TIMEOUT'
  | 'RESOURCE_PRESSURE' | 'POLICY_BLOCKED'

interface DegradeDecision {
  reason: DegradeReason
  userVisible: boolean
  retryAfterMs?: number
  preserveDraft: boolean
}

降级后仍应保存用户已完成的输入,避免要求重新操作。恢复主能力前重新查询设备、权限和业务对象的新鲜度,不能沿用旧会话句柄。

十一、测试矩阵要覆盖真实失败

建议至少执行以下专项:

  • 快速换手:记录前置条件、操作步骤、期望状态和可复现证据;
  • 桌面平放:记录前置条件、操作步骤、期望状态和可复现证据;
  • 权限拒绝:记录前置条件、操作步骤、期望状态和可复现证据;
  • 横竖屏切换:记录前置条件、操作步骤、期望状态和可复现证据;
  • 手动偏好优先:记录前置条件、操作步骤、期望状态和可复现证据;
const evidence = {
  article: 58,
  kit: 'Multimodal Awareness Kit',
  buildId: 'replace-with-real-build',
  apiVersion: 26,
  deviceModel: 'replace-with-real-device',
  scenarios: ["快速换手","桌面平放","权限拒绝","横竖屏切换","手动偏好优先"],
  result: 'PENDING_REAL_DEVICE'
}

模拟器、预览器、云真机和物理设备证据必须分开写。对官方明确不支持模拟器的能力,模拟器只能验证页面降级,不能作为能力通过证据。

十二、灰度、回滚与上线门禁

灰度维度至少包含设备型号、系统小版本、地区、应用版本和业务场景。开关默认关闭;错误率、P99、功耗或关键业务指标越线时自动停止扩大,并保留一键回退到稳定路径的能力。

interface RolloutGate {
  sampleSize: number
  successRate: number
  p99Ms: number
  criticalErrors: number
}

function canExpand(g: RolloutGate): boolean {
  return g.sampleSize >= 500 && g.successRate >= 0.98 &&
    g.criticalErrors === 0 && Number.isFinite(g.p99Ms)
}

上线说明应写清已验证设备、未验证范围、Beta 风险、降级入口和责任人。发生问题时能从 buildId、设备、会话和 revision 还原链路,而不是依赖用户口述。

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

十三、上线检查清单

  • 能力、权限和设备支持性在入口前完成门禁;
  • 业务页面只依赖 HoldingHandObserver 抽象,不直接绑定 Beta API 类型;
  • 状态机覆盖成功、取消、超时、重试与终态释放;
  • 每次 订阅持握手变化 都有稳定请求 ID 和版本号;
  • 日志不记录原始敏感数据,诊断包有用户确认和有效期;
  • 主路径失败后可以使用用户显式选择的左手或右手布局偏好;
  • 关键指标 layout_flip_count 绑定设备、系统、SDK 和构建版本;
  • 自动化、模拟环境和支持真机证据分别记录,不互相替代;

结语

Multimodal Awareness Kit 的高质量接入不在于调用次数,而在于边界清晰、状态可恢复、失败可解释、指标可验证。围绕“大屏阅读应用根据左手、右手或双手持握状态调整翻页热区和工具栏位置”建立能力门禁、领域契约、幂等状态机、资源生命周期和真机证据,才能把 HarmonyOS 7 / API 26 能力从演示代码推进到可维护产品。

官方参考

  • HarmonyOS 7 新能力一览:https://developer.huawei.com/consumer/cn/features/
  • HarmonyOS 7 / API 26 变更清单:https://developer.huawei.com/consumer/cn/doc/harmonyos-releases/changelogs-600
  • HarmonyOS SDK 能力目录:https://developer.huawei.com/consumer/cn/sdk
  • 持握手感知 API:https://developer.huawei.com/consumer/en/doc/harmonyos-references/js-apis-awareness-motion
Logo

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

更多推荐