通知负责把应用内的重要变化呈现在系统通知中心,提醒则负责在应用不运行时按时间或日程触达用户。二者看起来都在“弹消息”,但生命周期、可靠性和适用场景并不相同。
本文以一个待办应用为例,从普通通知开始,逐步实现进度通知、点击通知进入指定页面,以及由系统托管的定时提醒。同时梳理权限、开关、重复发布、数据更新和排障要点,让通知能力能够真正用于生产环境。
在写代码之前,先根据业务发生的时机选择能力:
需求 | 推荐能力 | 示例 |
|---|---|---|
业务已经发生,立即告知用户 | NotificationKit | 下载完成、审核结果、聊天消息 |
长任务需要持续展示状态 | 进度通知 | 文件下载、数据迁移 |
到达指定时间再触达用户 | ReminderAgent | 待办到期、日历日程、倒计时 |
只更新当前页面 | ArkUI 状态管理 | 表单校验、列表刷新 |
通知不是后台任务执行器。发布通知以后,应用进程仍可能被系统回收;需要继续下载、定位或播放时,应先使用与业务匹配的后台任务能力,再把通知作为状态反馈。
提醒也不是精确执行任意代码的计时器。它由系统托管,在符合规则的时间展示提醒,适合用户可感知的日程场景,不应被用来绕过后台运行限制。
HarmonyOS 的通知能力位于 NotificationKit。基础流程包括构造请求、发布通知和处理异常。
import { notificationManager } from '@kit.NotificationKit'
import { BusinessError } from '@kit.BasicServicesKit'
export class TodoNotificationService {
async publishCompleted(todoId: string, todoTitle: string): Promise<void> {
const request: notificationManager.NotificationRequest = {
id: this.toNotificationId(todoId),
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: '任务已完成',
text: todoTitle,
additionalText: '点击查看详情'
}
}
}
try {
await notificationManager.publish(request)
} catch (error) {
const err = error as BusinessError
console.error(`publish notification failed: ${err.code}, ${err.message}`)
throw err
}
}
private toNotificationId(todoId: string): number {
let hash = 0
for (let index = 0; index < todoId.length; index++) {
hash = (hash * 31 + todoId.charCodeAt(index)) & 0x7fffffff
}
return hash || 1
}
}id 不应随意使用随机数。相同业务对象使用稳定 ID,再次发布时可以更新原通知,避免通知中心堆出多条重复消息。不同业务对象则应映射到不同 ID。
不同 SDK 版本的类型导入和字段可能略有调整,实际项目应以当前 API 参考和工程编译结果为准。
用户有权关闭应用通知。生产代码不能把 publish() 成功调用等同于用户一定看到了通知,而应在合适的交互时机检查授权状态。
import { common } from '@kit.AbilityKit'
import { notificationManager } from '@kit.NotificationKit'
export async function ensureNotificationEnabled(
context: common.UIAbilityContext
): Promise<boolean> {
const enabled = await notificationManager.isNotificationEnabled()
if (enabled) {
return true
}
// 应由用户点击“开启通知”等明确操作触发,不要在启动时反复打扰。
await notificationManager.requestEnableNotification(context)
return notificationManager.isNotificationEnabled()
}授权设计需要遵守三个原则:
还要区分应用级通知开关和通知槽级别的设置。应用级开关开启,并不代表每一类通知都能以声音、横幅等方式出现。
通知槽可以把消息按业务用途分组,例如“任务提醒”“同步状态”“营销活动”。用户可以分别控制这些类别,应用也能设置合理的重要程度和提示方式。
创建通知槽时应注意:
import { notificationManager } from '@kit.NotificationKit'
const REMINDER_SLOT_ID = 'todo_reminder'
export async function ensureReminderSlot(): Promise<void> {
const slot: notificationManager.NotificationSlot = {
type: notificationManager.SlotType.SOCIAL_COMMUNICATION,
name: '任务提醒',
description: '待办到期和日程变化提醒',
level: notificationManager.SlotLevel.LEVEL_HIGH
}
await notificationManager.addSlot(slot)
}槽类型和可配置字段会随系统版本演进。如果业务需要特定声音、振动或锁屏展示,应在目标设备和目标 API 版本上验证,不能只看模拟器效果。
发布请求中应关联业务对应的槽。若当前 SDK 通过 slotType 或其他字段关联,请以当前类型定义为准,确保创建和发布使用同一业务分类。
通知只有文字还不够。用户点击后通常要进入订单、消息或待办详情,而不是停留在应用首页。
HarmonyOS 可以通过 WantAgent 描述点击后的动作。核心思路是把业务 ID 放进 Want 参数,点击通知时拉起目标 UIAbility,再由页面路由到详情。
import { wantAgent, WantAgentInfo, OperationType, WantAgentFlags } from '@kit.AbilityKit'
import { notificationManager } from '@kit.NotificationKit'
async function createTodoWantAgent(todoId: string): Promise<wantAgent.WantAgent> {
const info: WantAgentInfo = {
wants: [{
bundleName: 'com.example.todo',
abilityName: 'EntryAbility',
parameters: {
route: 'TodoDetail',
todoId
}
}],
operationType: OperationType.START_ABILITY,
requestCode: Math.abs(todoId.length * 1009),
wantAgentFlags: [WantAgentFlags.UPDATE_PRESENT_FLAG]
}
return wantAgent.getWantAgent(info)
}
export async function publishTodoReminder(todoId: string, title: string): Promise<void> {
const agent = await createTodoWantAgent(todoId)
const request: notificationManager.NotificationRequest = {
id: Date.now() % 100000000,
wantAgent: agent,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: '待办即将到期',
text: title
}
}
}
await notificationManager.publish(request)
}在 UIAbility 中读取参数时,要同时覆盖冷启动和热启动:
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.handleNotificationWant(want)
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.handleNotificationWant(want)
}
private handleNotificationWant(want: Want): void {
const route = want.parameters?.route as string | undefined
const todoId = want.parameters?.todoId as string | undefined
if (route === 'TodoDetail' && todoId) {
AppStorage.setOrCreate('pendingTodoId', todoId)
}
}
}页面出现后消费 pendingTodoId 并调用 Navigation 跳转。消费完成要清空该值,避免页面重建时重复导航。
不要把敏感信息放进 Want 参数。通知链路只传业务主键或一次性令牌,详情数据应在进入应用后从受保护的本地存储或服务端重新读取。
通知生命周期不仅有“发布”,还包括更新和取消。
下载任务可以使用同一个通知 ID 持续更新内容。更新频率要节流,例如进度变化明显或间隔达到一定时间再发布,避免频繁跨进程调用。
import { notificationManager } from '@kit.NotificationKit'
export async function updateDownloadProgress(
notificationId: number,
fileName: string,
progress: number
): Promise<void> {
const normalized = Math.max(0, Math.min(100, progress))
await notificationManager.publish({
id: notificationId,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: '正在下载',
text: fileName,
additionalText: `${normalized}%`
}
}
})
}业务失效后应主动取消。例如待办被删除、消息已在其他设备读取,旧通知继续存在会误导用户。
export async function cancelTodoNotification(notificationId: number): Promise<void> {
await notificationManager.cancel(notificationId)
}如果应用重装、账号切换或本地映射丢失,还要设计清理策略。通知 ID 与账号、业务类型和业务主键的映射应集中管理,避免各模块自行生成导致冲突。
普通通知适合“事件已经发生”。若用户创建了明天上午的待办,需要系统到时再提醒,应使用 ReminderAgent,而不是让应用保持运行或使用普通定时器。
import { reminderAgentManager } from '@kit.BackgroundTasksKit'
export async function scheduleTodoAlarm(
todoId: string,
title: string,
hour: number,
minute: number
): Promise<number> {
const reminder: reminderAgentManager.ReminderRequestAlarm = {
reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,
hour,
minute,
title: '待办提醒',
content: title,
expiredContent: '待办提醒已过期',
notificationId: Math.abs(todoId.length * 997),
slotType: reminderAgentManager.SlotType.SOCIAL_COMMUNICATION,
wantAgent: {
pkgName: 'com.example.todo',
abilityName: 'EntryAbility'
}
}
return reminderAgentManager.publishReminder(reminder)
}publishReminder() 返回的提醒 ID 必须持久化,并与待办记录关联。用户修改提醒时间时,先取消旧提醒,再发布新提醒;用户删除待办时同步取消:
export async function replaceReminder(
oldReminderId: number | undefined,
createNew: () => Promise<number>
): Promise<number> {
if (oldReminderId !== undefined) {
await reminderAgentManager.cancelReminder(oldReminderId)
}
return createNew()
}实际项目还要处理设备重启、时区变化、夏令时和重复提醒规则。日历类业务应存储用户选择的本地时间语义,同时保留时区信息,避免只保存一个毫秒时间戳后产生跨时区偏差。
提醒能力涉及相应权限和配置声明,字段也会因提醒类型与 SDK 版本不同而变化。接入时应检查当前版本的 module.json5 配置、权限文档和接口定义。
页面和业务模块不应直接散落调用系统 API。可以抽象一个面向业务的接口:
export interface AppNotificationService {
notifyTodoDue(todoId: string, title: string): Promise<void>
notifySyncResult(successCount: number, failedCount: number): Promise<void>
scheduleTodo(todoId: string, title: string, time: Date): Promise<number>
cancelNotification(notificationId: number): Promise<void>
cancelReminder(reminderId: number): Promise<void>
}实现层负责:
这样 ViewModel 只表达“待办到期需要通知”,不依赖系统 API 的具体结构,也更容易在单元测试中替换为假实现。
通知失败时,日志至少应包含业务类型、通知 ID、错误码和系统开关状态,但不要记录通知正文、账号信息等敏感数据。
export async function publishSafely(
request: notificationManager.NotificationRequest,
scene: string
): Promise<boolean> {
try {
const enabled = await notificationManager.isNotificationEnabled()
if (!enabled) {
console.info(`notification disabled, scene=${scene}`)
return false
}
await notificationManager.publish(request)
return true
} catch (error) {
const err = error as BusinessError
console.error(
`notification failed, scene=${scene}, id=${request.id}, code=${err.code}`
)
return false
}
}业务层需要区分两类结果:
对于服务端推送,还应建立服务端消息 ID、客户端业务 ID 和系统通知 ID 的关联,以便分析重复、延迟和点击转化。
依次检查:
检查 WantAgent 的 bundleName、abilityName 和参数;确认 onCreate() 与 onNewWant() 都处理了跳转;确认 ArkUI 页面已初始化后再执行 Navigation。
通常是 WantAgent 的 requestCode 固定,或更新标志与参数组合不正确。每个业务对象应有稳定且可区分的请求码,并验证系统是否复用了旧 WantAgent。
检查是否持久化了 ReminderAgent 返回的提醒 ID。仅更新数据库并不会自动替换系统中的提醒,必须显式取消旧提醒并重新发布。
不要只在 UI 层去重。应使用服务端消息 ID 或业务事件 ID 建立持久化去重记录,并让通知 ID 保持稳定。进程重启后,内存集合会丢失,无法承担可靠去重。
通知和提醒高度依赖系统状态,至少需要真机覆盖以下路径:
自动化测试可以覆盖 ID 映射、去重、参数构造和 ViewModel 行为;系统通知中心展示、声音、振动和点击拉起仍应保留真机验收。
可靠的通知能力不是简单调用一次 publish(),而是一套完整链路:
完成这些基础设施后,通知才能从“偶尔弹出一条消息”变成稳定、可维护、尊重用户的业务触达能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。