HarmonyOS开发:推送服务——Push Kit集成
HarmonyOS开发:推送服务——Push Kit集成
📌 核心要点:Push Kit是应用触达用户的"扩音器"——集成推送服务、发送通知消息、处理推送点击、分析推送效果,让关键信息在合适的时间送达合适的用户。
背景与动机
你的应用有一个限时促销活动,怎么通知用户?等用户自己打开应用?那黄花菜都凉了。
你的应用发布了一个重要更新修复了崩溃Bug,怎么通知受影响的用户?在更新日志里写?用户根本不看。
推送通知是应用触达用户最直接的方式——即使用户没有打开应用,推送消息也能出现在通知栏里,提醒用户"嘿,有重要的事情"。
但推送也是一把双刃剑——推得好,用户回访率提升30%;推得差,用户直接关掉通知权限甚至卸载应用。
推送服务要解决的核心问题:
- 怎么推:集成Push Kit,获取推送令牌,建立推送通道
- 推什么:通知消息、静默消息、富媒体消息,不同场景用不同类型
- 怎么处理:用户点击推送后的跳转逻辑,别点了没反应
- 推得怎样:推送到达率、点击率、转化率,用数据优化推送策略
鸿蒙的Push Kit提供了一套完整的推送解决方案,从令牌管理到消息处理,覆盖了推送的全链路。
核心原理
Push Kit推送的完整链路:应用获取Token → 服务端存储Token → 服务端推送消息 → Push Kit投递 → 用户点击 → 应用处理。
flowchart LR
subgraph 客户端
A[应用启动] --> B[请求Push Token]
B --> C[上报Token到服务端]
end
subgraph 服务端
C --> D[存储Token]
D --> E[触发推送<br/>运营/业务/系统]
E --> F[调用Push Kit API]
end
subgraph Push Kit
F --> G[消息路由]
G --> H[在线推送<br/>实时到达]
G --> I[离线推送<br/>等设备上线]
end
subgraph 用户端
H --> J[通知栏展示]
I --> J
J --> K{用户操作}
K -->|点击| L[打开应用<br/>跳转指定页面]
K -->|忽略| M[通知消失]
K -->|关闭| N[关闭通知权限]
end
classDef client fill:#6C5CE7,stroke:#5B4BC9,color:#fff
classDef server fill:#00B894,stroke:#00A383,color:#fff
classDef pushkit fill:#FDCB6E,stroke:#F0B429,color:#333
classDef user fill:#FF7675,stroke:#D63031,color:#fff
classDef decision fill:#74B9FF,stroke:#0984E3,color:#fff
class A,B,C client
class D,E,F server
class G,H,I pushkit
class J user
class K decision
class L,M,N user
推送消息类型:
| 消息类型 | 说明 | 展示方式 | 适用场景 |
|---|---|---|---|
| 通知消息 | 系统通知栏展示 | 通知栏+横幅+响铃 | 促销活动、系统公告 |
| 静默消息 | 不展示通知 | 无感知 | 数据同步、配置更新 |
| 富媒体消息 | 包含图片/按钮 | 通知栏+大图+操作按钮 | 商品推荐、社交互动 |
| 本地通知 | 本地触发 | 通知栏 | 提醒、定时任务 |
代码实战
基础用法:Push Kit集成与Token获取
先把Push Kit接进来,获取推送令牌。
// PushManager.ets - 推送管理器
import { pushService } from '@kit.PushKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { analytics } from '@kit.AnalyticsKit';
// 推送令牌回调
export interface PushTokenCallback {
onTokenSuccess: (token: string) => void;
onTokenFailure: (error: BusinessError) => void;
}
export class PushManager {
private static instance: PushManager;
private context: common.UIAbilityContext | null = null;
private pushToken: string = '';
private tokenCallback: PushTokenCallback | null = null;
static getInstance(): PushManager {
if (!PushManager.instance) {
PushManager.instance = new PushManager();
}
return PushManager.instance;
}
// 初始化Push Kit
init(context: common.UIAbilityContext, callback?: PushTokenCallback): void {
this.context = context;
this.tokenCallback = callback || null;
try {
// 请求推送令牌
pushService.getToken(context).then((token: string) => {
this.pushToken = token;
console.info(`[Push] 获取Token成功: ${token.substring(0, 10)}...`);
// 上报Token到服务端
this.reportTokenToServer(token);
// 回调
if (this.tokenCallback) {
this.tokenCallback.onTokenSuccess(token);
}
}).catch((error: BusinessError) => {
console.error(`[Push] 获取Token失败: ${error.code} - ${error.message}`);
if (this.tokenCallback) {
this.tokenCallback.onTokenFailure(error);
}
});
console.info('[Push] Push Kit初始化完成');
} catch (error) {
const err = error as BusinessError;
console.error(`[Push] 初始化失败: ${err.code} - ${err.message}`);
}
}
// 获取当前Token
getToken(): string {
return this.pushToken;
}
// 上报Token到服务端
private async reportTokenToServer(token: string): Promise<void> {
// 实际项目中用HTTP请求上报到自己的推送服务
console.info('[Push] Token已上报到服务端');
// 上报Token事件
analytics.reportEvent('push_token_obtained', {
'token_prefix': token.substring(0, 10),
'timestamp': Date.now(),
});
}
// 检查通知权限
async checkNotificationPermission(): Promise<boolean> {
// 检查通知权限是否开启
// 实际项目中使用notificationManager.isNotificationEnabled()
return true;
}
// 请求通知权限
async requestNotificationPermission(): Promise<boolean> {
// 请求通知权限
// 实际项目中使用notificationManager.requestNotificationEnabled()
console.info('[Push] 请求通知权限');
return true;
}
// 删除Token(用户关闭推送时调用)
async deleteToken(): Promise<void> {
if (!this.context) return;
try {
await pushService.deleteToken(this.context);
this.pushToken = '';
console.info('[Push] Token已删除');
} catch (error) {
const err = error as BusinessError;
console.error(`[Push] 删除Token失败: ${err.code}`);
}
}
}
在EntryAbility中初始化:
// EntryAbility.ets
import { PushManager } from '../push/PushManager';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam): void {
// 初始化Push Kit
PushManager.getInstance().init(this.context, {
onTokenSuccess: (token: string) => {
console.info(`Push Token: ${token}`);
},
onTokenFailure: (error: BusinessError) => {
console.error(`Push Token获取失败: ${error.code}`);
},
});
}
}
进阶用法:推送消息处理
推送消息来了,怎么处理?用户点击了推送,怎么跳转?
// PushMessageHandler.ets - 推送消息处理
import { AbilityConstant, Want } from '@kit.AbilityKit';
import { analytics } from '@kit.AnalyticsKit';
import { router } from '@kit.ArkUI';
// 推送消息类型
export enum PushMessageType {
PROMOTION = 'promotion', // 促销活动
ORDER_UPDATE = 'order_update', // 订单更新
SYSTEM_NOTICE = 'system', // 系统通知
SOCIAL = 'social', // 社交互动
REMINDER = 'reminder', // 提醒
}
// 推送消息数据
export interface PushMessageData {
messageId: string;
type: PushMessageType;
title: string;
content: string;
targetPage?: string; // 点击后跳转的页面
targetParams?: Record<string, string>; // 跳转参数
imageUrl?: string; // 富媒体图片
actionButtons?: PushActionButton[]; // 操作按钮
}
// 操作按钮
export interface PushActionButton {
title: string;
action: string;
}
// 推送效果数据
export interface PushEffectData {
messageId: string;
received: boolean; // 是否收到
clicked: boolean; // 是否点击
clickTime?: number; // 点击时间
converted: boolean; // 是否转化
convertTime?: number; // 转化时间
}
export class PushMessageHandler {
private static instance: PushMessageHandler;
private effectData: Map<string, PushEffectData> = new Map();
static getInstance(): PushMessageHandler {
if (!PushMessageHandler.instance) {
PushMessageHandler.instance = new PushMessageHandler();
}
return PushMessageHandler.instance;
}
// 处理推送消息——在Ability的onCreate/onNewWant中调用
handleMessage(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (!want || !want.parameters) return;
// 解析推送数据
const pushData = this.parsePushData(want);
if (!pushData) return;
console.info(`[PushHandler] 收到推送: ${pushData.title}`);
// 记录推送效果
this.recordReceived(pushData.messageId);
// 判断是否是点击推送打开的应用
if (launchParam.launchReason === AbilityConstant.LaunchReason.PUSH) {
this.handlePushClick(pushData);
}
}
// 解析推送数据
private parsePushData(want: Want): PushMessageData | null {
try {
const params = want.parameters as Record<string, string>;
return {
messageId: params['msg_id'] || '',
type: params['msg_type'] as PushMessageType || PushMessageType.SYSTEM_NOTICE,
title: params['title'] || '',
content: params['content'] || '',
targetPage: params['target_page'],
targetParams: params['target_params']
? JSON.parse(params['target_params'])
: undefined,
imageUrl: params['image_url'],
};
} catch (error) {
console.error('[PushHandler] 解析推送数据失败');
return null;
}
}
// 处理推送点击
private handlePushClick(pushData: PushMessageData): void {
// 记录点击
this.recordClicked(pushData.messageId);
// 上报推送点击事件
analytics.reportEvent('push_clicked', {
'message_id': pushData.messageId,
'message_type': pushData.type,
'target_page': pushData.targetPage || 'none',
});
// 跳转到目标页面
if (pushData.targetPage) {
this.navigateToTarget(pushData);
}
}
// 跳转到目标页面
private navigateToTarget(pushData: PushMessageData): void {
const targetMap: Record<string, string> = {
'home': 'pages/HomePage',
'product_detail': 'pages/ProductDetailPage',
'order_detail': 'pages/OrderDetailPage',
'promotion': 'pages/PromotionPage',
'message': 'pages/MessagePage',
};
const targetUrl = targetMap[pushData.targetPage!] || 'pages/HomePage';
try {
router.pushUrl({
url: targetUrl,
params: pushData.targetParams || {},
});
console.info(`[PushHandler] 跳转到: ${targetUrl}`);
} catch (error) {
console.error(`[PushHandler] 跳转失败: ${targetUrl}`);
}
}
// 记录推送收到
private recordReceived(messageId: string): void {
this.effectData.set(messageId, {
messageId: messageId,
received: true,
clicked: false,
converted: false,
});
}
// 记录推送点击
private recordClicked(messageId: string): void {
const data = this.effectData.get(messageId);
if (data) {
data.clicked = true;
data.clickTime = Date.now();
}
}
// 记录推送转化(用户完成目标行为)
recordConversion(messageId: string): void {
const data = this.effectData.get(messageId);
if (data) {
data.converted = true;
data.convertTime = Date.now();
}
}
// 获取推送效果数据
getEffectData(messageId: string): PushEffectData | undefined {
return this.effectData.get(messageId);
}
}
在Ability中处理推送:
// EntryAbility.ets
import { PushMessageHandler } from '../push/PushMessageHandler';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam): void {
// 处理推送消息
PushMessageHandler.getInstance().handleMessage(want, launchParam);
}
onNewWant(want, launchParam): void {
// 应用已在后台,通过推送重新打开
PushMessageHandler.getInstance().handleMessage(want, launchParam);
}
}
完整示例:推送效果分析
推送发出去,到达率多少?点击率多少?转化率多少?这些数据直接影响推送策略的优化。
// PushAnalytics.ets - 推送效果分析
import { analytics } from '@kit.AnalyticsKit';
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { PushEffectData } from './PushMessageHandler';
// 推送效果统计
export interface PushEffectStats {
totalSent: number; // 总发送数
totalReceived: number; // 总到达数
totalClicked: number; // 总点击数
totalConverted: number; // 总转化数
deliveryRate: number; // 到达率
clickRate: number; // 点击率
conversionRate: number; // 转化率
clickToConvertRate: number; // 点击到转化率
}
// 推送策略
export interface PushStrategy {
name: string;
description: string;
targetSegment: string; // 目标用户群
pushTime: string; // 推送时间
frequency: string; // 推送频率
messageTypes: string[]; // 消息类型
}
export class PushAnalytics {
private static instance: PushAnalytics;
private pref: preferences.Preferences | null = null;
// 推送效果数据
private effectRecords: PushEffectData[] = [];
static getInstance(): PushAnalytics {
if (!PushAnalytics.instance) {
PushAnalytics.instance = new PushAnalytics();
}
return PushAnalytics.instance;
}
async init(context: common.UIAbilityContext): Promise<void> {
this.pref = await preferences.getPreferences(context, 'push_analytics');
}
// 添加效果记录
addEffectRecord(record: PushEffectData): void {
this.effectRecords.push(record);
}
// 计算推送效果统计
calculateStats(): PushEffectStats {
const totalSent = this.effectRecords.length;
const totalReceived = this.effectRecords.filter(r => r.received).length;
const totalClicked = this.effectRecords.filter(r => r.clicked).length;
const totalConverted = this.effectRecords.filter(r => r.converted).length;
const deliveryRate = totalSent > 0 ? totalReceived / totalSent : 0;
const clickRate = totalReceived > 0 ? totalClicked / totalReceived : 0;
const conversionRate = totalReceived > 0 ? totalConverted / totalReceived : 0;
const clickToConvertRate = totalClicked > 0 ? totalConverted / totalClicked : 0;
return {
totalSent,
totalReceived,
totalClicked,
totalConverted,
deliveryRate: Math.round(deliveryRate * 10000) / 100,
clickRate: Math.round(clickRate * 10000) / 100,
conversionRate: Math.round(conversionRate * 10000) / 100,
clickToConvertRate: Math.round(clickToConvertRate * 10000) / 100,
};
}
// 生成推送优化建议
generateOptimizationSuggestions(stats: PushEffectStats): string[] {
const suggestions: string[] = [];
// 到达率低
if (stats.deliveryRate < 90) {
suggestions.push(
`到达率${stats.deliveryRate}%偏低,建议:` +
'1.检查Token是否有效 2.确认通知权限开启率 3.优化推送通道选择'
);
}
// 点击率低
if (stats.clickRate < 5) {
suggestions.push(
`点击率${stats.clickRate}%偏低,建议:` +
'1.优化推送标题(更吸引人) 2.优化推送时间(用户活跃时段) ' +
'3.个性化推送内容(基于用户偏好)'
);
}
// 点击到转化率低
if (stats.clickToConvertRate < 10) {
suggestions.push(
`点击到转化率${stats.clickToConvertRate}%偏低,建议:` +
'1.优化落地页体验 2.缩短转化路径 3.推送内容与落地页一致'
);
}
// 一切正常
if (suggestions.length === 0) {
suggestions.push('推送效果良好,建议持续监控并逐步优化。');
}
return suggestions;
}
// 获取最佳推送时间
getBestPushTime(): { hour: number; reason: string } {
// 基于历史数据分析最佳推送时间
// 简化实现:返回常见最佳时间
const timeSlots = [
{ hour: 8, reason: '早间通勤时段,用户查看手机频率高' },
{ hour: 12, reason: '午休时段,用户有更多时间浏览' },
{ hour: 19, reason: '晚间休闲时段,用户活跃度最高' },
];
// 实际项目中应基于点击率数据选择
return timeSlots[2]; // 默认推荐晚间
}
// 生成推送策略
generatePushStrategy(segment: string): PushStrategy {
const strategies: Record<string, PushStrategy> = {
'new_user': {
name: '新用户引导推送',
description: '帮助新用户快速了解产品核心功能',
targetSegment: '注册7天内的新用户',
pushTime: '注册后2小时',
frequency: '首周3次',
messageTypes: ['新手引导', '功能推荐', '首单优惠'],
},
'active_user': {
name: '活跃用户运营推送',
description: '保持活跃用户粘性,提升使用频次',
targetSegment: '7日内活跃用户',
pushTime: '晚间19:00',
frequency: '每周2-3次',
messageTypes: ['个性化推荐', '活动通知', '内容更新'],
},
'churn_risk': {
name: '流失风险用户召回推送',
description: '挽回即将流失的用户',
targetSegment: '7-14天未活跃用户',
pushTime: '午间12:00',
frequency: '每周1次',
messageTypes: ['专属优惠', '新功能亮点', '好友动态'],
},
'churned': {
name: '已流失用户召回推送',
description: '尝试召回长期未活跃用户',
targetSegment: '30天以上未活跃用户',
pushTime: '早间8:00',
frequency: '每月1次',
messageTypes: ['大额优惠券', '产品重大更新', '好友邀请'],
},
};
return strategies[segment] || strategies['active_user'];
}
// 打印推送效果报告
printReport(): void {
const stats = this.calculateStats();
const suggestions = this.generateOptimizationSuggestions(stats);
console.info('\n========== 推送效果报告 ==========');
console.info(`总发送: ${stats.totalSent}`);
console.info(`总到达: ${stats.totalReceived} (到达率${stats.deliveryRate}%)`);
console.info(`总点击: ${stats.totalClicked} (点击率${stats.clickRate}%)`);
console.info(`总转化: ${stats.totalConverted} (转化率${stats.conversionRate}%)`);
console.info(`点击到转化: ${stats.clickToConvertRate}%`);
console.info('\n优化建议:');
suggestions.forEach((s, i) => {
console.info(` ${i + 1}. ${s}`);
});
const bestTime = this.getBestPushTime();
console.info(`\n最佳推送时间: ${bestTime.hour}:00 (${bestTime.reason})`);
console.info('====================================\n');
}
}
踩坑与注意事项
1. 推送权限要引导获取
用户第一次打开应用,你直接请求推送权限,大概率被拒绝。应该先让用户感受到推送的价值,再请求权限。比如用户下单后,提示"开启通知及时了解订单状态"——这种场景下用户更愿意授权。
2. 推送频率要克制
一天推5条?用户直接关掉通知权限。推送频率建议:
- 促销类:每天不超过1条
- 系统通知:按需推送
- 社交互动:按需推送
- 内容推荐:每天不超过2条
3. 推送内容要个性化
“亲爱的用户,我们有新活动”——这种群发推送点击率极低。应该基于用户画像做个性化推送:“你收藏的商品降价了”、“你关注的主播正在直播”——这种推送点击率能翻3倍。
4. Token会失效
推送Token不是永久有效的——用户卸载重装、清除数据、系统更新都可能导致Token失效。必须定期刷新Token,并处理Token失效的情况。
5. 推送与隐私合规
推送涉及用户隐私,必须遵守相关规定:
- 获取推送权限前告知用户用途
- 用户可以随时关闭推送
- 不推送敏感信息(如交易金额、密码等)
- 保留推送记录以备审计
HarmonyOS 6适配说明
HarmonyOS 6在推送服务方面的更新:
- Push Kit增强:支持富媒体推送(大图、视频、操作按钮)
- 实时推送:新增实时消息通道,适用于IM、直播等场景
- 智能推送时机:AI分析用户活跃时段,自动选择最佳推送时间
- 推送效果分析:内置推送效果统计,到达率/点击率/转化率一目了然
// HarmonyOS 6 富媒体推送
import { notificationManager } from '@kit.NotificationKit';
// 发送富媒体通知
async function sendRichNotification(
title: string,
text: string,
imageUrl: string
): Promise<void> {
const request: notificationManager.NotificationRequest = {
id: Date.now(),
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_PICTURE,
picture: {
title: title,
text: text,
expandedTitle: title,
briefText: text,
picture: imageUrl, // 大图
},
},
actionButtons: [
{ title: '立即查看' },
{ title: '稍后再看' },
],
};
await notificationManager.publish(request);
}
总结
推送服务是应用触达用户的关键通道——没有推送,用户可能永远不会回来。但推送也是一把双刃剑,推得好用户回访,推得差用户卸载。集成Push Kit获取推送通道,精心设计推送内容,分析推送效果持续优化,这是推送运营的基本功。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐☆☆☆ 集成简单,难点在推送策略设计 |
| 使用频率 | ⭐⭐⭐⭐⭐ 持续使用,每日推送 |
| 重要程度 | ⭐⭐⭐⭐⭐ 直接影响用户回访率和活跃度 |
核心记住三点:推送频率要克制(别骚扰用户)、推送内容要个性化(别群发)、推送效果要分析(用数据优化)。推送不是目的,用户回访才是目的——如果推送不能带来有价值的回访,不如不推。
- 点赞
- 收藏
- 关注作者
评论(0)