【鸿蒙开发实战】HarmonyOS 实况窗教程:上传进度实时展示
HarmonyOS 实况窗教程:上传进度实时展示
一、概述
实况窗(Live View)是 HarmonyOS 提供的系统级实时信息展示能力,可将应用的进行中任务以卡片形式展示在实况窗、通知中心和锁屏上。与普通通知不同,实况窗支持实时更新进度、点击回跳应用。
本教程以「云星图」项目的图片上传场景为例,讲解:
- 实况窗的创建与进度更新 – 进度条布局 + 胶囊状态 + 序列号递增
- keepAlive 续期机制 – 防止 keepTime 到期导致实况窗消失
- 与后台长时任务联动 – 后台上传时不被系统杀进程
- WantAgent 点击回跳 – 用户点击实况窗回到应用
- 生命周期管理 – 初始化/更新/失败/关闭的完整闭环
整体架构如下:
二、前置准备
2.1 AGC 开通实况窗权益
必须在 AGC(AppGallery Connect)开通 Live View Kit 权益后才能使用。未开通时调用 startLiveView 会报错(错误码 67108900),且上架后功能会失效。
官方参考文档: 开发准备-Live View Kit(实况窗服务)-应用服务 - 华为HarmonyOS开发者
开通步骤:
- 登录 AppGallery Connect 后台
- 进入「我的项目」-> 选择目标项目 -> 「HarmonyOS 应用」
- 在「API 管理」或「增长」->「实况窗」中找到 Live View Kit
- 点击「开通」并提交场景说明文档(描述使用实况窗的业务场景)
- 等待审核(约 3 个工作日)
调试白名单: 开发阶段如果权益尚未正式开通,可以在 AGC 中申请将测试设备加入白名单,白名单设备上可直接调试实况窗功能。
Push Token 获取: 实况窗底层依赖 Push Kit 基础设施,应用启动时需获取 Push Token,否则 startLiveView 可能无法正常工作。本项目已在 EntryAbility.onCreate 中完成:
import { pushService } from '@kit.PushKit';
// EntryAbility.onCreate 中获取 Push Token
pushService.getToken().then((token: string) => {
hilog.info(DOMAIN, 'testTag', 'Push Token: %{public}s', token);
}).catch((err: BusinessError) => {
hilog.error(DOMAIN, 'testTag', 'Failed to get push token: %{public}d %{public}s', err.code, err.message);
});
参考文档: 实况窗准备工作
2.2 依赖导入
import { liveViewManager } from '@kit.LiveViewKit';
import { common, Want, WantAgent, wantAgent } from '@kit.AbilityKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';
2.3 module.json5 配置
实况窗在 module.json5 中不需要声明 requestPermissions(AGC 权益是平台级开通,不在 module.json5 中配置,申请通过后需要手动签名)。但如果实况窗展示的是后台任务进度(如本教程的上传场景),需要配置后台长时任务:
{
"module": {
"requestPermissions": [
{
// 后台长时任务必需权限
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
],
"abilities": [
{
"name": "EntryAbility",
// 声明后台模式为数据传输
"backgroundModes": ["dataTransfer"]
}
]
}
}
配置说明:
| 配置项 | 作用 |
|---|---|
KEEP_BACKGROUND_RUNNING 权限 |
允许应用在后台运行长时任务 |
backgroundModes: ["dataTransfer"] |
声明后台任务类型为数据传输,系统据此决定保活策略 |
2.4 LiveView 数据结构
实况窗的核心数据结构 LiveView 包含主体区域(primary)和胶囊区域(capsule):
三、封装实况窗控制器
将实况窗的所有操作封装为静态管理器 StarMapLiveViewController,供上传流程调用。
3.1 初始化:缓存 WantAgent
WantAgent 用于实况窗的点击回跳,在应用启动时提前构建并缓存,避免上传时异步等待:
export class StarMapLiveViewController {
private static liveView: liveViewManager.LiveView | undefined = undefined;
private static keepAliveTimer: number = -1;
private static cachedWantAgent: WantAgent | undefined = undefined;
private static provider: string = '';
private static uploadDone: boolean = false;
// 在 EntryAbility.onCreate() 中调用
static async init(context: common.UIAbilityContext): Promise<void> {
StarMapLiveViewController.cachedWantAgent = await StarMapLiveViewController.buildWantAgent(context);
console.info('[LiveView] WantAgent 已缓存');
}
private static async buildWantAgent(context: common.UIAbilityContext): Promise<WantAgent> {
const info: wantAgent.WantAgentInfo = {
wants: [{
bundleName: context.abilityInfo.bundleName,
abilityName: 'EntryAbility'
} as Want],
actionType: wantAgent.OperationType.START_ABILITIES,
requestCode: 0,
actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
return await wantAgent.getWantAgent(info);
}
}
为什么提前缓存? wantAgent.getWantAgent 是异步操作,如果在上传开始时才构建会导致实况窗创建延迟。在 onCreate 中提前构建,上传时直接使用。
在 EntryAbility.onCreate 中调用:
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
// ... 其他初始化
void StarMapLiveViewController.init(this.context);
}
3.2 创建实况窗:startUpload
上传开始时创建实况窗,展示初始进度 0/N:
static async startUpload(total: number, provider: string): Promise<void> {
StarMapLiveViewController.uploadDone = false;
StarMapLiveViewController.provider = provider;
StarMapLiveViewController.liveView = {
id: 1001,
event: 'DELIVERY',
sequence: 1,
isMute: false,
liveViewData: {
primary: {
title: '云星图上传中',
content: [{ text: `已完成 0/${total}` }],
keepTime: 15, // 15 秒后自动消失
clickAction: StarMapLiveViewController.cachedWantAgent!, // 点击回跳应用
extensionData: {
text: provider, // 显示图床厂商名
type: liveViewManager.ExtensionType.EXTENSION_TYPE_COMMON_TEXT
},
layoutData: {
layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
progress: 0, // 进度 0%
color: '#FF3880F4', // 进度条颜色(蓝)
backgroundColor: '#33FFFFFF', // 背景色
nodeIcons: ['start.png', 'ok.png'] // 起止图标
}
},
capsule: {
type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
status: 1,
icon: 'logo.png', // 应用图标
title: '云星图',
content: `0/${total}`, // 胶囊显示 0/N
backgroundColor: '#FF3497FF'
}
}
};
try {
await liveViewManager.startLiveView(StarMapLiveViewController.liveView);
StarMapLiveViewController.startKeepAlive();
console.info(`[LiveView] 开始上传 ${total} 张`);
} catch (e) {
const err = e as BusinessError;
if (err.code === 1003500006) {
// 实况窗已存在(上次未被正确关闭),直接更新
StarMapLiveViewController.liveView.sequence =
(StarMapLiveViewController.liveView.sequence ?? 1) + 1;
await liveViewManager.updateLiveView(StarMapLiveViewController.liveView);
StarMapLiveViewController.startKeepAlive();
return;
}
console.error(`[LiveView] startLiveView error: ${err.code} ${err.message}`);
StarMapLiveViewController.liveView = undefined;
}
}
创建流程:
关键设计点:
id: 1001:实况窗唯一标识,同一 id 的实况窗全局只有一个。更新和关闭都基于这个 id。event: 'DELIVERY':事件类型,系统据此选择展示模板。常用值有DELIVERY(配送/进行中场景,适合上传进度展示)、TAXI(出行场景)、FLIGHT(航班场景)等。sequence: 1:序列号,每次 updateLiveView 必须比上次大,否则系统忽略更新。keepTime: 15:实况窗存活 15 秒,到期自动消失。通过 keepAlive 续期机制保持长期显示。- 错误码
1003500006:实况窗已存在(上次未正确关闭),此时不能再次 startLiveView,改为 updateLiveView。 nodeIcons: ['start.png', 'ok.png']:进度条两端的图标,开始和完成时分别展示。图片需放在resources/rawfile/目录下。
3.3 更新进度:updateProgress
每上传完一张图片调用一次,更新进度条、文案和胶囊:
static async updateProgress(completed: number, total: number): Promise<void> {
if (StarMapLiveViewController.liveView === undefined) return;
const done: boolean = completed >= total && total > 0;
const progress: number = total > 0 ? Math.round((completed / total) * 100) : 0;
// 1. 递增序列号(必须比上次大)
StarMapLiveViewController.liveView.sequence =
(StarMapLiveViewController.liveView.sequence ?? 1) + 1;
// 2. 更新主体区域
StarMapLiveViewController.liveView.liveViewData.primary.title =
done ? '上传完成' : '云星图上传中';
StarMapLiveViewController.liveView.liveViewData.primary.content =
[{ text: done ? `共 ${total} 张` : `已完成 ${completed}/${total}` }];
// 3. 更新进度条
const ld = StarMapLiveViewController.liveView.liveViewData.primary.layoutData
as liveViewManager.ProgressLayout;
if (ld !== undefined) {
ld.progress = progress;
}
// 4. 更新胶囊
if (StarMapLiveViewController.liveView.liveViewData.capsule !== undefined) {
StarMapLiveViewController.liveView.liveViewData.capsule.content =
done ? '上传完成' : `${completed}/${total}`;
}
// 5. 提交更新
try {
await liveViewManager.updateLiveView(StarMapLiveViewController.liveView);
} catch (e) {
const err = e as BusinessError;
console.error(`[LiveView] updateLiveView error: ${err.code} ${err.message}`);
}
if (done) {
StarMapLiveViewController.uploadDone = true;
}
}
3.4 展示失败:showFailed
上传出现失败时,切换为红色失败样式:
static async showFailed(successCount: number, failedCount: number, total: number): Promise<void> {
if (StarMapLiveViewController.liveView === undefined) return;
const remaining: number = total - successCount - failedCount;
StarMapLiveViewController.liveView.sequence =
(StarMapLiveViewController.liveView.sequence ?? 1) + 1;
// 切换失败样式
StarMapLiveViewController.liveView.liveViewData.primary.title = '上传失败';
StarMapLiveViewController.liveView.liveViewData.primary.content =
[{ text: `成功 ${successCount} 张,剩余 ${remaining} 张` }];
const ld = StarMapLiveViewController.liveView.liveViewData.primary.layoutData
as liveViewManager.ProgressLayout;
if (ld !== undefined) {
ld.progress = 100;
ld.color = '#FFFF4444'; // 红色进度条
ld.nodeIcons = ['start.png', 'fail.png']; // 失败图标
}
if (StarMapLiveViewController.liveView.liveViewData.capsule !== undefined) {
StarMapLiveViewController.liveView.liveViewData.capsule.content = '上传失败';
}
try {
await liveViewManager.updateLiveView(StarMapLiveViewController.liveView);
} catch (e) {
const err = e as BusinessError;
console.error(`[LiveView] showFailed error: ${err.code} ${err.message}`);
}
StarMapLiveViewController.uploadDone = true;
}
三种状态的样式对比:
效果展示:

3.5 keepAlive 续期机制
实况窗的 keepTime 默认只有 15 秒,到期后自动消失。对于上传这种耗时操作,需要定期续期:
// 每 10 秒续期一次,防止 keepTime 到期自动消失
private static startKeepAlive(): void {
StarMapLiveViewController.stopKeepAlive();
StarMapLiveViewController.keepAliveTimer = setInterval(() => {
if (StarMapLiveViewController.liveView !== undefined) {
// 续期 = 递增 sequence + 调用 updateLiveView
StarMapLiveViewController.liveView.sequence =
(StarMapLiveViewController.liveView.sequence ?? 1) + 1;
liveViewManager.updateLiveView(StarMapLiveViewController.liveView).catch(() => {});
}
}, 10000);
}
private static stopKeepAlive(): void {
if (StarMapLiveViewController.keepAliveTimer !== -1) {
clearInterval(StarMapLiveViewController.keepAliveTimer);
StarMapLiveViewController.keepAliveTimer = -1;
}
}
为什么是 10 秒? keepTime 设为 15 秒,续期间隔 10 秒,留 5 秒余量,确保在到期前完成续期。
续期机制的工作原理:
3.6 关闭实况窗:stop
上传完成或应用退出时关闭实况窗:
static async stop(): Promise<void> {
StarMapLiveViewController.stopKeepAlive(); // 先停止续期定时器
if (StarMapLiveViewController.liveView === undefined) return;
const lv = StarMapLiveViewController.liveView;
lv.sequence = (lv.sequence ?? 1) + 1;
lv.liveViewData.primary.title = '上传完成';
lv.liveViewData.primary.content = [{ text: '所有图片已上传' }];
lv.liveViewData.primary.keepTime = 0; // 设为 0 立即关闭
const ld = lv.liveViewData.primary.layoutData as liveViewManager.ProgressLayout;
if (ld !== undefined) {
ld.progress = 100;
}
if (lv.liveViewData.capsule !== undefined) {
lv.liveViewData.capsule.content = '上传完成';
}
try {
await liveViewManager.stopLiveView(lv);
console.info('[LiveView] 实况窗已关闭');
} catch (e) {
const err = e as BusinessError;
console.error(`[LiveView] stop error: ${err.code} ${err.message}`);
}
StarMapLiveViewController.liveView = undefined;
}
关键点: keepTime = 0 是关闭实况窗的信号,系统收到后会立即移除实况窗。
四、与上传流程集成
4.1 上传流程中的调用
在 NormalUpLoad.doBatchUpload 中,实况窗与上传循环、后台任务紧密配合:
private async doBatchUpload(config: CloudConfig) {
this.isAllUploading = true;
const idleItems = this.uploadList.filter(item => item.status === 'idle' || item.status === 'failed');
const total: number = idleItems.length;
let completed: number = 0;
let failedCount: number = 0;
let hasFailed: boolean = false;
const providerName: string = getProviderMeta(config.type)?.name ?? config.type;
// 1. 创建实况窗 + 启动后台长时任务
await StarMapLiveViewController.startUpload(total, providerName);
const wantAgent: WantAgent | undefined = StarMapLiveViewController.getWantAgent();
if (wantAgent !== undefined) {
backgroundTaskManager.startBackgroundRunning(this.context,
['dataTransfer'], wantAgent).catch(() => {});
}
// 2. 逐张上传,每完成一张更新实况窗进度
for (const item of idleItems) {
this.updateItemStatus(item.id, 'uploading', 0);
await this.uploadSingleItem(item, config);
completed++;
if (item.status === 'failed') {
failedCount++;
hasFailed = true;
break;
}
void StarMapLiveViewController.updateProgress(completed, total);
}
// 3. 停止后台长时任务
this.isAllUploading = false;
void backgroundTaskManager.stopBackgroundRunning(this.context).catch(() => {});
// 4. 根据结果更新实况窗最终状态
if (hasFailed) {
const successCount = completed - failedCount;
void StarMapLiveViewController.showFailed(successCount, failedCount, total);
}
// 全部成功时,实况窗在 onForeground 中由 stop() 关闭
// ... 省略 UI 状态更新与上传记录刷新等非实况窗相关代码
}
上传流程与实况窗的完整交互时序:
4.2 生命周期管理
实况窗需要在多个生命周期节点进行管理:
// EntryAbility.ets
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
// 应用启动时缓存 WantAgent(不启动实况窗)
void StarMapLiveViewController.init(this.context);
}
onForeground(): void {
// 应用回到前台时,如果上传已完成则关闭实况窗
if (StarMapLiveViewController.isUploadDone()) {
void StarMapLiveViewController.stop();
}
}
onDestroy(): void {
// 应用销毁时关闭实况窗
void StarMapLiveViewController.stop();
}
生命周期管理流程:
为什么在 onForeground 中关闭? 上传完成时应用可能在后台,用户看到实况窗显示"上传完成"后回到应用。此时实况窗已无意义,在 onForeground 中检测到 uploadDone = true 就关闭它,提供干净的体验。
五、后台长时任务配合
实况窗展示上传进度时,上传任务本身需要在后台持续运行。backgroundTaskManager 提供了长时任务保活能力:
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { WantAgent } from '@kit.AbilityKit';
// 开始上传时启动后台长时任务
const wantAgent: WantAgent | undefined = StarMapLiveViewController.getWantAgent();
if (wantAgent !== undefined) {
backgroundTaskManager.startBackgroundRunning(this.context,
['dataTransfer'], wantAgent).catch(() => {});
}
// 上传完成后停止
void backgroundTaskManager.stopBackgroundRunning(this.context).catch(() => {});
实况窗与后台任务的协作关系:
两者的分工:
| 后台长时任务 | 实况窗 | |
|---|---|---|
| 作用 | 防止应用进程被系统杀死 | 向用户展示任务进度 |
| 核心 API | backgroundTaskManager |
liveViewManager |
| 启动时机 | 上传开始 | 上传开始 |
| 停止时机 | 上传结束 | 上传结束 + 应用回前台 |
| 需要权限 | KEEP_BACKGROUND_RUNNING |
无(需 AGC 权益) |
| 需要配置 | backgroundModes |
无 |
六、完整 API 速查
6.1 StarMapLiveViewController 方法
| 方法 | 调用时机 | 作用 |
|---|---|---|
init(context) |
EntryAbility.onCreate |
缓存 WantAgent |
startUpload(total, provider) |
上传开始 | 创建实况窗 + 启动续期 |
updateProgress(completed, total) |
每完成一张 | 更新进度/文案/胶囊 |
showFailed(success, fail, total) |
上传有失败 | 切换红色失败样式 |
stop() |
上传完成/应用销毁 | 关闭实况窗 + 停止续期 |
isUploadDone() |
onForeground |
查询上传是否完成 |
getWantAgent() |
启动后台任务时 | 获取缓存的 WantAgent |
6.2 liveViewManager API
| API | 作用 | 注意事项 |
|---|---|---|
startLiveView(lv) |
创建实况窗 | 同 id 已存在会报 1003500006 |
updateLiveView(lv) |
更新实况窗 | sequence 必须比上次大 |
stopLiveView(lv) |
关闭实况窗 | keepTime 设为 0 |
6.3 LiveView 关键字段
| 字段 | 说明 | 必填 |
|---|---|---|
id |
实况窗唯一标识 | 是 |
sequence |
序列号,每次更新必须递增 | 是 |
liveViewData.primary.title |
主体标题 | 是 |
liveViewData.primary.content |
主体内容文案数组 | 是 |
liveViewData.primary.keepTime |
存活秒数,0 = 立即关闭 | 是 |
liveViewData.primary.clickAction |
点击回跳的 WantAgent | 是 |
liveViewData.primary.layoutData |
进度条布局(ProgressLayout) | 否 |
liveViewData.capsule |
胶囊区域 | 否 |
七、常见问题与最佳实践
7.1 常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
startLiveView 报 67108900 |
AGC 未开通 Live View Kit 权益 | 按 2.1 节步骤在 AGC 开通权益或申请白名单 |
| 实况窗更新不生效 | sequence 未递增或比上次小 | 每次更新前 sequence = sequence + 1 |
| 实况窗 15 秒后消失 | 未启动 keepAlive 续期 | startUpload 后调用 startKeepAlive |
startLiveView 报 1003500006 |
上次实况窗未正确关闭 | catch 中改为 updateLiveView |
| 后台上传被系统杀进程 | 未启动后台长时任务 | 配合 backgroundTaskManager.startBackgroundRunning |
| 点击实况窗无反应 | 未设置 clickAction 或 WantAgent 无效 | 在 onCreate 中提前 init 缓存 WantAgent |
| 应用销毁后实况窗残留 | 未在 onDestroy 中关闭 |
onDestroy 调用 stop() |
7.2 最佳实践
-
提前缓存 WantAgent:
wantAgent.getWantAgent是异步操作,在onCreate中提前构建,避免上传时延迟。 -
sequence 严格递增:系统通过 sequence 判断是否为有效更新。每次
updateLiveView和stopLiveView前都必须递增。 -
keepAlive 间隔小于 keepTime:
keepTime = 15s,续期间隔10s,留 5 秒余量确保不中断。 -
错误码 1003500006 容错:
startLiveView失败时检查是否为"已存在"错误,改为updateLiveView而非直接放弃。 -
onForeground 检测关闭:用户回到应用时如果上传已完成,及时关闭实况窗,避免残留。
-
后台任务配对:
startBackgroundRunning和stopBackgroundRunning必须严格配对,否则系统会一直保活进程。 -
失败样式区分:用红色进度条 + 失败图标让用户一眼看出失败,比纯文字更直观。
八、总结
实况窗是展示进行中任务进度的最佳方式,本教程以图片上传为例,讲解了完整实现:
- 初始化:
onCreate中缓存 WantAgent,避免异步延迟 - 创建:
startUpload构建 LiveView(进度条布局 + 胶囊),处理"已存在"错误码 - 更新:
updateProgress递增 sequence + 更新进度/文案/胶囊 - 续期:
keepAlive每 10 秒updateLiveView防止keepTime到期消失 - 失败:
showFailed切换红色进度条 + 失败图标 - 关闭:
stop设keepTime=0+stopLiveView+ 停止续期定时器 - 后台配合:
backgroundTaskManager长时任务保活,backgroundModes: ["dataTransfer"]声明后台模式
开发建议:
- 实况窗和后台长时任务成对使用,前者展示进度,后者保活进程
- sequence 每次更新必须递增,否则系统忽略
- keepAlive 间隔必须小于 keepTime,留足余量
- 在 onForeground 和 onDestroy 中都处理关闭,避免残留
- startLiveView 的 1003500006 错误码要 catch 并改为 updateLiveView
更多推荐


所有评论(0)