在这里插入图片描述

1 -> 概述

实况窗(Live View)是 HarmonyOS 推出的一种全新的通知服务形态,旨在帮助用户聚焦正在进行的任务,方便快速查看和即时处理。与传统的单点通知不同,实况窗具有时段性、时效性和变化性三大核心特点。所谓时段性,指的是事件或服务需要持续一段时间,有明确的开始和结束,比如打车从叫车到下车、外卖从下单到送达,整个过程都在实况窗的覆盖范围内;时效性则强调信息在特定时间段内对用户有价值,例如外卖配送中的实时位置更新;变化性要求展示内容需要动态更新,确保用户始终看到最新的服务状态。

在形态设计上,实况窗主要分为两种呈现方式:胶囊态卡片态。胶囊态位于状态栏区域,以极简的方式展示关键信息,不打断用户当前的操作界面;卡片态则在通知中心或锁屏界面展开,承载更丰富的内容,用户可以通过点击胶囊展开卡片,再点击卡片快速进入应用落地页。这种逐层递进的信息架构,既保证了信息的轻量可达,又为深度交互预留了空间。

Live View Kit 作为实况窗服务的核心 API 模块,为开发者提供了一系列基础能力,包括创建实况窗、更新实况窗内容、结束实况窗、获取活动实况窗以及检查实况窗开关状态等。本文重点探讨其中一项重要能力——进度胶囊中的副文本显示功能,并结合代码示例进行详细解析。

2 -> 实况窗的服务范围与接入前提

在深入技术实现之前,有必要了解实况窗的服务范围与接入要求,这对开发者规划功能落地具有指导意义。

2.1 -> 设备与系统要求

实况窗特性与设备硬件完全解耦,面向 HarmonyOS 5 及以上的全量 Phone 和 Tablet 设备开放。目前仅支持中国境内(不包含香港特别行政区、澳门特别行政区、中国台湾地区),海外设备暂不支持。

2.2 -> 接入场景类型

Live View Kit 当前覆盖 12 种典型服务场景,每种场景对应固定的 EVENT 取值:

场景类型 EVENT取值 典型场景
出行打车 TAXI 司机接驾等待、行程剩余距离/时间
即时配送 DELIVERY 外卖、生鲜、同城配送进度
航班 FLIGHT 登机、起飞、延误、到达通知
高铁/火车 TRAIN 检票口、座位号、运行状态
排队 QUEUE 办事大厅、医院、银行叫号
取餐 PICK_UP 餐品制作进度、取餐提醒
赛事比分 SCORE 比赛双方成绩实时更新
共享租赁 RENT 租赁时长与费用展示
计时 TIMER 正计时或倒计时
订阅计时 SUBSCRIBE_TIMER 开售倒计时提醒
运动锻炼 WORKOUT 运动时长与进度
导航 NAVIGATION 步行、骑行、车辆导航指引

开发者在接入时需要选择对应的场景类型,并在创建实况窗时配置相应的 EVENT 参数。

2.3 -> 准入原则

实况窗并非任意场景均可接入,开发者需遵循以下准入原则:

  • 用户关注:该活动场景是用户非常关注且需要反复查看或快捷操作的
  • 时长限制:活动有明确的开始和结束时间,总时长最长不超过 8 小时
  • 用户预期:用户对接收到该活动的实况窗通知有明确预期,通常为用户主动行为触发
  • 非营销:确保展示内容对用户有足够的价值,不可用于营销、广告场景

2.4 -> 权限申请流程

实况窗目前仍处于 Beta 阶段,优先对满足场景要求的应用开放申请。正式开发前,开发者需要完成以下步骤:

  1. 在 AppGallery Connect 中进入“增长 > 推送服务 > 实况窗”申请页面
  2. 点击“立即开通”,若应用月活数 ≥1000 且为已上架应用,进入正式权限申请页面
  3. 提供企业名称、应用名称、应用包名、Client ID 等信息进行审核

若在 AGC 后台未找到申请入口,可发送邮件至 hwpush@huawei.com 反馈,通常 5 个工作日内会收到回复。

