在这里插入图片描述

每日一句正能量

一个认为只有自己正确的人,永远不会快乐。
绝对正确意味着拒绝成长、关闭倾听、无法共情。这种人常陷入人际冲突与认知僵局。快乐往往诞生于开放、谦逊与自我修正的能力之中。

摘要

摘要: HarmonyOS 6.1 推出的实况窗(Live View)特性,为开发者提供了一套系统级的实时信息展示框架。本文从架构原理出发,完整演示实况窗的创建、数据更新、交互响应流程,并详细讲解其在锁屏、通知中心、状态栏等多入口的呈现机制。通过外卖配送场景的完整代码示例,帮助开发者快速掌握 Live View 的接入与上线全链路。


一、实况窗特性概述

1.1 什么是实况窗(Live View)?

实况窗是 HarmonyOS 6.1 在系统通知体系之上构建的实时信息展示能力。与传统通知(Notification)的一次性推送不同,实况窗具有以下核心特征:

  • 持续更新:同一实况窗可在生命周期内被多次更新,用户无需打开应用即可看到最新进度;
  • 多入口呈现:同时展示在锁屏界面、通知中心、状态栏胶囊等多个系统级入口;
  • 低打扰:以静默方式更新,不触发声音或震动,避免对用户造成干扰;
  • 可交互:支持按钮点击、进度查看、快捷操作等轻量级交互。

1.2 典型应用场景

场景 展示内容 更新频率
外卖配送 商家备餐 → 骑手取餐 → 配送中 → 已送达 每 30 秒或状态变更
网约车出行 司机接单 → 距离 2km → 距离 500m → 已到达 每 5 秒或位置变更
运动健身 跑步时长、配速、消耗卡路里、心率区间 每 1-3 秒
航班出行 计划起飞 → 开始登机 → 登机结束 → 起飞 状态变更时
文件下载 下载进度、剩余时间、当前速度 每 500ms

1.3 实况窗 vs 传统通知

维度 传统通知(Notification) 实况窗(Live View)
生命周期 一次性展示,用户清除即消失 持续存在,主动更新或到期消失
更新能力 无法更新已发送内容 支持多次更新同一实例
展示位置 通知中心 + 横幅 锁屏 + 通知中心 + 状态栏胶囊
用户打扰 响铃/震动/亮屏 静默更新,无打扰
交互深度 点击打开应用 支持按钮、进度条等内联交互

二、核心架构与 API 详解

2.1 架构分层

HarmonyOS 6.1 的实况窗框架分为三层:

  • 应用层:业务应用通过 LiveViewKit 创建/更新/销毁实况窗实例;
  • 服务层:LiveViewService 负责数据校验、冲突管理、生命周期调度;
  • 系统层:SystemUI 负责将实况窗渲染到锁屏、通知中心、状态栏等位置。

2.2 核心 API 一览

实况窗能力封装在 @kit.LiveViewKit 中,核心接口如下:

import { liveView } from '@kit.LiveViewKit';
接口 作用
liveView.create(config: LiveViewConfig) 创建实况窗实例
liveView.update(id: string, content: LiveViewContent) 更新指定实况窗的内容
liveView.dismiss(id: string) 主动结束实况窗
liveView.on('click', callback) 监听实况窗点击事件
liveView.getAllLiveViews() 获取当前应用的所有实况窗

2.3 关键数据模型

// 实况窗创建配置
interface LiveViewConfig {
  id: string;                    // 唯一标识,建议格式:业务前缀 + 订单号
  type: LiveViewType;            // 类型:PROGRESS / TIMER / NAVIGATION / SCORE
  priority: LiveViewPriority;    // 优先级:LOW / DEFAULT / HIGH / CRITICAL
  expiration: number;            // 过期时间(ms),超时自动销毁
  initialContent: LiveViewContent;
}

// 实况窗内容结构
interface LiveViewContent {
  title: string;                 // 主标题
  subtitle?: string;             // 副标题
  progress?: LiveViewProgress;   // 进度信息
  buttons?: LiveViewButton[];    // 操作按钮
  icons?: LiveViewIcon[];        // 图标数组
  customData?: Record<string, string>; // 透传业务数据
}

