准备工作
在开始使用华为实况窗功能之前,您需要完成以下准备工作:
已参考Android SDK接入与华为厂商通道集成完成SDK和华为厂商通道的接入。
已参考实况窗设计规范依据样式模板设计您的实况窗通知范本。
已参考开通服务在AppGallery Connect中申请实况窗权限。
快速开始
完成准备工作后,您可以通过下面的示例来快速在客户端创建一个打车实况窗:
// 获取NotificationManager对象
Context context = getApplicationContext();
String channelId = "test_channel";
int activityId = 1;
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
// 创建通知通道,若已有通知通道可直接复用,无需重复创建
NotificationChannel channel = new NotificationChannel(channelId, "test_channel_name", NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
// 创建bundle保存实况窗通知信息,设置type为3,表示进度可视化类型
Bundle liveNotificationData = new Bundle();
liveNotificationData.putInt("notification.live.operation", 0);
liveNotificationData.putString("notification.live.event", "TAXI");
liveNotificationData.putInt("notification.live.type", 3);
liveNotificationData.putCharSequence("notification.live.titleOverlay", "司机正在赶来");
// 创建一个 SpannableString
SpannableString spannableString = new SpannableString("距您 1.2公里 | 5分钟");
// 设置 "1.2公里" 的颜色为蓝色(#FF317AF7)
ForegroundColorSpan colorSpan1 = new ForegroundColorSpan(Color.parseColor("#FF317AF7"));
spannableString.setSpan(colorSpan1, 3, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置 "5分钟" 的颜色为蓝色(#FF317AF7)
ForegroundColorSpan colorSpan2 = new ForegroundColorSpan(Color.parseColor("#FF317AF7"));
spannableString.setSpan(colorSpan2, 11, 14, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 将 SpannableString 用作通知内容
liveNotificationData.putCharSequence("notification.live.contentOverlay", spannableString);
// 创建bundle保存进度可视化类型的扩展参数
Bundle feature = new Bundle();
feature.putInt("notification.live.feature.progressType", 0);
ArrayList<Parcelable> nodeIcons = new ArrayList<>();
nodeIcons.add(Icon.createWithResource(context, R.drawable.icon1));
nodeIcons.add(Icon.createWithResource(context, R.drawable.icon2));
feature.putParcelableArrayList("notification.live.feature.nodeIcon", new ArrayList<>(nodeIcons));
feature.putParcelable("notification.live.feature.indicatorIcon", Icon.createWithResource(context, R.drawable.icon3));
feature.putInt("notification.live.feature.indicatorType", 1);
feature.putInt("notification.live.feature.progress", 40);
feature.putInt("notification.live.feature.progressColor", Color.parseColor("#FF317AF7"));
feature.putInt("notification.live.feature.progressBgColor", Color.parseColor("#19000000"));
feature.putInt("notification.live.feature.extendType", 3);
feature.putParcelable("notification.live.feature.extendImage", Icon.createWithResource(context, R.drawable.icon4));
// 创建bundle保存实况窗胶囊的扩展参数
Bundle capsule = new Bundle();
capsule.putInt("notification.live.capsuleStatus", 1);
capsule.putInt("notification.live.capsuleType", 1);
capsule.putParcelable("notification.live.capsuleIcon", Icon.createWithResource(context, R.drawable.icon5));
capsule.putInt("notification.live.capsuleBgColor", Color.parseColor("#FF317AF7"));
capsule.putString("notification.live.capsuleTitle", "接驾中");
capsule.putString("notification.live.capsuleContent", "预计5分钟");
capsule.putBoolean("notification.live.capsuleRemind", true);
// 将扩展参数设置到实况窗通知参数中
liveNotificationData.putBundle("notification.live.capsule", capsule);
liveNotificationData.putBundle("notification.live.feature", feature);
// 创建小图标
Icon smallIcon = Icon.createWithResource(context, R.drawable.icon6);
// 创建通知,调用addExtras添加实况窗通知信息
Notification notification = new Notification.Builder(context, channelId)
.setContentTitle("司机正在赶来")
.setContentText("距您 1.2公里 | 5分钟")
.addExtras(liveNotificationData)
.setSmallIcon(smallIcon)
.build();
// 发送实况窗通知
notificationManager.notify(activityId, notification);
客户端显示如下:
核心概念
华为实况窗是华为HMS Core推送服务提供的一种新型通知形态,能够帮助用户聚焦正在进行的任务,方便快速查看和即时处理。移动推送现已支持华为实况窗的远程更新和结束操作,为开发者提供便捷的实况窗管理能力。
什么是实况窗
实况窗是一种具有时段性、时效性、变化性特点的通知形态。在展示形态上,实况窗支持在熄屏(AOD)、锁屏、通知中心、状态栏等位置展示,主要有三种展示形式:胶囊态、卡片态、小折叠外屏展示态。
核心特点
时段性:事件或服务需要持续一段时间,有明确的开始和结束。
时效性:内容为正在进行或即时发生的事件,在特定时间段内对用户有价值。
变化性:展示的内容需要动态更新,确保用户看到最新状态。
提醒方式
实况窗和普通通知一样,能够在锁屏、通知中心等位置显示卡片。除此之外,还支持在状态栏、AOD 显示胶囊形态,以及在状态栏点击胶囊后展开悬浮卡片。多种显示方式能够将信息即时触达到用户,避免用户反复进出应用或服务的页面。在不同的显示位置中,通知中心会显示全量通知,其他显示位置则根据用户的设置及业务的重要程度进行呈现。各种显示方式的详细样式请参考文档实况通知。
通知中心的实况通知卡片会默认显示在顶部,根据活动的创建时间排序展示,最新创建的通知展示在最前面。避免滥用对用户造成打扰。
状态栏胶囊可根据业务诉求和用户需要进行显示,避免过度显示,抢占状态栏过长时间。
同一个事件活动在不同场景下仅出现一个形态,如当前事件所承载的落地页在前台,则当前无胶囊;如当前为通知中心/锁屏,则不显示胶囊。
基础交互
点击胶囊,呼出悬浮卡片,胶囊消失;点击卡片空白处,进入对应详情页。
实况窗支持的场景类型
场景类型 | EVENT取值 | 场景描述 | 适用范围 |
出行打车 | TAXI | - | 适用于网约车、出租车、拼车、顺风车等场景 |
即时配送 | DELIVERY | 指配送员将餐品、商品送达到用户指定地点的业务场景,通常在较短时间内完成配送环节 | 适用于外卖、生鲜配送、同城配送等场景 |
航班 | FLIGHT | - | 适用于用户通过航班出行或者主动关注某个航班进展的场景 |
高铁/火车 | TRAIN | - | 适用于高铁出行、火车出行的场景 |
排队 | QUEUE | 需要通过排队叫号的方式,按顺序为用户提供服务的业务场景 | 适用于办事大厅、医院、银行、餐饮等排队叫号能力场景 |
取餐 | PICK_UP | 指的是用户完成餐品/商品下单后,自行取餐或者取件的场景 | 适用于餐饮线下取餐提醒,包括餐品排队情况、制作进度、取餐提醒等 |
赛事比分 | SCORE | 展示比赛双方成绩变化情况 | 游戏赛事、体育赛事等展示比分变化情况的场景 |
共享租赁 | RENT | 用户使用临时租赁服务时,向用户展示实时租赁时长和费用等租赁状态信息的场景 | 适用于共享单车、共享充电宝、停车场临时停车等场景 |
计时 | TIMER | 用户在某个短时间段持续的正计时或任务前的倒计时场景 | 适用于专注时刻、番茄时钟、抢票倒计时提醒场景,仅限于工具类应用申请 |
实况窗样式模板
实况窗的卡片态支持固定区、辅助区、扩展区三个板块,其中固定区为必选,辅助区和扩展区为可选。
基础类型模板
基础类型模板适用于通话、传输进度、录音等场景。
进度可视化模板
进度可视化模板适用于打车、外卖等需要呈现完整进程及当前进度节点的场景。
强调文本模板
强调文本模板适用于取餐、排队等需要强调部分文本信息的场景。
左右文本模板
左右文本模板适用于高铁、航班等左右信息对称的场景。
赛事比分模板
赛事比分模板适用于体育赛事比分场景、游戏赛事比分场景等。
客户端本地创建/更新/结束实况窗
应用进程活跃时,可以通过Android原生通知API在客户端本地操作实况窗。具体步骤请参考构建本地实况窗通知,参数说明请参考实况窗通知。其中notification.live.operation
参数设置为0/1/2
时分别对应实况窗的创建/更新/结束操作。
示例代码
// 获取NotificationManager对象
Context context = getApplicationContext();
String channelId = "test_channel";
int activityId = 1;
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
// 创建通知通道,若已有通知通道可直接复用,无需重复创建
NotificationChannel channel = new NotificationChannel(channelId, "test_channel_name", NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
// 创建bundle保存实况窗通知信息,设置type为3,表示进度可视化类型
Bundle liveNotificationData = new Bundle();
liveNotificationData.putInt("notification.live.operation", 0);
liveNotificationData.putString("notification.live.event", "TAXI");
liveNotificationData.putInt("notification.live.type", 3);
liveNotificationData.putCharSequence("notification.live.titleOverlay", "司机正在赶来");
// 创建一个 SpannableString
SpannableString spannableString = new SpannableString("距您 1.2公里 | 5分钟");
// 设置 "1.2公里" 的颜色为蓝色(#FF317AF7)
ForegroundColorSpan colorSpan1 = new ForegroundColorSpan(Color.parseColor("#FF317AF7"));
spannableString.setSpan(colorSpan1, 3, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置 "5分钟" 的颜色为蓝色(#FF317AF7)
ForegroundColorSpan colorSpan2 = new ForegroundColorSpan(Color.parseColor("#FF317AF7"));
spannableString.setSpan(colorSpan2, 11, 14, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 将 SpannableString 用作通知内容
liveNotificationData.putCharSequence("notification.live.contentOverlay", spannableString);
// 创建bundle保存进度可视化类型的扩展参数
Bundle feature = new Bundle();
feature.putInt("notification.live.feature.progressType", 0);
ArrayList<Parcelable> nodeIcons = new ArrayList<>();
nodeIcons.add(Icon.createWithResource(context, R.drawable.icon1));
nodeIcons.add(Icon.createWithResource(context, R.drawable.icon2));
feature.putParcelableArrayList("notification.live.feature.nodeIcon", new ArrayList<>(nodeIcons));
feature.putParcelable("notification.live.feature.indicatorIcon", Icon.createWithResource(context, R.drawable.icon3));
feature.putInt("notification.live.feature.indicatorType", 1);
feature.putInt("notification.live.feature.progress", 40);
feature.putInt("notification.live.feature.progressColor", Color.parseColor("#FF317AF7"));
feature.putInt("notification.live.feature.progressBgColor", Color.parseColor("#19000000"));
feature.putInt("notification.live.feature.extendType", 3);
feature.putParcelable("notification.live.feature.extendImage", Icon.createWithResource(context, R.drawable.icon4));
// 创建bundle保存实况窗胶囊的扩展参数
Bundle capsule = new Bundle();
capsule.putInt("notification.live.capsuleStatus", 1);
capsule.putInt("notification.live.capsuleType", 1);
capsule.putParcelable("notification.live.capsuleIcon", Icon.createWithResource(context, R.drawable.icon5));
capsule.putInt("notification.live.capsuleBgColor", Color.parseColor("#FF317AF7"));
capsule.putString("notification.live.capsuleTitle", "接驾中");
capsule.putString("notification.live.capsuleContent", "预计5分钟");
capsule.putBoolean("notification.live.capsuleRemind", true);
// 将扩展参数设置到实况窗通知参数中
liveNotificationData.putBundle("notification.live.capsule", capsule);
liveNotificationData.putBundle("notification.live.feature", feature);
// 创建小图标
Icon smallIcon = Icon.createWithResource(context, R.drawable.icon6);
// 创建通知,调用addExtras添加实况窗通知信息
Notification notification = new Notification.Builder(context, channelId)
.setContentTitle("司机正在赶来")
.setContentText("距您 1.2公里 | 5分钟")
.addExtras(liveNotificationData)
.setSmallIcon(smallIcon)
.build();
// 发送实况窗通知
notificationManager.notify(activityId, notification);
通过移动推送接口远程更新/结束实况窗
移动推送支持通过Push接口或MassPush接口远程更新/结束实况窗。
参数设置
调用接口时注意以下关键参数:
PushType: 推送实况窗时固定设置为
NOTICE
。AndroidTargetUserType或AndroidHuaweiTargetUserType: 测试消息设置为
1
,正式消息设置为0
。AndroidHuaweiLiveNotificationPayload: 实况窗数据结构LiveNotificationPayload的JSON字符串,其中
operation
在更新/结束时分别设置为1/2
。
LiveNotificationPayload的结构如下表所示:
参数 | 是否必选 | 参数类型 | 描述 |
activityId | 是 | Integer | 实况窗唯一标识,由开发者自行生成,对应客户端
重要 发送的activityId对应的实况窗通知不存在,将限制发送该activityId的实况窗通知24小时。 |
operation | 是 | Integer | 实况窗通知操作类型:
|
event | 是 | String | 业务场景取值,必须为以下内容之一:
|
title | 否 | String | 可选,当系统不支持实况窗通知时,展示在通知栏的内容。 |
content | 否 | String | 可选,当系统不支持实况窗通知时,展示在通知栏的内容。 |
mute | 否 | Boolean | 标识消息更新是否需要提醒。
|
version | 否 | Integer | 更新实况窗通知的版本号(默认值0),大于等于0,新的实况窗通知版本号需大于当前实况窗通知版本号,否则会刷新失败。 |
activityData | 是 | Object | 实况窗通知详细数据,具体字段请参见ActivityData结构体。 |
示例代码
远程更新实况窗
PushRequest pushRequest = new PushRequest();
// 基础参数
pushRequest.setAppKey(appKey);
pushRequest.setPushType("NOTICE");
pushRequest.setDeviceType("ANDROID");
pushRequest.setTarget(target);
pushRequest.setTargetValue(targetValue);
pushRequest.setAndroidTargetUserType(1);
// 设置远程更新实况窗的参数
String updateLiveViewPayload = """
{
"activityId": 1,
"operation": 1,
"event": "TAXI",
"activityData": {
"notificationData": {
"type": 3,
"contentTitle": "司机已到达上车点",
"contentText": [
{
"text": "距您"
},
{
"text": "0.5公里",
"foregroundColor": "#FF317AF7"
},
{
"text": " | "
},
{
"text": "2分钟",
"foregroundColor": "#FF317AF7"
}
],
"clickAction": {
"actionType": 1,
"action": "xxxxxx"
},
"richProgress": {
"type": 0,
"nodeIcons": [
"res/drawable/icon1",
"res/drawable/icon2"
],
"indicatorIcon": "res/drawable/icon3",
"progress": 80,
"indicatorType": 1,
"color": "#FF317AF7",
"bgColor": "#19000000"
},
"extend": {
"type": 3,
"image": "res/drawable/icon4",
"clickAction": {
"actionType": 0
}
}
},
"capsuleData": {
"type": 1,
"status": 1,
"icon": "res/drawable/icon5",
"bgColor": "#FF317AF7",
"remind": true,
"title": "司机已到达上车点",
"content": "预计2分钟"
}
}
}
""";
pushRequest.setAndroidHuaweiLiveNotificationPayload(updateLiveViewPayload);
// 发送推送
PushResponse pushResponse = client.getAcsResponse(pushRequest);
System.out.printf("RequestId: %s, MessageId: %s\n",
pushResponse.getRequestId(), pushResponse.getMessageId());
远程结束实况窗
PushRequest pushRequest = new PushRequest();
// 基础参数
pushRequest.setAppKey(appKey);
pushRequest.setPushType("NOTICE");
pushRequest.setDeviceType("ANDROID");
pushRequest.setTarget(target);
pushRequest.setTargetValue(targetValue);
pushRequest.setAndroidTargetUserType(1);
// 设置远程结束实况窗的参数
String updateLiveViewPayload = """
{
"activityId": 1,
"operation": 2,
"event": "TAXI",
"activityData": {
"notificationData": {
"type": 3,
"contentTitle": "司机已到达上车点",
"contentText": [
{
"text": "距您"
},
{
"text": "0.5公里",
"foregroundColor": "#FF317AF7"
},
{
"text": " | "
},
{
"text": "2分钟",
"foregroundColor": "#FF317AF7"
}
],
"clickAction": {
"actionType": 1,
"action": "xxxxxx"
},
"richProgress": {
"type": 0,
"nodeIcons": [
"res/drawable/icon1",
"res/drawable/icon2"
],
"indicatorIcon": "res/drawable/icon3",
"progress": 80,
"indicatorType": 1,
"color": "#FF317AF7",
"bgColor": "#19000000"
},
"extend": {
"type": 3,
"image": "res/drawable/icon4",
"clickAction": {
"actionType": 0
}
}
},
"capsuleData": {
"type": 1,
"status": 1,
"icon": "res/drawable/icon5",
"bgColor": "#FF317AF7",
"remind": true,
"title": "司机已到达上车点",
"content": "预计2分钟"
}
}
}
""";
pushRequest.setAndroidHuaweiLiveNotificationPayload(updateLiveViewPayload);
// 发送推送
PushResponse pushResponse = client.getAcsResponse(pushRequest);
System.out.printf("RequestId: %s, MessageId: %s\n",
pushResponse.getRequestId(), pushResponse.getMessageId());
实况窗限制
实况窗支持HarmonyOS 4.0及以上的所有机型。
小折叠外屏展示态当前仅支持在HUAWEI Pocket 2设备上展示,应用可自行适配。
通过移动推送远程更新实况窗通知时,单个活动支持每5分钟最多刷新10次,每小时最多刷新60次,超过频次部分将丢弃不下发。
为了确保用户看到的内容是新的且有价值的,实况窗通知支持的最长不刷新时间为2小时,超过2小时未更新的实况窗通知,系统会认为通知结束。
问题排查
创建实况窗失败
检查是否已在AppGallery Connect中申请对应场景实况窗权限。
检查设备机型是否为HarmonyOS 4.0及以上。
检查设备通知权限是否打开。
检查是否成功创建NotificationChannel。
更新/结束实况窗失败
检查
AndroidHuaweiLiveNotificationPayload
参数中的activityId
是否与创建实况窗时的activityId
相同。检查
AndroidHuaweiLiveNotificationPayload
参数中的version
是否正确设置。检查是否触发频率限制。
根据消息ID与设备ID在移动推送控制台的排查工具->排查消息页面检查实况窗推送链路。
实况窗样式异常
检查
AndroidHuaweiLiveNotificationPayload
参数中的type
是否匹配对应模板。检查资源文件路径是否正确。