3 -> liveViewManager 核心 API 详解

liveViewManager 是 Live View Kit 的核心接口模块,提供实况窗的创建、更新、结束和状态检查等基础能力。

3.1 -> 模块导入

在 ArkTS 代码中,首先需要导入 liveViewManager 模块:

import { liveViewManager } from '@kit.LiveViewKit';

该模块在 Phone 和 Tablet 设备中可正常调用,在其他设备类型中无效果。所有接口仅可在 Stage 模型下使用,系统能力标识为 SystemCapability.LiveView.LiveViewService

3.2 -> 检查实况窗开关状态

在创建实况窗之前,建议先检查设备上实况窗功能是否已开启:

import { liveViewManager } from '@kit.LiveViewKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';

try {
  liveViewManager.isLiveViewEnabled().then((isEnabled: boolean) => {
    hilog.info(0x0000, 'testTag', '实况窗开关状态: %{public}s', isEnabled);
    if (!isEnabled) {
      // 可提示用户开启实况窗功能
    }
  }).catch((err: BusinessError) => {
    hilog.error(0x0000, 'testTag', '检查实况窗状态失败: %{public}d %{public}s', 
      err.code, err.message);
  });
} catch (err) {
  let e: BusinessError = err as BusinessError;
  hilog.error(0x0000, 'testTag', '检查实况窗状态异常: %{public}d %{public}s', 
    e.code, e.message);
}

该方法返回 Promise 类型的布尔值,true 表示实况窗开关开启,false 表示关闭。可能抛出的错误码包括 1003500001(系统内部错误)、1003500002(序列化错误)、1003500003(连接服务失败)。

3.3 -> 创建实况窗

创建实况窗使用 startLiveView 方法,传入 LiveView 类型的参数:

import { liveViewManager } from '@kit.LiveViewKit';

// 构建实况窗对象
const liveView: LiveView = {
  id: 'delivery_order_001',           // 唯一标识
  event: 'DELIVERY',                   // 场景类型
  title: '您的订单正在配送中',           // 主标题
  subTitle: '预计 15 分钟后送达',       // 副标题
  progressCapsule: {
    current: 60,                       // 当前进度值
    total: 100,                        // 进度总值
    subText: '即将到达'                // 🔥 副文本(本章核心内容)
  }
};

// 创建实况窗
liveViewManager.startLiveView(liveView)
  .then(() => {
    console.info('实况窗创建成功');
  })
  .catch((err: BusinessError) => {
    console.error(`实况窗创建失败: ${err.code} - ${err.message}`);
  });

需要特别注意的是,同一 id 在同一时刻只能创建一个实况窗。当该 id 的实况窗结束后,Live View Kit 可以通过该 id 再次创建新窗,但 Push Kit 在 12 小时内无法使用该 id 再次创建。这意味着如果您的业务需要通过 Push Kit 通道远程更新实况窗,每个 id 的有效窗口为 12 小时,超过时限需要重新规划或使用新的 id

3.4 -> 更新实况窗内容

在实况窗的生命周期内,可以通过 updateLiveView 方法更新其展示内容:

liveViewManager.updateLiveView({
  id: 'delivery_order_001',
  progressCapsule: {
    current: 85,
    total: 100,
    subText: '骑手距您 500 米'          // 动态更新副文本
  },
  subTitle: '预计 3 分钟后送达'         // 同步更新副标题
}).then(() => {
  console.info('实况窗更新成功');
});

本地更新时,也可通过 liveViewManager.getActiveLiveView 函数获取当前活动的 LiveView 实例后再进行更新。这种方式适用于需要获取当前实况窗完整状态再进行增量更新的场景。

3.5 -> 结束实况窗

任务完成后,应及时结束实况窗以释放系统资源:

liveViewManager.stopLiveView('delivery_order_001')
  .then(() => {
    console.info('实况窗已结束');
  });

建议使用 stopLiveView 方法而非依赖系统自动清理,以确保资源及时释放。结束后的实况窗会进入约 15 秒的留存期,之后同一 id 方可重新使用。

4 -> 进度胶囊副文本功能深度解析