// 进度信息
interface LiveViewProgress {
  current: number;               // 当前值
  total: number;                 // 总量
  label: string;                 // 进度描述,如"配送中"
}

// 操作按钮
interface LiveViewButton {
  id: string;                    // 按钮标识
  text: string;                  // 按钮文案
  action: string;                // 触发动作名
  style: ButtonStyle;            // 样式:DEFAULT / PRIMARY / DANGER
}

三、创建实况窗:外卖配送场景实战

3.1 场景设计

以"外卖配送"为例,实况窗需要展示以下信息:

  • 订单状态:商家备餐 → 骑手取餐 → 配送中 → 已送达
  • 配送进度:百分比进度条 + 预计送达时间
  • 快捷操作:联系骑手、查看地图、确认收货

3.2 创建实况窗代码

// services/DeliveryLiveView.ets
import { liveView, LiveViewType, LiveViewPriority } from '@kit.LiveViewKit';

export class DeliveryLiveView {
  private static readonly LIVE_VIEW_ID = 'delivery_order_';

  // 创建配送实况窗
  static async create(orderId: string, shopName: string): Promise<void> {
    const config = {
      id: this.LIVE_VIEW_ID + orderId,
      type: LiveViewType.PROGRESS,
      priority: LiveViewPriority.HIGH,
      expiration: 60 * 60 * 1000,  // 1 小时后自动过期
      initialContent: {
        title: shopName,
        subtitle: '商家正在备餐',
        progress: {
          current: 0,
          total: 100,
          label: '备餐中'
        },
        icons: [
          { src: $r('app.media.ic_shop'), position: 'leading' },
          { src: $r('app.media.ic_clock'), position: 'trailing' }
        ],
        buttons: [
          { id: 'contact_rider', text: '联系骑手', action: 'contact', style: 'DEFAULT' },
          { id: 'view_map', text: '查看地图', action: 'map', style: 'PRIMARY' }
        ],
        customData: {
          orderId: orderId,
          shopName: shopName,
          createTime: Date.now().toString()
        }
      }
    };

    try {
      await liveView.create(config);
      console.info(`[LiveView] 配送实况窗已创建: ${orderId}`);
    } catch (err) {
      console.error(`[LiveView] 创建失败: ${err.message}`);
    }
  }
}

3.3 更新实况窗内容

随着配送状态变化,应用需要持续更新实况窗:

// services/DeliveryLiveView.ets(续)

export enum DeliveryStatus {
  PREPARING = 'preparing',       // 商家备餐
  RIDER_PICKED = 'rider_picked', // 骑手已取餐
  DELIVERING = 'delivering',     // 配送中
  ARRIVED = 'arrived',           // 已送达
  COMPLETED = 'completed'        // 已完成
}

export class DeliveryLiveView {
  // ... create 方法 ...

