一、背景说明

实况窗(Live View)是鸿蒙系统中一种聚焦正在进行的任务的通知形态,具有时效性、时段性、变化性的特点,典型场景包括即时配送、打车出行、航旅值机等。在最新的 HarmonyOS 7.0.0.111 版本中,系统进一步开放了实况窗的分享能力(通过 LiveView 结构体的 shareUrl 字段),实况窗开发正成为鸿蒙应用的标准配置。

本文以即时配送场景为例,基于 Live View Kit 的 liveViewManager 模块,完整实现实况窗的创建、更新与结束全生命周期流程。文中代码基于 HarmonyOS 6.0.0 Release SDK 及以上版本(API 12+),DevEco Studio 6.0.0 Release 及以上。

二、技术原理

实况窗由 liveViewManager 模块提供基础能力,核心接口共四个:

  1. isLiveViewEnabled():校验实况窗开关(设置 > 应用和元服务 > 应用名 > 实况窗)是否打开,这是创建实况窗的前提;
  2. startLiveView(liveView):创建实况窗,传入 LiveView 实例;
  3. updateLiveView(liveView):更新实况窗内容;
  4. stopLiveView(liveView):结束实况窗生命周期。

LiveView 结构体中,event 声明应用场景(如 DELIVERY 即时配送、TAXI 打车、TRAIN 高铁等),liveViewData.primary 承载主区内容,layoutData 控制扩展区模板——共 5 种样式模板:进度可视化模板(LAYOUT_TYPE_PROGRESS)、强调文本模板、左右文本模板、赛事比分模板和导航模板。

本地实况窗依赖应用进程存活。若应用需要退到后台后仍持续更新实况窗,需配合 Push Kit 云侧 REST API(operation 值 0/1/2 分别对应创建/更新/结束)。

三、实现步骤
  1. 开通权益

实况窗属于受限开放能力,需先在 AppGallery Connect 中开通推送服务权益与实况窗权益,并将 AppScope/app.json5 中的 bundleName 替换为自己已开通权益的包名。

  1. 工程配置

在 entry/src/main/module.json5 中添加网络权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}
  1. 构建上下文工具类

实况窗的 clickAction 字段需要 WantAgent 对象,实现点击实况窗拉起应用指定页面。封装工具类 ContextUtil.ets:

// utils/ContextUtil.ets
import { wantAgent, WantAgent } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN: number = 0x0000;
const TAG: string = 'ContextUtil';

export class ContextUtil {
  private static wantAgentObj: WantAgent | null = null;

  /**
   * 构建 WantAgent,用于实况窗点击后跳转到应用首页
   * abilityName 参数可在资源表中配置,此处以 EntryAbility 为例
   */
  public static async buildWantAgent(abilityName: string): Promise<WantAgent | undefined> {
    try {
      const wantAgentInfo: wantAgent.WantAgentInfo = {
        wants: [
          {
            bundleName: 'com.example.liveviewdemo',
            abilityName: abilityName
          }
        ],
        actionType: wantAgent.OperationType.START_ABILITY,
        requestCode: 0,
        actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      };
      this.wantAgentObj = await wantAgent.getWantAgent(wantAgentInfo);
      return this.wantAgentObj;
    } catch (err) {
      hilog.error(DOMAIN, TAG, 'buildWantAgent error: %{public}d %{public}s',
        (err as BusinessError).code, (err as BusinessError).message);
      return undefined;
    }
  }
}

注意:需要在文件头部补充 import { BusinessError } from '@kit.BasicServicesKit';。

  1. 实况窗控制类

封装实况窗全生命周期操作 DeliveryLiveViewUtil.ets:

// utils/DeliveryLiveViewUtil.ets
import { liveViewManager } from '@kit.LiveViewKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { ContextUtil } from './ContextUtil';

const DOMAIN: number = 0x0000;
const TAG: string = 'DeliveryLiveView';

export class DeliveryLiveViewUtil {
  private static viewId: number = 106;