4.1 -> 什么是 progressCapsule

progressCapsule 是实况窗中用于展示进度信息的功能模块。按照实况窗的设计规范,胶囊态位于状态栏区域,显示极简信息;卡片态在通知中心或锁屏界面展开,展示更丰富的内容。progressCapsule 主要服务于卡片态中的进度展示场景,如外卖配送进度、文件传输进度、排队等待进度等。

在实况窗的多种模板中,进度可视化模板是专门用于配送、传输等场景的核心模板之一。根据开发实践,实况窗共支持 5 种卡片形态模板,分别是:进度可视化模板、强调文本模板、左右文本模板、赛事比分模板和导航模板。其中进度可视化模板对 progressCapsule 的支持最为全面。

4.2 -> 副文本(subText)的作用与设计规范

subTextprogressCapsule 对象中的一个可选字段,用于在进度条附近显示辅助性的文本信息。在进度可视化场景中,主进度数值(current/total)展示了量化进展,而副文本则用于补充说明当前的定性状态。

举例来说,在即时配送场景中,配送进度可能已经完成了 80%(current: 80, total: 100),此时 subText 设置为“骑手距您 500 米”,能够给用户提供比“80%”更直观的位置感知。在文件传输场景中,主进度显示“12MB / 15MB”,副文本显示“正在校验文件完整性”,让用户了解当前阶段任务的具体操作。

根据现有的开发实践,信息密度控制方面存在一些设计建议:主文本通常不超过 20 字,副文本在横屏或折叠屏设备上会有更充裕的显示空间。开发者在设计副文本时应遵循“简洁、即时、有价值”的原则,避免使用过长的句子或营销性内容。

4.3 -> progressCapsule 完整字段解析

progressCapsule 的数据结构包含了以下关键字段:

字段 类型 必填 说明
current number 当前进度值
total number 进度总值
subText string 辅助说明文本(即副文本)
unit string 进度单位,如“%”、“MB”、“km”等

subText 的使用场景非常灵活。由于胶囊态的显示空间有限,副文本主要在卡片态中展示。但在某些折叠屏或横屏设备上,副文本也可能出现在胶囊态的扩展区域中,因此开发时需要兼顾不同设备形态的适配。

4.4 -> 完整的代码实现示例

以下是一个完整的即时配送场景实况窗实现示例,重点展示 progressCapsulesubText 的配置方法:

第一步:定义进度更新接口

// 定义配送状态枚举
enum DeliveryStatus {
  WAITING_PAYMENT = '待支付',
  WAITING_MERCHANT = '待商家接单',
  WAITING_RIDER = '待骑手接单',
  RIDER_ACCEPTED = '骑手已接单',
  RIDER_ARRIVED = '骑手已到店',
  DELIVERING = '商品配送中',
  ALMOST_ARRIVED = '即将送达',
  COMPLETED = '已送达'
}

// 定义进度数据结构
interface ProgressData {
  current: number;      // 当前进度
  total: number;        // 总进度
  subText: string;      // 副文本
  status: DeliveryStatus;
}

第二步:创建实况窗工具类