  // 更新配送状态
  static async updateStatus(
    orderId: string,
    status: DeliveryStatus,
    progressPercent: number,
    etaMinutes?: number
  ): Promise<void> {
    const statusMap: Record<DeliveryStatus, { subtitle: string; label: string }> = {
      [DeliveryStatus.PREPARING]: { subtitle: '商家正在备餐', label: '备餐中' },
      [DeliveryStatus.RIDER_PICKED]: { subtitle: '骑手已取餐,准备配送', label: '已取餐' },
      [DeliveryStatus.DELIVERING]: { subtitle: `预计 ${etaMinutes} 分钟送达`, label: '配送中' },
      [DeliveryStatus.ARRIVED]: { subtitle: '骑手已到达,请取餐', label: '已到达' },
      [DeliveryStatus.COMPLETED]: { subtitle: '订单已完成,期待再次光临', label: '已完成' }
    };

    const info = statusMap[status];
    const content = {
      title: this.getLastShopName(orderId),
      subtitle: info.subtitle,
      progress: {
        current: progressPercent,
        total: 100,
        label: info.label
      },
      buttons: status === DeliveryStatus.DELIVERING
        ? [
            { id: 'contact_rider', text: '联系骑手', action: 'contact', style: 'DEFAULT' },
            { id: 'confirm_receipt', text: '确认收货', action: 'confirm', style: 'PRIMARY' }
          ]
        : status === DeliveryStatus.ARRIVED
          ? [
              { id: 'confirm_receipt', text: '确认收货', action: 'confirm', style: 'PRIMARY' },
              { id: 'dismiss', text: '关闭', action: 'dismiss', style: 'DEFAULT' }
            ]
          : [
              { id: 'contact_rider', text: '联系骑手', action: 'contact', style: 'DEFAULT' },
              { id: 'view_map', text: '查看地图', action: 'map', style: 'PRIMARY' }
            ]
    };

    try {
      await liveView.update(this.LIVE_VIEW_ID + orderId, content);
      console.info(`[LiveView] 状态已更新: ${status}, 进度: ${progressPercent}%`);
    } catch (err) {
      console.error(`[LiveView] 更新失败: ${err.message}`);
    }
  }

  // 结束实况窗
  static async complete(orderId: string): Promise<void> {
    try {
      await liveView.dismiss(this.LIVE_VIEW_ID + orderId);
      console.info(`[LiveView] 实况窗已结束: ${orderId}`);
    } catch (err) {
      console.error(`[LiveView] 结束失败: ${err.message}`);
    }
  }

  private static getLastShopName(orderId: string): string {
    // 实际项目中从缓存或数据库读取
    return AppStorage.get<string>('lastShopName_' + orderId) ?? '商家';
  }
}

四、数据更新时序与交互响应

4.1 数据更新时序图

在这里插入图片描述

上图展示了外卖配送场景下,从订单创建到完成的完整数据更新时序。服务端状态变更通过 Push 或轮询到达应用,应用调用 LiveViewKit 更新实况窗,SystemUI 在收到更新后刷新所有展示入口。

4.2 交互响应处理

用户在实况窗上点击按钮后,系统通过 Ability 的 onCreateonNewWant 将事件回传给应用:

// entry/src/main/ets/entryability/EntryAbility.ets
import { UIAbility, Want } from '@kit.AbilityKit';
import { liveView } from '@kit.LiveViewKit';
import { router } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want): void {
    this.handleLiveViewAction(want);
  }

  onNewWant(want: Want): void {
    this.handleLiveViewAction(want);
  }

  private handleLiveViewAction(want: Want): void {
    const action = want.parameters?.['ohos.liveview.action'] as string;
    const liveViewId = want.parameters?.['ohos.liveview.id'] as string;
    const buttonId = want.parameters?.['ohos.liveview.buttonId'] as string;
    const customData = want.parameters?.['ohos.liveview.customData'] as Record<string, string>;

    if (!action || !liveViewId) return;

    console.info(`[LiveView] 交互事件: action=${action}, button=${buttonId}`);

    switch (action) {
      case 'contact':
        this.openContactPage(customData?.orderId);
        break;
      case 'map':
        this.openMapPage(customData?.orderId);
        break;
      case 'confirm':
        this.handleConfirmReceipt(customData?.orderId, liveViewId);
        break;
      case 'dismiss':
        liveView.dismiss(liveViewId);
        break;
      default:
        // 默认打开订单详情
        this.openOrderDetail(customData?.orderId);
    }
  }

  private openContactPage(orderId?: string): void {
    router.pushUrl({ url: 'pages/ContactRider', params: { orderId } });
  }

  private openMapPage(orderId?: string): void {
    router.pushUrl({ url: 'pages/DeliveryMap', params: { orderId } });
  }

  private async handleConfirmReceipt(orderId?: string, liveViewId?: string): Promise<void> {
    // 1. 调用服务端确认收货接口
    await this.callConfirmApi(orderId);
    
    // 2. 更新实况窗为完成状态
    if (liveViewId) {
      await liveView.update(liveViewId, {
        title: '订单已完成',
        subtitle: '感谢您的使用,期待再次光临',
        progress: { current: 100, total: 100, label: '已完成' },
        buttons: [
          { id: 'rate_order', text: '评价订单', action: 'rate', style: 'PRIMARY' }
        ]
      });
      
      // 3. 5 秒后自动销毁实况窗
      setTimeout(() => {
        liveView.dismiss(liveViewId);
      }, 5000);
    }
  }

  private openOrderDetail(orderId?: string): void {
    router.pushUrl({ url: 'pages/OrderDetail', params: { orderId } });
  }

  private async callConfirmApi(orderId?: string): Promise<void> {
    // 实际项目中调用 HTTP 接口
    console.info(`[LiveView] 确认收货: ${orderId}`);
  }
}

