华为实况窗推送指南

准备工作

在开始使用华为实况窗功能之前,您需要完成以下准备工作:

  1. 已参考Android SDK接入华为厂商通道集成完成SDK和华为厂商通道的接入。

  2. 已参考实况窗设计规范依据样式模板设计您的实况窗通知范本。

  3. 已参考开通服务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);

客户端显示如下:

imageimage

核心概念

华为实况窗是华为HMS Core推送服务提供的一种新型通知形态,能够帮助用户聚焦正在进行的任务,方便快速查看和即时处理。移动推送现已支持华为实况窗的远程更新和结束操作,为开发者提供便捷的实况窗管理能力。

什么是实况窗

实况窗是一种具有时段性、时效性、变化性特点的通知形态。在展示形态上,实况窗支持在熄屏(AOD)、锁屏、通知中心、状态栏等位置展示,主要有三种展示形式:胶囊态、卡片态、小折叠外屏展示态。

image

核心特点

  • 时段性:事件或服务需要持续一段时间,有明确的开始和结束。

  • 时效性:内容为正在进行或即时发生的事件,在特定时间段内对用户有价值。

  • 变化性:展示的内容需要动态更新,确保用户看到最新状态。

提醒方式

实况窗和普通通知一样,能够在锁屏、通知中心等位置显示卡片。除此之外,还支持在状态栏、AOD 显示胶囊形态,以及在状态栏点击胶囊后展开悬浮卡片。多种显示方式能够将信息即时触达到用户,避免用户反复进出应用或服务的页面。在不同的显示位置中,通知中心会显示全量通知,其他显示位置则根据用户的设置及业务的重要程度进行呈现。各种显示方式的详细样式请参考文档实况通知

  1. 通知中心的实况通知卡片会默认显示在顶部,根据活动的创建时间排序展示,最新创建的通知展示在最前面。避免滥用对用户造成打扰。

  2. 状态栏胶囊可根据业务诉求和用户需要进行显示,避免过度显示,抢占状态栏过长时间。

  3. 同一个事件活动在不同场景下仅出现一个形态,如当前事件所承载的落地页在前台,则当前无胶囊;如当前为通知中心/锁屏,则不显示胶囊。

image

基础交互

image

点击胶囊,呼出悬浮卡片,胶囊消失;点击卡片空白处,进入对应详情页。

实况窗支持的场景类型

场景类型

EVENT取值

场景描述

适用范围

出行打车

TAXI

-

适用于网约车、出租车、拼车、顺风车等场景

即时配送

DELIVERY

指配送员将餐品、商品送达到用户指定地点的业务场景,通常在较短时间内完成配送环节

适用于外卖、生鲜配送、同城配送等场景

航班

FLIGHT

-

适用于用户通过航班出行或者主动关注某个航班进展的场景

高铁/火车

TRAIN

-

适用于高铁出行、火车出行的场景

排队

QUEUE

需要通过排队叫号的方式,按顺序为用户提供服务的业务场景

适用于办事大厅、医院、银行、餐饮等排队叫号能力场景

取餐

PICK_UP

指的是用户完成餐品/商品下单后,自行取餐或者取件的场景

适用于餐饮线下取餐提醒,包括餐品排队情况、制作进度、取餐提醒等

赛事比分

SCORE

展示比赛双方成绩变化情况

游戏赛事、体育赛事等展示比分变化情况的场景

共享租赁

RENT

用户使用临时租赁服务时,向用户展示实时租赁时长和费用等租赁状态信息的场景

适用于共享单车、共享充电宝、停车场临时停车等场景

计时

TIMER

用户在某个短时间段持续的正计时或任务前的倒计时场景

适用于专注时刻、番茄时钟、抢票倒计时提醒场景,仅限于工具类应用申请

实况窗样式模板

实况窗的卡片态支持固定区、辅助区、扩展区三个板块,其中固定区为必选,辅助区和扩展区为可选。

image

基础类型模板

基础类型模板适用于通话、传输进度、录音等场景。

image

进度可视化模板

进度可视化模板适用于打车、外卖等需要呈现完整进程及当前进度节点的场景。

image

强调文本模板

强调文本模板适用于取餐、排队等需要强调部分文本信息的场景。

image

左右文本模板

左右文本模板适用于高铁、航班等左右信息对称的场景。

image

赛事比分模板

赛事比分模板适用于体育赛事比分场景、游戏赛事比分场景等。

image

客户端本地创建/更新/结束实况窗

应用进程活跃时,可以通过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

  • AndroidTargetUserTypeAndroidHuaweiTargetUserType: 测试消息设置为 1,正式消息设置为0

  • AndroidHuaweiLiveNotificationPayload: 实况窗数据结构LiveNotificationPayloadJSON字符串,其中operation在更新/结束时分别设置为1/2

LiveNotificationPayload的结构如下表所示:

参数

是否必选

参数类型

描述

activityId

Integer

实况窗唯一标识,由开发者自行生成,对应客户端

notificationManager.notify(activityId, notification)中的activityId

重要

发送的activityId对应的实况窗通知不存在,将限制发送该activityId的实况窗通知24小时。

operation

Integer

实况窗通知操作类型:

  • 1:表示更新实况窗通知

  • 2:表示结束实况窗通知

event

String

业务场景取值,必须为以下内容之一:

  • TAXI:出行打车

  • DELIVERY:即时配送(外卖、生鲜)

  • FLIGHT:航班

  • TRAIN:高铁/火车

  • QUEUE:排队

  • PICK_UP:取餐

  • SCORE:赛事比分

  • RENT:共享租赁

title

String

可选,当系统不支持实况窗通知时,展示在通知栏的内容。

content

String

可选,当系统不支持实况窗通知时,展示在通知栏的内容。

mute

Boolean

标识消息更新是否需要提醒。

  • true:静默提醒(默认值)

  • false:铃声震动提醒

version

Integer

更新实况窗通知的版本号(默认值0),大于等于0,新的实况窗通知版本号需大于当前实况窗通知版本号,否则会刷新失败。

activityData

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小时未更新的实况窗通知,系统会认为通知结束。

问题排查

创建实况窗失败

  1. 检查是否已在AppGallery Connect中申请对应场景实况窗权限。

  2. 检查设备机型是否为HarmonyOS 4.0及以上。

  3. 检查设备通知权限是否打开。

  4. 检查是否成功创建NotificationChannel。

更新/结束实况窗失败

  1. 检查AndroidHuaweiLiveNotificationPayload参数中的activityId是否与创建实况窗时的activityId相同。

  2. 检查AndroidHuaweiLiveNotificationPayload参数中的version是否正确设置。

  3. 检查是否触发频率限制。

  4. 根据消息ID与设备ID在移动推送控制台的排查工具->排查消息页面检查实况窗推送链路。

实况窗样式异常

  1. 检查AndroidHuaweiLiveNotificationPayload参数中的type是否匹配对应模板。

  2. 检查资源文件路径是否正确。