import { liveViewManager } from '@kit.LiveViewKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class LiveViewUtil {
  private static instance: LiveViewUtil;
  private liveViewId: string = '';
  
  public static getInstance(): LiveViewUtil {
    if (!LiveViewUtil.instance) {
      LiveViewUtil.instance = new LiveViewUtil();
    }
    return LiveViewUtil.instance;
  }
  
  // 创建实况窗
  async createLiveView(orderId: string, initialProgress: ProgressData): Promise<boolean> {
    this.liveViewId = `delivery_${orderId}`;
    
    const liveView = {
      id: this.liveViewId,
      event: 'DELIVERY',
      title: '您的订单正在处理中',
      subTitle: this.getSubtitleByStatus(initialProgress.status),
      progressCapsule: {
        current: initialProgress.current,
        total: initialProgress.total,
        subText: initialProgress.subText,    // 🔥 关键:设置副文本
        unit: '%'
      }
    };
    
    try {
      await liveViewManager.startLiveView(liveView);
      console.info(`实况窗创建成功,ID: ${this.liveViewId}`);
      return true;
    } catch (err) {
      let error: BusinessError = err as BusinessError;
      console.error(`创建实况窗失败: ${error.code} - ${error.message}`);
      return false;
    }
  }
  
  // 更新实况窗进度和副文本
  async updateProgress(progressData: ProgressData): Promise<boolean> {
    if (!this.liveViewId) {
      console.error('未找到活动实况窗');
      return false;
    }
    
    try {
      await liveViewManager.updateLiveView({
        id: this.liveViewId,
        progressCapsule: {
          current: progressData.current,
          total: progressData.total,
          subText: progressData.subText,     // 🔥 动态更新副文本
          unit: '%'
        },
        subTitle: this.getSubtitleByStatus(progressData.status)
      });
      console.info(`进度更新: ${progressData.current}/${progressData.total}, 状态: ${progressData.subText}`);
      return true;
    } catch (err) {
      let error: BusinessError = err as BusinessError;
      console.error(`更新实况窗失败: ${error.code} - ${error.message}`);
      return false;
    }
  }
  
  // 结束实况窗
  async stopLiveView(): Promise<boolean> {
    if (!this.liveViewId) {
      return true;
    }
    
    try {
      await liveViewManager.stopLiveView(this.liveViewId);
      console.info(`实况窗已结束,ID: ${this.liveViewId}`);
      this.liveViewId = '';
      return true;
    } catch (err) {
      let error: BusinessError = err as BusinessError;
      console.error(`结束实况窗失败: ${error.code} - ${error.message}`);
      return false;
    }
  }
  
  private getSubtitleByStatus(status: DeliveryStatus): string {
    const statusMap: Record<DeliveryStatus, string> = {
      [DeliveryStatus.WAITING_PAYMENT]: '请完成支付以继续',
      [DeliveryStatus.WAITING_MERCHANT]: '商家正在准备您的订单',
      [DeliveryStatus.WAITING_RIDER]: '正在为您匹配骑手',
      [DeliveryStatus.RIDER_ACCEPTED]: '骑手已接单,准备取餐',
      [DeliveryStatus.RIDER_ARRIVED]: '骑手已到店,正在取餐',
      [DeliveryStatus.DELIVERING]: '商品正在配送中',
      [DeliveryStatus.ALMOST_ARRIVED]: '即将送达,请留意',
      [DeliveryStatus.COMPLETED]: '订单已完成,感谢使用'
    };
    return statusMap[status] || '配送进行中';
  }
}

第三步:在业务页面中调用

@Entry
@Component
struct DeliveryPage {
  @State currentProgress: number = 0;
  @State subTextValue: string = '订单已提交';
  private liveViewUtil = LiveViewUtil.getInstance();
  
  // 模拟配送流程
  private startDeliverySimulation(): void {
    // 定义配送过程中的进度变化
    const progressStages: ProgressData[] = [
      { current: 5, total: 100, subText: '订单已提交,等待商家接单', status: DeliveryStatus.WAITING_MERCHANT },
      { current: 10, total: 100, subText: '商家已接单,正在备餐', status: DeliveryStatus.WAITING_MERCHANT },
      { current: 15, total: 100, subText: '餐品制作中,预计还需 5 分钟', status: DeliveryStatus.WAITING_MERCHANT },
      { current: 25, total: 100, subText: '正在匹配骑手', status: DeliveryStatus.WAITING_RIDER },
      { current: 30, total: 100, subText: '骑手已接单,正在前往商家', status: DeliveryStatus.RIDER_ACCEPTED },
      { current: 35, total: 100, subText: '骑手已到店,正在取餐', status: DeliveryStatus.RIDER_ARRIVED },
      { current: 40, total: 100, subText: '餐品已取件,开始配送', status: DeliveryStatus.DELIVERING },
      { current: 55, total: 100, subText: '配送中,距您 2.5 公里', status: DeliveryStatus.DELIVERING },
      { current: 70, total: 100, subText: '距您 1 公里,约 3 分钟送达', status: DeliveryStatus.DELIVERING },
      { current: 85, total: 100, subText: '距您 500 米,准备接单', status: DeliveryStatus.ALMOST_ARRIVED },
      { current: 95, total: 100, subText: '即将到达,请保持电话畅通', status: DeliveryStatus.ALMOST_ARRIVED },
      { current: 100, total: 100, subText: '订单已送达,祝您用餐愉快', status: DeliveryStatus.COMPLETED }
    ];
    
    let index = 0;
    
    // 先创建实况窗
    this.liveViewUtil.createLiveView('ORDER_12345', progressStages[0]);
    
    // 定时更新进度
    const timer = setInterval(async () => {
      if (index < progressStages.length) {
        const stage = progressStages[index];
        this.currentProgress = stage.current;
        this.subTextValue = stage.subText;
        
        await this.liveViewUtil.updateProgress(stage);
        
        if (stage.status === DeliveryStatus.COMPLETED) {
          clearInterval(timer);
          // 订单完成后可稍作延迟再结束实况窗,给用户查看时间
          setTimeout(() => {
            this.liveViewUtil.stopLiveView();
          }, 3000);
        }
        
        index++;
      } else {
        clearInterval(timer);
      }
    }, 4000); // 每 4 秒更新一次
  }
  