五、多位置呈现机制

5.1 实况窗展示入口

HarmonyOS 6.1 的实况窗支持在以下四个系统入口同时呈现:

在这里插入图片描述

上图展示了同一实况窗在锁屏界面、状态栏胶囊、通知中心展开态和桌面小卡片四个位置的呈现效果。各位置会自动根据可用空间对内容进行裁剪与重排。

5.2 各位置渲染规则

展示位置 最大信息容量 交互能力 更新策略
锁屏界面 标题 + 副标题 + 进度条 + 2 个按钮 点击按钮触发 Action 实时同步
状态栏胶囊 图标 + 单行标题(最多 8 字) 点击展开通知中心 实时同步
通知中心 完整内容:标题 + 副标题 + 进度 + 按钮 全部交互可用 实时同步
桌面小卡片 标题 + 进度条 + 1 个按钮(可选) 点击打开应用 受卡片刷新频率限制

5.3 不同位置的适配建议

开发者无需为每个位置单独编写代码,SystemUI 会自动裁剪。但以下建议可提升展示效果:

  • 标题控制在 8 个汉字以内,确保状态栏胶囊能完整显示;
  • 按钮文案控制在 4 个汉字以内,避免锁屏界面按钮换行;
  • 副标题使用动态数据,如"预计 12 分钟送达"比"配送中"信息密度更高;
  • 进度条标签(label)使用状态枚举,如"备餐中 / 已取餐 / 配送中 / 已到达"。

六、完整实战:外卖配送全流程接入

在这里插入图片描述

6.1 订单创建时初始化实况窗

// pages/OrderConfirm.ets
import { DeliveryLiveView, DeliveryStatus } from '../services/DeliveryLiveView';

@Entry
@Component
struct OrderConfirm {
  private orderId: string = 'ORD' + Date.now();
  private shopName: string = '老北京炸酱面';

  async onOrderCreated(): Promise<void> {
    // 1. 创建实况窗
    await DeliveryLiveView.create(this.orderId, this.shopName);
    
    // 2. 保存商家名称到缓存
    AppStorage.setOrCreate('lastShopName_' + this.orderId, this.shopName);
    
    // 3. 启动配送状态轮询(实际项目中可用 Push 替代)
    this.startStatusPolling();
  }

  private startStatusPolling(): void {
    const pollInterval = setInterval(async () => {
      const status = await this.fetchDeliveryStatus(this.orderId);
      
      if (status.isCompleted) {
        clearInterval(pollInterval);
        await DeliveryLiveView.complete(this.orderId);
        return;
      }

      await DeliveryLiveView.updateStatus(
        this.orderId,
        status.phase as DeliveryStatus,
        status.progress,
        status.etaMinutes
      );
    }, 15000);  // 每 15 秒轮询一次
  }

  private async fetchDeliveryStatus(orderId: string): Promise<DeliveryStatusResponse> {
    // 实际项目中调用服务端接口
    return { isCompleted: false, phase: 'delivering', progress: 65, etaMinutes: 8 };
  }
}

interface DeliveryStatusResponse {
  isCompleted: boolean;
  phase: string;
  progress: number;
  etaMinutes: number;
}

6.2 服务端 Push 驱动更新(推荐方案)