  // 校验实况窗开关
  public static async isLiveViewEnabled(): Promise<boolean> {
    try {
      return await liveViewManager.isLiveViewEnabled();
    } catch (err) {
      const e = err as BusinessError;
      hilog.error(DOMAIN, TAG, 'isLiveViewEnabled error: %{public}d %{public}s', e.code, e.message);
      return false;
    }
  }

  // 创建实况窗:骑手已接单(进度可视化模板)
  public static async startLiveView(): Promise<void> {
    if (!await this.isLiveViewEnabled()) {
      hilog.warn(DOMAIN, TAG, 'live view is disabled.');
      return;
    }
    try {
      const liveView: liveViewManager.LiveView = {
        id: this.viewId,
        event: 'DELIVERY',
        isMute: false,
        liveViewData: {
          primary: {
            title: '骑手已接单',
            content: [
              { text: '距商家 ' },
              { text: '300 ', textColor: '#FF0A59F7' },
              { text: '米 | ' },
              { text: '3 ', textColor: '#FF0A59F7' },
              { text: '分钟到店' }
            ],
            keepTime: 0,
            clickAction: await ContextUtil.buildWantAgent('EntryAbility'),
            layoutData: {
              layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
              progress: 40,
              color: '#FF317AF7',
              backgroundColor: '#f7819ae0',
              indicatorType: liveViewManager.IndicatorType.INDICATOR_TYPE_UP,
              indicatorIcon: 'icon_rider.png',
              lineType: liveViewManager.LineType.LINE_TYPE_DOTTED_LINE,
              nodeIcons: ['icon_order.png', 'icon_store_white.png', 'icon_finish.png']
            }
          }
        }
      };
      const result = await liveViewManager.startLiveView(liveView);
      hilog.info(DOMAIN, TAG, 'startLiveView result: %{public}s', JSON.stringify(result));
    } catch (err) {
      const e = err as BusinessError;
      hilog.error(DOMAIN, TAG, 'startLiveView error: %{public}d %{public}s', e.code, e.message);
    }
  }

  // 更新实况窗:商品配送中(progress 推进到 70)
  public static async updateLiveView(): Promise<void> {
    try {
      const liveView: liveViewManager.LiveView = {
        id: this.viewId,
        event: 'DELIVERY',
        isMute: false,
        liveViewData: {
          primary: {
            title: '商品配送中',
            content: [
              { text: '预计 ' },
              { text: '10 ', textColor: '#FF0A59F7' },
              { text: '分钟后送达' }
            ],
            keepTime: 0,
            clickAction: await ContextUtil.buildWantAgent('EntryAbility'),
            layoutData: {
              layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
              progress: 70,
              color: '#FF317AF7',
              backgroundColor: '#f7819ae0',
              indicatorType: liveViewManager.IndicatorType.INDICATOR_TYPE_UP,
              indicatorIcon: 'icon_rider.png',
              lineType: liveViewManager.LineType.LINE_TYPE_DOTTED_LINE,
              nodeIcons: ['icon_order.png', 'icon_store_white.png', 'icon_finish.png']
            }
          }
        }
      };
      const result = await liveViewManager.updateLiveView(liveView);
      hilog.info(DOMAIN, TAG, 'updateLiveView result: %{public}s', JSON.stringify(result));
    } catch (err) {
      const e = err as BusinessError;
      hilog.error(DOMAIN, TAG, 'updateLiveView error: %{public}d %{public}s', e.code, e.message);
    }
  }

  // 结束实况窗:商品已送达
  public static async stopLiveView(): Promise<void> {
    try {
      const liveView: liveViewManager.LiveView = {
        id: this.viewId,
        event: 'DELIVERY',
        isMute: false,
        liveViewData: {
          primary: {
            title: '商品已送达',
            content: [{ text: '感谢您的订单,欢迎再次下单' }],
            keepTime: 15
          }
        }
      };
      const result = await liveViewManager.stopLiveView(liveView);
      hilog.info(DOMAIN, TAG, 'stopLiveView result: %{public}s', JSON.stringify(result));
    } catch (err) {
      const e = err as BusinessError;
      hilog.error(DOMAIN, TAG, 'stopLiveView error: %{public}d %{public}s', e.code, e.message);
    }
  }
}
  1. 页面调用