  build() {
    Column() {
      Text('配送进度')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })
      
      Text(`当前进度: ${this.currentProgress}%`)
        .fontSize(18)
        .margin({ bottom: 10 })
      
      Text(this.subTextValue)
        .fontSize(16)
        .fontColor('#666')
        .margin({ bottom: 30 })
      
      Button('模拟配送流程')
        .onClick(() => {
          this.startDeliverySimulation();
        })
    }
    .padding(20)
    .width('100%')
    .height('100%')
  }
}

5 -> 副文本在实际场景中的应用价值

subText 副文本功能的设计初衷,是通过补充性的文字信息增强进度可视化模板的表达能力。以下是几个典型场景中的具体应用方式:

1. 即时配送场景

主进度展示订单完成百分比(如 60/100),副文本则提供空间位置信息:“骑手距您 500 米”。在配送流程的不同节点,副文本可以动态变化——“商家正在备餐”、“骑手已取件”、“距您 1.2 公里”等信息递进式更新,让用户对整个配送链条有清晰的预期。

2. 文件传输场景

主进度显示已传输字节数(如 12.5MB / 25MB),副文本提供速度或剩余时间信息:“剩余约 3 分钟”或“传输速度 2.4MB/s”。在当前网络状况不佳时,副文本可以提示“网络不稳定,已开启断点续传”,有效管理用户预期。

3. 排队场景

主进度显示排队位置(如 5 / 20),副文本提供预估等待时间和安抚性信息:“预计还需 10 分钟,前方还有 3 桌”。在医院挂号、银行办理等场景中,副文本还可以加上叫号规则提示,如“过号需重新排队,请留意叫号”。

4. 运动锻炼场景

主进度显示运动量达成率(如 3.2km / 5km),副文本提供心率、配速等辅助信息:“配速 5’20",心率 142”。对于正在跑步的用户来说,实况窗可以让用户在不打断运动的情况下随时查看核心数据。

5. 赛事比分场景

主进度展示比赛时间或局分,副文本展示关键时刻描述:“第 3 节还剩 2:30,主队请求暂停”。在比分胶着的时刻,副文本可以起到“实时解说”的作用,增强观赛体验。

6 -> 开发注意事项与最佳实践

6.1 -> 副文本的信息密度控制

胶囊态可用空间有限,副文本在设计时应当遵循“简洁即高效”的原则。一般建议主文本不超过 20 字,副文本在横屏或折叠屏设备上可适当增加长度,但要避免一次性展示过多内容。如果信息量较大,可以考虑拆分为多次更新,每次聚焦一个关键信息点,而不是堆砌在一段文字中。

6.2 -> 实况窗 ID 管理策略