相比客户端轮询,更优的方案是通过服务端 Push 触发更新:

// services/PushHandler.ets
import { push } from '@kit.PushKit';
import { DeliveryLiveView, DeliveryStatus } from './DeliveryLiveView';

export class PushHandler {
  static init(): void {
    push.on('receiveMessage', (message) => {
      const payload = JSON.parse(message.data);
      
      if (payload.type === 'delivery_status_update') {
        const { orderId, status, progress, etaMinutes } = payload;
        
        DeliveryLiveView.updateStatus(
          orderId,
          status as DeliveryStatus,
          progress,
          etaMinutes
        );
      }
    });
  }
}

七、常见问题与排障指南

7.1 实况窗不显示

可能原因 排查方法
权限未声明 检查 module.json5 是否申请 ohos.permission.LIVE_VIEW
优先级过低被挤掉 系统最多同时展示 3 个实况窗,提高 priority 至 HIGH
ID 重复导致创建失败 确保每个实况窗 ID 全局唯一
已过期自动销毁 检查 expiration 设置是否合理

7.2 更新内容不生效

  • 确认 liveView.update() 的 ID 与创建时一致;
  • 检查更新内容是否超出字段长度限制(标题 ≤ 50 字符,副标题 ≤ 100 字符);
  • 观察系统日志 hilog | grep LiveView,查看是否有渲染异常。

7.3 交互按钮无响应

  • 确认 EntryAbility 的 onCreate / onNewWant 中正确处理了 ohos.liveview.action
  • 检查 want.parameters 中是否包含预期的 buttonIdcustomData
  • 验证按钮的 action 字段与 Ability 中的 switch case 匹配。

八、性能与体验优化

8.1 更新频率控制

场景 建议更新频率 说明
外卖配送 状态变更时 + 每 30 秒 用户关注预计到达时间
网约车 每 5 秒 位置变化快,用户实时关注
运动跑步 每 1-3 秒 实时配速/心率需高频刷新
文件下载 每 500ms 进度条需流畅动画
航班动态 状态变更时 更新频率低,但准确性要求高

8.2 电量与性能优化

  • 避免后台常驻:实况窗本身不消耗电量,但驱动它更新的后台轮询会。优先使用 Push 而非轮询;
  • 批量更新:若多个字段同时变化,合并为一次 update() 调用;
  • 及时销毁:订单完成后立即调用 dismiss(),释放系统资源;
  • 过期设置合理:根据业务场景设置 expiration,避免僵尸实况窗长期占用展示位。

九、总结

本文从架构到代码,完整演示了 HarmonyOS 6.1 实况窗的接入与上线全链路:

  1. 创建阶段:使用 LiveViewConfig 定义实况窗类型、优先级、初始内容和过期时间;
  2. 更新阶段:通过 liveView.update() 在业务状态变化时同步刷新展示内容;
  3. 交互阶段:在 EntryAbility 中处理 ohos.liveview.actionbuttonId,实现按钮响应;
  4. 呈现阶段:实况窗自动在锁屏、状态栏胶囊、通知中心、桌面卡片多入口同步展示;
  5. 收尾阶段:业务完成后及时 dismiss(),避免资源浪费。

开发者行动清单:

  • module.json5 中申请 ohos.permission.LIVE_VIEW 权限;
  • 封装 LiveViewService 统一封装创建/更新/销毁逻辑;
  • 优先使用服务端 Push 驱动更新,减少客户端轮询;
  • 标题和按钮文案控制长度,适配状态栏胶囊的窄空间;
  • 在 EntryAbility 中完整处理实况窗的交互事件;
  • 为实况窗设置合理的 expiration,防止长期残留;
  • 业务完成后 3-5 秒内自动销毁实况窗,或引导用户手动关闭。

实况窗是 HarmonyOS 6.1 连接用户与应用的重要桥梁。用好这一特性,既能提升用户的任务效率,也能增强应用的品牌感知——让关键信息始终"在场"。


转载自:https://blog.csdn.net/u014727709/article/details/162937332
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

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

更多推荐