HarmonyOS APP开发中通知监听小知识
一、背景
想象一下这个场景:你装了10个聊天App,每个App都在发通知,通知栏里乱成一锅粥。如果能有一个"通知管家",把所有聊天通知聚合到一起,按优先级排序,甚至自动过滤掉垃圾通知,那该多好。
这就是通知监听的价值所在。通过 NotificationSubscriber,你可以"监听"系统中所有通知的生命周期事件——发布、更新、删除。这让你能够:
- 通知聚合:把分散的通知集中展示,比如一个"消息中心"页面
- 智能过滤:自动识别并过滤垃圾通知
- 跨设备转发:把手机上的通知转发到平板或手表
- 辅助功能:为视障用户提供语音播报通知
不过,通知监听也是一把双刃剑——它能看到所有应用的通知内容,所以权限管控非常严格。HarmonyOS 要求用户必须手动授权,且应用必须声明敏感权限。这就像安装监控摄像头——不是你想装就能装的,需要物业(系统)和住户(用户)的双重同意。
二、核心原理
2.1 通知监听架构

2.2 NotificationSubscriber 回调方法
NotificationSubscriber 是一个接口,你需要实现以下回调方法:
| 回调方法 | 触发时机 | 参数 |
|---|---|---|
onConsume |
新通知发布 | SubscribeInfo |
onCancel |
通知被取消 | SubscribeInfo |
onUpdate |
通知内容更新 | SubscribeInfo |
onConnect |
订阅连接成功 | 无 |
onDisconnect |
订阅连接断开 | 无 |
onDestroy |
订阅被销毁 | 无 |
onDoNotDisturbDateChange |
免打扰模式变更 | DoNotDisturbDate |
onEnabledNotificationChanged |
通知开关变更 | EnabledNotificationCallbackData |
2.3 SubscribeInfo 数据结构
当通知事件触发时,回调方法会收到 SubscribeInfo 对象,包含通知的完整信息:
interface SubscribeInfo {
notificationId: number; // 通知ID
bundleName: string; // 来源应用包名
label: string; // 通知标签
content: NotificationContent; // 通知内容
slotType: SlotType; // 渠道类型
isUnremovable: boolean; // 是否不可移除
isOngoing: boolean; // 是否常驻
showDeliveryTime: boolean; // 是否显示送达时间
deliveryTime: number; // 送达时间戳
tapDismissed: boolean; // 点击后是否消失
// ... 更多字段
}
2.4 通知监听安全模型
flowchart TD
A[应用请求通知监听] --> B{声明权限}
B -->|未声明| C[编译错误]
B -->|已声明| D{用户授权}
D -->|未授权| E[运行时异常]
D -->|已授权| F[订阅成功]
F --> G[可读取通知内容]
G --> H{安全约束}
H --> I[不可修改其他应用通知]
H --> J[不可拦截其他应用通知]
H --> K[仅可读取已授权类型]
classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
classDef error fill:#F44336,stroke:#D32F2F,color:#fff
classDef info fill:#2196F3,stroke:#1976D2,color:#fff
classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff
class A,B primary
class C,E error
class D,F,G info
class H,I,J,K warning
2.5 通知监听与通知发布的区别
| 特性 | 通知发布 | 通知监听 |
|---|---|---|
| 方向 | 应用→系统 | 系统→应用 |
| 权限 | 通知权限 | 通知订阅权限(更严格) |
| 数据访问 | 自己的通知 | 所有应用的通知 |
| 用途 | 向用户展示信息 | 监控和聚合通知 |
| 安全级别 | 普通 | 敏感 |
三、代码实战
3.1 基础通知订阅:监听所有通知
这是最基础的通知监听实现——创建 NotificationSubscriber 并订阅系统通知。
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct NotificationSubscribePage {
@State subscribeStatus: string = '未订阅';
@State notificationLog: string = '暂无通知记录';
@State notificationCount: number = 0;
private isSubscribed: boolean = false;
// 保存通知记录
private notificationRecords: Array<{
id: number;
bundleName: string;
title: string;
text: string;
time: string;
}> = [];
/**
* 创建通知订阅者
* 实现所有回调方法,监听通知生命周期事件
*/
private createSubscriber(): notificationManager.NotificationSubscriber {
const subscriber: notificationManager.NotificationSubscriber = {
// 通知发布时触发 —— 这是最常用的回调
onConsume: (data: notificationManager.SubscribeInfo) => {
this.notificationCount++;
const now = new Date();
const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
// 提取通知内容
let title = '';
let text = '';
if (data.content) {
if (data.content.normal) {
title = data.content.normal.title || '';
text = data.content.normal.text || '';
}
}
// 记录通知信息
const record = {
id: data.notificationId,
bundleName: data.bundleName || '未知应用',
title,
text,
time: timeStr
};
this.notificationRecords.unshift(record); // 最新的在前面
// 限制记录数量
if (this.notificationRecords.length > 20) {
this.notificationRecords.pop();
}
// 更新UI
this.notificationLog = this.formatNotificationLog();
this.subscribeStatus = `已订阅 | 已接收 ${this.notificationCount} 条通知`;
console.info(`[通知监听] 新通知: ${title} - 来自 ${data.bundleName}`);
},
// 通知取消时触发
onCancel: (data: notificationManager.SubscribeInfo) => {
console.info(`[通知监听] 通知取消: ID=${data.notificationId}`);
},
// 通知更新时触发
onUpdate: (data: notificationManager.SubscribeInfo) => {
console.info(`[通知监听] 通知更新: ID=${data.notificationId}`);
},
// 订阅连接成功时触发
onConnect: () => {
this.subscribeStatus = '订阅连接成功 ✅';
this.isSubscribed = true;
console.info('[通知监听] 订阅连接成功');
},
// 订阅断开时触发
onDisconnect: () => {
this.subscribeStatus = '订阅已断开';
this.isSubscribed = false;
console.info('[通知监听] 订阅断开');
},
// 订阅销毁时触发
onDestroy: () => {
this.subscribeStatus = '订阅已销毁';
this.isSubscribed = false;
console.info('[通知监听] 订阅销毁');
},
// 免打扰模式变更时触发
onDoNotDisturbDateChange: (data: notificationManager.DoNotDisturbDate) => {
console.info(`[通知监听] 免打扰模式变更: type=${data.type}`);
},
// 通知开关变更时触发
onEnabledNotificationChanged: (data: notificationManager.EnabledNotificationCallbackData) => {
console.info(`[通知监听] 通知开关变更: bundle=${data.bundle}, enabled=${data.enable}`);
}
};
return subscriber;
}
/**
* 格式化通知日志
*/
private formatNotificationLog(): string {
if (this.notificationRecords.length === 0) {
return '暂无通知记录';
}
return this.notificationRecords.map((record, index) => {
return `${index + 1}. [${record.time}] ${record.bundleName}\n ${record.title}: ${record.text}`;
}).join('\n\n');
}
/**
* 开始订阅通知
*/
async startSubscribe(): Promise<void> {
if (this.isSubscribed) {
this.subscribeStatus = '已经在订阅中';
return;
}
try {
const subscriber = this.createSubscriber();
await notificationManager.subscribe(subscriber);
this.subscribeStatus = '订阅请求已发送,等待连接...';
console.info('[通知监听] 订阅请求已发送');
} catch (err) {
const error = err as BusinessError;
this.subscribeStatus = `订阅失败: ${error.message}`;
console.error(`[通知监听] 订阅失败: ${error.code}`);
}
}
/**
* 取消订阅通知
*/
async stopSubscribe(): Promise<void> {
if (!this.isSubscribed) {
this.subscribeStatus = '当前未订阅';
return;
}
try {
const subscriber = this.createSubscriber();
await notificationManager.unsubscribe(subscriber);
this.subscribeStatus = '已取消订阅';
this.isSubscribed = false;
console.info('[通知监听] 取消订阅成功');
} catch (err) {
const error = err as BusinessError;
this.subscribeStatus = `取消失败: ${error.message}`;
}
}
build() {
Column({ space: 16 }) {
Text('通知监听')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text(this.subscribeStatus)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.padding(12)
.borderRadius(8)
.backgroundColor(this.isSubscribed ? '#E8F5E9' : '#FFF3E0')
.width('90%')
.textAlign(TextAlign.Center)
Text(`已接收: ${this.notificationCount} 条`)
.fontSize(14)
.fontColor('#666666')
// 通知日志展示区
Scroll() {
Text(this.notificationLog)
.fontSize(12)
.width('100%')
.padding(12)
}
.width('90%')
.height(300)
.borderRadius(12)
.backgroundColor('#F0F4F8')
Row({ space: 16 }) {
Button('开始订阅')
.width('40%')
.height(45)
.backgroundColor('#4CAF50')
.onClick(() => this.startSubscribe())
Button('取消订阅')
.width('40%')
.height(45)
.backgroundColor('#F44336')
.onClick(() => this.stopSubscribe())
}
Button('清空日志')
.width('80%')
.height(40)
.backgroundColor('#757575')
.onClick(() => {
this.notificationRecords = [];
this.notificationLog = '暂无通知记录';
this.notificationCount = 0;
})
}
.width('100%')
.height('100%')
.padding(20)
}
}
3.2 通知过滤与分类:智能通知管家
实际场景中,我们通常不需要监听所有通知,而是只关心特定类型或特定应用的通知。这个示例实现了通知的过滤和分类功能。
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 通知分类枚举
*/
enum NotificationCategory {
SOCIAL = '社交通信',
SERVICE = '服务提醒',
CONTENT = '内容推荐',
OTHER = '其他'
}
/**
* 通知记录数据结构
*/
interface NotificationRecord {
id: number;
bundleName: string;
title: string;
text: string;
category: NotificationCategory;
time: string;
timestamp: number;
}
@Entry
@Component
struct SmartNotificationFilterPage {
@State allNotifications: NotificationRecord[] = [];
@State filteredNotifications: NotificationRecord[] = [];
@State currentFilter: string = '全部';
@State statsText: string = '等待订阅...';
@State isSubscribed: boolean = false;
// 过滤条件
private filterOptions: string[] = ['全部', '社交通信', '服务提醒', '内容推荐', '其他'];
/**
* 根据渠道类型判断通知分类
*/
private categorizeNotification(slotType: number): NotificationCategory {
switch (slotType) {
case notificationManager.SlotType.SOCIAL_COMMUNICATION:
return NotificationCategory.SOCIAL;
case notificationManager.SlotType.SERVICE_INFORMATION:
return NotificationCategory.SERVICE;
case notificationManager.SlotType.CONTENT_INFORMATION:
return NotificationCategory.CONTENT;
default:
return NotificationCategory.OTHER;
}
}
/**
* 创建带过滤功能的通知订阅者
*/
private createFilteredSubscriber(): notificationManager.NotificationSubscriber {
return {
onConsume: (data: notificationManager.SubscribeInfo) => {
// 提取通知内容
let title = '';
let text = '';
if (data.content?.normal) {
title = data.content.normal.title || '';
text = data.content.normal.text || '';
}
// 分类通知
const category = this.categorizeNotification(data.slotType ?? 3);
// 创建记录
const now = new Date();
const record: NotificationRecord = {
id: data.notificationId,
bundleName: data.bundleName || '未知',
title,
text,
category,
time: `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`,
timestamp: now.getTime()
};
// 添加到列表(最多保留50条)
this.allNotifications.unshift(record);
if (this.allNotifications.length > 50) {
this.allNotifications.pop();
}
// 应用当前过滤条件
this.applyFilter(this.currentFilter);
// 更新统计
this.updateStats();
},
onConnect: () => {
this.isSubscribed = true;
this.statsText = '订阅成功,正在监听通知...';
},
onDisconnect: () => {
this.isSubscribed = false;
this.statsText = '订阅已断开';
},
onCancel: () => {},
onUpdate: () => {},
onDestroy: () => {
this.isSubscribed = false;
},
onDoNotDisturbDateChange: () => {},
onEnabledNotificationChanged: () => {}
};
}
/**
* 应用过滤条件
*/
private applyFilter(filter: string): void {
this.currentFilter = filter;
if (filter === '全部') {
this.filteredNotifications = [...this.allNotifications];
} else {
this.filteredNotifications = this.allNotifications.filter(
item => item.category === filter
);
}
}
/**
* 更新统计信息
*/
private updateStats(): void {
const social = this.allNotifications.filter(n => n.category === NotificationCategory.SOCIAL).length;
const service = this.allNotifications.filter(n => n.category === NotificationCategory.SERVICE).length;
const content = this.allNotifications.filter(n => n.category === NotificationCategory.CONTENT).length;
const other = this.allNotifications.filter(n => n.category === NotificationCategory.OTHER).length;
this.statsText = `总计: ${this.allNotifications.length} | 社交: ${social} | 服务: ${service} | 内容: ${content} | 其他: ${other}`;
}
/**
* 开始订阅
*/
async startSubscribe(): Promise<void> {
try {
const subscriber = this.createFilteredSubscriber();
await notificationManager.subscribe(subscriber);
} catch (err) {
const error = err as BusinessError;
this.statsText = `订阅失败: ${error.message}`;
}
}
/**
* 取消订阅
*/
async stopSubscribe(): Promise<void> {
try {
const subscriber = this.createFilteredSubscriber();
await notificationManager.unsubscribe(subscriber);
this.isSubscribed = false;
this.statsText = '已取消订阅';
} catch (err) {
const error = err as BusinessError;
this.statsText = `取消失败: ${error.message}`;
}
}
build() {
Column({ space: 12 }) {
Text('智能通知管家')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text(this.statsText)
.fontSize(12)
.fontColor('#666666')
.width('90%')
.textAlign(TextAlign.Center)
// 过滤条件选择
Row({ space: 8 }) {
ForEach(this.filterOptions, (option: string) => {
Button(option)
.height(32)
.fontSize(12)
.backgroundColor(this.currentFilter === option ? '#2196F3' : '#E0E0E0')
.fontColor(this.currentFilter === option ? '#FFFFFF' : '#333333')
.onClick(() => this.applyFilter(option))
})
}
.width('90%')
.justifyContent(FlexAlign.Center)
// 通知列表
List({ space: 8 }) {
ForEach(this.filteredNotifications, (item: NotificationRecord) => {
ListItem() {
Row({ space: 12 }) {
// 分类图标
Text(item.category === NotificationCategory.SOCIAL ? '💬' :
item.category === NotificationCategory.SERVICE ? '📦' :
item.category === NotificationCategory.CONTENT ? '📰' : '📌')
.fontSize(24)
// 通知内容
Column({ space: 4 }) {
Text(item.title)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.maxLines(1)
Text(`${item.bundleName} · ${item.time}`)
.fontSize(11)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.padding(12)
.borderRadius(8)
.backgroundColor('#FFFFFF')
}
})
}
.width('90%')
.layoutWeight(1)
.borderRadius(12)
.backgroundColor('#F5F5F5')
.padding(8)
Row({ space: 16 }) {
Button('开始订阅')
.width('40%')
.height(45)
.backgroundColor('#4CAF50')
.onClick(() => this.startSubscribe())
Button('取消订阅')
.width('40%')
.height(45)
.backgroundColor('#F44336')
.onClick(() => this.stopSubscribe())
}
}
.width('100%')
.height('100%')
.padding(16)
}
}
3.3 通知转发与安全:跨设备通知同步
这个示例展示了如何将监听到的通知转发到其他设备,同时处理安全与隐私问题。
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 通知转发配置
*/
interface ForwardConfig {
enabled: boolean; // 是否启用转发
filterCategories: string[]; // 允许转发的分类
stripSensitiveData: boolean; // 是否脱敏
maxForwardPerHour: number; // 每小时最大转发数
}
@Entry
@Component
struct NotificationForwardPage {
@State logText: string = '通知转发服务就绪';
@State isSubscribed: boolean = false;
@State forwardCount: number = 0;
@State configText: string = '';
// 转发配置
private forwardConfig: ForwardConfig = {
enabled: true,
filterCategories: ['社交通信', '服务提醒'], // 只转发社交和服务类
stripSensitiveData: true, // 启用脱敏
maxForwardPerHour: 30 // 每小时最多30条
};
// 转发计数(用于限流)
private forwardTimestamps: number[] = [];
/**
* 敏感数据脱敏处理
* 隐藏通知中的手机号、身份证号等敏感信息
*/
private stripSensitiveInfo(text: string): string {
if (!this.forwardConfig.stripSensitiveData) {
return text;
}
let result = text;
// 脱敏手机号:138****1234
result = result.replace(/1[3-9]\d{9}/g, (match) => {
return match.substring(0, 3) + '****' + match.substring(7);
});
// 脱敏身份证号
result = result.replace(/\d{17}[\dXx]/g, (match) => {
return match.substring(0, 6) + '********' + match.substring(14);
});
// 脱敏邮箱
result = result.replace(/[\w.-]+@[\w.-]+\.\w+/g, (match) => {
const parts = match.split('@');
return parts[0].substring(0, 2) + '***@' + parts[1];
});
return result;
}
/**
* 检查是否超过转发限流
*/
private checkRateLimit(): boolean {
const now = Date.now();
const oneHourAgo = now - 60 * 60 * 1000;
// 清理1小时前的时间戳
this.forwardTimestamps = this.forwardTimestamps.filter(ts => ts > oneHourAgo);
// 检查是否超过限制
if (this.forwardTimestamps.length >= this.forwardConfig.maxForwardPerHour) {
return false; // 超过限流
}
// 记录本次转发时间
this.forwardTimestamps.push(now);
return true;
}
/**
* 模拟转发通知到其他设备
* 实际开发中可使用分布式软总线或云端API
*/
private async forwardNotification(record: {
bundleName: string;
title: string;
text: string;
category: string;
}): Promise<void> {
// 检查转发开关
if (!this.forwardConfig.enabled) {
return;
}
// 检查分类过滤
if (!this.forwardConfig.filterCategories.includes(record.category)) {
return;
}
// 检查限流
if (!this.checkRateLimit()) {
console.warn('[转发] 超过每小时转发上限');
return;
}
// 脱敏处理
const safeTitle = this.stripSensitiveInfo(record.title);
const safeText = this.stripSensitiveInfo(record.text);
// 模拟转发(实际开发中替换为真实的跨设备通信逻辑)
console.info(`[转发] 通知已转发: ${safeTitle} - ${safeText}`);
this.forwardCount++;
// 在实际项目中,这里可以使用分布式数据管理或RPC进行跨设备通信
// 例如:
// await distributedDataObject.setSessionId('notification_sync');
// await rpc.RemoteObject.sendMessageRequest(...);
}
/**
* 创建带转发功能的通知订阅者
*/
private createForwardSubscriber(): notificationManager.NotificationSubscriber {
return {
onConsume: (data: notificationManager.SubscribeInfo) => {
let title = '';
let text = '';
if (data.content?.normal) {
title = data.content.normal.title || '';
text = data.content.normal.text || '';
}
// 确定分类
const categoryMap: Record<number, string> = {
0: '社交通信',
1: '服务提醒',
2: '内容推荐',
3: '其他'
};
const category = categoryMap[data.slotType ?? 3] || '其他';
// 记录日志
const now = new Date();
const timeStr = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
this.logText = `[${timeStr}] 收到通知\n` +
`来源: ${data.bundleName}\n` +
`分类: ${category}\n` +
`标题: ${title}\n` +
`正文: ${text}`;
// 尝试转发
this.forwardNotification({
bundleName: data.bundleName || '',
title,
text,
category
});
},
onConnect: () => {
this.isSubscribed = true;
this.logText = '订阅成功,通知转发服务已启动';
},
onDisconnect: () => {
this.isSubscribed = false;
this.logText = '订阅已断开,转发服务已停止';
},
onCancel: () => {},
onUpdate: () => {},
onDestroy: () => {
this.isSubscribed = false;
},
onDoNotDisturbDateChange: () => {},
onEnabledNotificationChanged: () => {}
};
}
/**
* 开始订阅
*/
async startSubscribe(): Promise<void> {
try {
const subscriber = this.createForwardSubscriber();
await notificationManager.subscribe(subscriber);
} catch (err) {
const error = err as BusinessError;
this.logText = `订阅失败: ${error.message}`;
}
}
/**
* 取消订阅
*/
async stopSubscribe(): Promise<void> {
try {
const subscriber = this.createForwardSubscriber();
await notificationManager.unsubscribe(subscriber);
this.isSubscribed = false;
this.logText = '已取消订阅';
} catch (err) {
const error = err as BusinessError;
this.logText = `取消失败: ${error.message}`;
}
}
aboutToAppear(): void {
// 显示当前配置
this.configText = `转发: ${this.forwardConfig.enabled ? '开' : '关'} | ` +
`分类: ${this.forwardConfig.filterCategories.join(', ')} | ` +
`脱敏: ${this.forwardConfig.stripSensitiveData ? '开' : '关'} | ` +
`限流: ${this.forwardConfig.maxForwardPerHour}条/小时`;
}
build() {
Scroll() {
Column({ space: 16 }) {
Text('通知转发服务')
.fontSize(24)
.fontWeight(FontWeight.Bold)
// 配置信息
Text(this.configText)
.fontSize(12)
.fontColor('#666666')
.width('90%')
.padding(8)
.borderRadius(8)
.backgroundColor('#E3F2FD')
.textAlign(TextAlign.Center)
// 状态信息
Text(this.logText)
.fontSize(13)
.width('90%')
.padding(16)
.borderRadius(12)
.backgroundColor('#F0F4F8')
.minHeight(100)
Text(`已转发: ${this.forwardCount} 条通知`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Row({ space: 16 }) {
Button('开始订阅')
.width('40%')
.height(50)
.backgroundColor('#4CAF50')
.onClick(() => this.startSubscribe())
Button('取消订阅')
.width('40%')
.height(50)
.backgroundColor('#F44336')
.onClick(() => this.stopSubscribe())
}
// 安全提示
Row() {
Text('⚠️ 安全提示')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#F44336')
}
.width('90%')
.padding(12)
.borderRadius(8)
.backgroundColor('#FFF3E0')
Text('通知监听涉及用户隐私,请确保:\n' +
'1. 已获得用户明确授权\n' +
'2. 敏感数据已脱敏处理\n' +
'3. 转发数据使用加密传输\n' +
'4. 遵守当地隐私法规')
.fontSize(12)
.fontColor('#666666')
.width('90%')
}
.width('100%')
.padding(20)
}
.width('100%')
.height('100%')
}
}
四、踩坑与注意事项
4.1 权限声明与用户授权
通知监听需要两个关键权限,缺一不可:
// module.json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.NOTIFICATION_CONTROLLER" // 通知控制权限
}
]
}
}
重要:ohos.permission.NOTIFICATION_CONTROLLER 是系统签名级别的权限,普通应用无法获取。这意味着通知监听功能主要面向系统应用或与系统签名的应用。
对于普通应用,如果需要实现类似功能,可以考虑:
- 使用
notificationManager.getActiveNotifications()获取自己的通知 - 使用
reminderAgentManager实现定时提醒 - 通过应用内消息机制替代系统通知监听
4.2 subscribe 和 unsubscribe 必须使用同一个 subscriber 实例
// ❌ 错误示范:每次创建新的 subscriber
await notificationManager.subscribe(this.createSubscriber());
// 后续取消时又创建了一个新的实例
await notificationManager.unsubscribe(this.createSubscriber()); // 无法取消!
// ✅ 正确做法:保存 subscriber 实例
private subscriber: notificationManager.NotificationSubscriber | null = null;
// 订阅时
this.subscriber = this.createSubscriber();
await notificationManager.subscribe(this.subscriber);
// 取消时
if (this.subscriber) {
await notificationManager.unsubscribe(this.subscriber);
}
4.3 onConsume 回调中的 UI 更新
onConsume 回调在系统线程中执行,如果需要更新 UI,必须确保状态变量的更新是线程安全的。在 ArkTS 中,@State 变量的更新会自动触发 UI 刷新,但在高频通知场景下,建议做防抖处理:
// ✅ 防抖处理:避免高频通知导致UI卡顿
private pendingUpdate: boolean = false;
onConsume: (data) => {
// 记录数据
this.notificationRecords.unshift({...});
// 防抖:100ms内只更新一次UI
if (!this.pendingUpdate) {
this.pendingUpdate = true;
setTimeout(() => {
this.notificationLog = this.formatNotificationLog();
this.pendingUpdate = false;
}, 100);
}
}
4.4 通知监听与电池消耗
长时间的通知监听会持续消耗电池。建议:
- 在不需要时及时取消订阅
- 使用
onDisconnect回调检测异常断开并重连 - 避免在后台长时间持有订阅
4.5 隐私合规
通知监听能读取其他应用的通知内容,这涉及严重的隐私问题:
- 最小化数据收集:只收集必要的信息,不要存储完整的通知内容
- 数据脱敏:转发或存储前必须脱敏处理
- 用户知情:在应用中明确告知用户监听的范围和用途
- 合规审查:上架前需要通过隐私合规审查
4.6 回调方法的完整性
NotificationSubscriber 接口要求实现所有回调方法。如果某些回调不需要处理,也必须提供空实现:
// ❌ 错误:只实现 onConsume
const subscriber = {
onConsume: (data) => { ... }
// 缺少其他回调,编译会报错
};
// ✅ 正确:所有回调都实现
const subscriber = {
onConsume: (data) => { ... },
onCancel: () => {},
onUpdate: () => {},
onConnect: () => {},
onDisconnect: () => {},
onDestroy: () => {},
onDoNotDisturbDateChange: () => {},
onEnabledNotificationChanged: () => {}
};
五、HarmonyOS 6 适配
5.1 API 变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 订阅方式 | 同步回调 | 新增异步回调模式 |
| 权限模型 | NOTIFICATION_CONTROLLER | 新增细粒度监听权限 |
| 跨设备监听 | 不支持 | 新增分布式通知监听 |
| 隐私保护 | 基础脱敏 | 新增自动脱敏 API |
| 批量订阅 | 不支持 | 新增按应用/渠道批量订阅 |
5.2 迁移指南
- 细粒度权限:HarmonyOS 6 对通知监听权限做了更细粒度的划分,可以指定只监听特定应用的通知:
// HarmonyOS 6 新增:指定监听的应用
const subscribeInfo: notificationManager.SubscribeInfo = {
bundleNames: ['com.example.chat', 'com.example.mail'], // 只监听这两个应用
slotTypes: [notificationManager.SlotType.SOCIAL_COMMUNICATION] // 只监听社交通信渠道
};
await notificationManager.subscribe(subscriber, subscribeInfo);
- 自动脱敏 API:HarmonyOS 6 提供了内置的脱敏工具:
// HarmonyOS 6 新增:自动脱敏
import { privacyUtils } from '@kit.PrivacyKit';
const safeText = privacyUtils.desensitize(originalText, {
phone: true, // 脱敏手机号
email: true, // 脱敏邮箱
idCard: true, // 脱敏身份证
bankCard: true // 脱敏银行卡
});
- 分布式通知监听:HarmonyOS 6 支持监听同一账号下其他设备的通知,实现跨设备通知同步。
六、总结
mindmap
root((通知监听))
核心接口
NotificationSubscriber
onConsume 通知消费
onCancel 通知取消
onUpdate 通知更新
onConnect 连接成功
onDisconnect 断开连接
onDestroy 销毁
onDoNotDisturbDateChange 免打扰变更
onEnabledNotificationChanged 开关变更
核心API
subscribe 订阅通知
unsubscribe 取消订阅
getActiveNotifications 获取活跃通知
应用场景
通知聚合
智能过滤
跨设备转发
辅助功能
安全与隐私
NOTIFICATION_CONTROLLER权限
系统签名级别
敏感数据脱敏
转发限流
隐私合规审查
注意事项
subscribe/unsubscribe同一实例
onConsume中UI防抖
所有回调必须实现
及时取消订阅省电
最小化数据收集
| 知识点 | 要点 |
|---|---|
| NotificationSubscriber | 通知监听的核心接口,需实现8个回调方法 |
| onConsume | 最常用的回调,新通知发布时触发 |
| subscribe/unsubscribe | 必须使用同一个 subscriber 实例 |
| 权限要求 | 需要 NOTIFICATION_CONTROLLER 系统签名权限 |
| 数据脱敏 | 转发前必须脱敏手机号、身份证号等敏感信息 |
| 限流机制 | 设置每小时最大转发数,避免通知风暴 |
| UI防抖 | 高频通知场景下,onConsume 中更新 UI 需做防抖处理 |
| 隐私合规 | 最小化数据收集,用户知情同意,通过合规审查 |
通知监听是通知体系中最强大也最敏感的能力。它就像一把万能钥匙——能打开所有门,但也意味着巨大的责任。使用时务必遵循最小权限原则,做好安全防护,让这项能力真正为用户服务,而不是成为隐私的漏洞。
- 点赞
- 收藏
- 关注作者
评论(0)