同一 id 在同一时刻只能创建一个实况窗。开发者在设计 ID 生成策略时需要注意以下三点:

  • 唯一性保证:建议使用业务订单号或 UUID 作为实况窗 ID,避免同一任务重复创建
  • 复用间隔:实况窗结束后,使用同一 id 创建新窗需要等待约 15 秒的系统留存期;若通过 Push Kit 通道创建,则需要等待 12 小时
  • 状态管理:在应用本地维护当前活动实况窗 ID 列表,避免无意义的重复创建或更新操作

6.3 -> 副文本的跨版本兼容性

当前实况窗 API 起始版本为 4.1.0(11),progressCapsule 中的 subText 字段在该版本中已得到支持。但随着 HarmonyOS 版本的迭代,建议开发者在应用中做版本检测:

import { deviceInfo } from '@kit.BasicServicesKit';

function checkLiveViewCapability(): boolean {
  const apiVersion = deviceInfo.getApiVersion();
  // API 版本 11 及以上完整支持 subText 功能
  return apiVersion >= 11;
}

6.4 -> 性能考量

实况窗的更新频率直接影响系统性能和功耗表现。在实际开发中,副文本内容不必过于频繁更新。建议根据业务场景设置合理的更新间隔:

  • 配送类场景:每 15-30 秒更新一次(等待骑手接单、位置追踪)
  • 计时类场景:每秒更新一次(正计时/倒计时)
  • 文件传输类:每 1-5 秒更新一次(根据文件大小调整)

根据开发实践,部分实现示例中默认每隔 15 秒自动更新一次实况窗状态,这个频率适用于大多数场景,既保证了信息的时效性,又不会对系统造成过大负担。

6.5 -> 权限与使用约束

实况窗的副文本展示内容需要遵循《实况窗设计规范》,不符合设计规范的方案将不被予以开通正式权限。需要注意,实况窗不可用于营销、广告场景,展示内容必须对用户有足够的价值。实际审核中,如果应用上线后出现违反设计规范的行为,可能会被撤回权限。因此,建议在开发初期就与实况窗产品团队保持沟通,确保设计方案符合规范要求。

6.6 -> 本地更新与远程更新的区分

Live View Kit 支持两种更新方式:本地更新和远程推送更新。二者存在重要差异:

  • 本地更新:通过 liveViewManager.updateLiveView 方法实现,仅当应用在前台运行时有效
  • 远程更新:需要通过 Push Kit 通道将更新消息推送到设备,可以实现后台实时同步

对于外卖、打车等需要云端状态同步的场景,建议采用 Push Kit 远程更新方案。具体流程为:应用创建实况窗成功后,将实况窗 id、pushToken 和场景 event 等信息保存到业务服务端;当业务状态发生变化时,服务端通过 Push Kit 通道推送更新消息,由系统代为更新实况窗内容。这种方案即使在应用被杀掉的情况下,实况窗内容仍能保持同步。

7 -> 总结

鸿蒙 6.0 Live View Kit 实况窗服务为开发者提供了一套完整、规范的实时信息展示解决方案。其中 progressCapsule 的副文本功能,作为进度可视化模板中的重要补充,极大地丰富了实况窗的信息表达层次,能够向用户提供除定量进度之外的定性状态说明。

从技术实现角度来看,副文本功能的使用门槛并不高——只需要在 progressCapsule 对象中设置 subText 字段,配合业务状态的变化进行动态更新即可。但从产品设计层面来看,如何在有限的界面空间内有效传递有价值的补充信息,考验的是开发者对业务场景的理解深度和用户体验的把握能力。

随着 HarmonyOS 生态的持续演进,Live View Kit 也在不断迭代。从最新的 HarmonyOS 6.0 版本来看,实况窗的能力边界正在逐步扩展——多设备的协同展示能力、与元服务的深度整合、更丰富的场景模板支持等新特性值得开发者持续关注。每一次更新的背后,都是华为对实时信息交互体验的深入思考和技术深耕。对于接入实况窗服务的应用而言,这不仅是提升用户使用体验的有效途径,更是融入鸿蒙生态、抢占服务触达先机的重要布局。


感谢各位大佬新作!!!

互三啦!!!
Logo

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

更多推荐