在首页放置三个按钮,分别触发创建、更新、结束:

// pages/Index.ets
import { DeliveryLiveViewUtil } from '../utils/DeliveryLiveViewUtil';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      Text('实况窗 Demo:即时配送场景')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Button('提交订单(创建实况窗)')
        .width('60%')
        .onClick(() => {
          DeliveryLiveViewUtil.startLiveView();
        })

      Button('更新进展(更新实况窗)')
        .width('60%')
        .onClick(() => {
          DeliveryLiveViewUtil.updateLiveView();
        })

      Button('确认收货(结束实况窗)')
        .width('60%')
        .onClick(() => {
          DeliveryLiveViewUtil.stopLiveView();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
  1. 资源文件

在 entry/src/main/resources/rawfile/ 目录下放置进度条指示器图标 icon_rider.png 与三个节点图标 icon_order.png、icon_store_white.png、icon_finish.png。

四、运行效果说明

真机运行后:

  1. 点击「提交订单」,状态栏与锁屏出现实况窗卡片,显示"骑手已接单",扩展区为进度条(40%),节点图标依次为订单-商家-完成;
  2. 点击「更新进展」,卡片标题变为"商品配送中",进度推进到 70%;
  3. 点击「确认收货」,实况窗进入结束态并按 keepTime: 15 留存 15 秒后消失;
  4. 结束后 15 秒留存期内不可重复创建同一 id 的实况窗。
五、技术要点
  1. 开关校验必须前置:用户未开启实况窗开关时调用 startLiveView 会返回错误码 1001(权限未开通)或直接创建失败,创建前统一调用 isLiveViewEnabled() 可以规避大部分异常。

  2. keepTime 的两种语义:创建/更新时 keepTime: 0 表示本次内容立即展示;结束实况窗时 keepTime: 15 表示结束态卡片留存 15 秒再消失,给用户一个明确的"已完成"反馈。

  3. textColor 的一致性约束:content 数组中所有设置 textColor 的对象仅能使用同一种颜色,混用多种颜色时以第一个为准,未设置时默认 #FF000000。

  4. 后台更新必须走 Push Kit:本地 updateLiveView 依赖应用进程,应用被冻结或退出后实况窗将停留在最后一次状态。生产环境推荐"本地创建 + 云侧 Push Kit 更新"的组合,Push Kit REST API 的 operation 字段 0/1/2 与端侧三个接口一一对应,liveView.id 对应云侧 activityId。

  5. 错误处理:所有接口调用都应捕获 BusinessError,常见的错误码:1001 权益未开通、1600004 用户未开启实况窗开关、1600001 内部错误。可在错误回调中引导用户去设置页打开开关。

六、扩展建议
  1. 对接分享能力:HarmonyOS 7.0.0.111 起实况窗支持分享,在 LiveView 中配置 shareUrl 字段即可让用户将订单进度分享给他人,适合电商协作收货、多人拼单等场景:
const liveView: liveViewManager.LiveView = {
  id: 106,
  event: 'DELIVERY',
  shareUrl: 'https://example.com/order/12345',
  // ...其余字段同上
};
  1. 计时器模板:取餐、运动锻炼等场景可使用 timer 字段(isCountdown、isPaused)实现卡片上的正/倒计时展示,无需应用轮询刷新。

  2. 胶囊形态:capsuleType 字段可让实况窗以胶囊形态常驻(如 CAPSULE_TYPE_TEXT 显示"待取餐"),配合 extensionData 自定义辅助区内容。

结语

本文基于 Live View Kit 的 liveViewManager 模块,用约 150 行代码实现了即时配送场景实况窗的完整生命周期(创建、更新、结束),并给出了权益开通、权限配置、WantAgent 跳转与错误处理的生产级细节。实况窗在 HarmonyOS 7 时代已从外卖打车的"专属能力"扩展到计时、取餐、运动等十类场景,且分享能力进一步打开使用面,建议开发者按业务场景尽早接入。需注意接口默认 QPS 有限制,高频更新建议合并且走 Push Kit 云侧链路。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