[鸿蒙从零到一] HarmonyOS 通知与提醒实战:消息发布、点击跳转与定时触达

简介: 本文详解鸿蒙HarmonyOS通知与提醒实战:区分NotificationKit(即时通知)与ReminderAgent(定时提醒),涵盖权限申请、通知槽管理、点击跳转、进度更新、定时触发、ID去重、错误处理及真机测试要点,助开发者构建稳定可靠的生产级消息触达能力

[鸿蒙从零到一] HarmonyOS 通知与提醒实战:消息发布、点击跳转与定时触达

前言

通知负责把应用内的重要变化呈现在系统通知中心,提醒则负责在应用不运行时按时间或日程触达用户。二者看起来都在“弹消息”,但生命周期、可靠性和适用场景并不相同。

本文以一个待办应用为例,从普通通知开始,逐步实现进度通知、点击通知进入指定页面,以及由系统托管的定时提醒。同时梳理权限、开关、重复发布、数据更新和排障要点,让通知能力能够真正用于生产环境。


一、先区分通知与提醒

在写代码之前,先根据业务发生的时机选择能力:

需求 推荐能力 示例
业务已经发生,立即告知用户 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()
}

授权设计需要遵守三个原则:

  • 在用户理解收益的场景中请求,例如用户开启“到期提醒”时。
  • 请求前用简短文案说明用途,不承诺通知一定送达。
  • 用户拒绝后保留核心功能,并提供进入系统设置的入口。

还要区分应用级通知开关和通知槽级别的设置。应用级开关开启,并不代表每一类通知都能以声音、横幅等方式出现。


四、用通知槽管理不同消息类型

通知槽可以把消息按业务用途分组,例如“任务提醒”“同步状态”“营销活动”。用户可以分别控制这些类别,应用也能设置合理的重要程度和提示方式。

创建通知槽时应注意:

  • 槽 ID 一旦上线就视为稳定协议,不要频繁变更。
  • 重要级别由真实业务决定,不要把普通消息都设成强提醒。
  • 名称要让用户能理解,避免使用内部模块名。
  • 创建操作应具备幂等性,可在应用初始化或首次使用能力时执行。
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 参数。通知链路只传业务主键或一次性令牌,详情数据应在进入应用后从受保护的本地存储或服务端重新读取。


六、更新、取消与去重

通知生命周期不仅有“发布”,还包括更新和取消。

6.1 更新进度

下载任务可以使用同一个通知 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}%`
      }
    }
  })
}

6.2 取消通知

业务失效后应主动取消。例如待办被删除、消息已在其他设备读取,旧通知继续存在会误导用户。

export async function cancelTodoNotification(notificationId: number): Promise<void> {
   
  await notificationManager.cancel(notificationId)
}

如果应用重装、账号切换或本地映射丢失,还要设计清理策略。通知 ID 与账号、业务类型和业务主键的映射应集中管理,避免各模块自行生成导致冲突。


七、使用 ReminderAgent 托管定时提醒

普通通知适合“事件已经发生”。若用户创建了明天上午的待办,需要系统到时再提醒,应使用 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>
}

实现层负责:

  • 权限和开关检查;
  • 通知槽初始化;
  • 业务 ID 到通知 ID 的映射;
  • WantAgent 构建;
  • 提醒 ID 持久化;
  • 错误码转换和日志记录;
  • 频率控制与重复消息合并。

这样 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 的关联,以便分析重复、延迟和点击转化。


十、常见问题排查

10.1 调用成功但没有看到通知

依次检查:

  • 应用级通知开关是否开启;
  • 对应通知槽是否关闭或被设置为静默;
  • 通知 ID 是否与现有通知相同,导致原通知被更新;
  • 设备是否处于免打扰、锁屏限制等状态;
  • 标题和内容是否为空,字段是否符合当前 API 约束。

10.2 点击通知只进入首页

检查 WantAgent 的 bundleNameabilityName 和参数;确认 onCreate()onNewWant() 都处理了跳转;确认 ArkUI 页面已初始化后再执行 Navigation。

10.3 多条通知点击后进入同一个详情

通常是 WantAgent 的 requestCode 固定,或更新标志与参数组合不正确。每个业务对象应有稳定且可区分的请求码,并验证系统是否复用了旧 WantAgent。

10.4 修改待办后旧提醒仍然触发

检查是否持久化了 ReminderAgent 返回的提醒 ID。仅更新数据库并不会自动替换系统中的提醒,必须显式取消旧提醒并重新发布。

10.5 重复收到相同消息

不要只在 UI 层去重。应使用服务端消息 ID 或业务事件 ID 建立持久化去重记录,并让通知 ID 保持稳定。进程重启后,内存集合会丢失,无法承担可靠去重。


十一、测试清单

通知和提醒高度依赖系统状态,至少需要真机覆盖以下路径:

  • 首次请求授权、同意、拒绝和拒绝后再次进入设置;
  • 前台、后台、进程被回收时的通知展示;
  • 冷启动点击通知与应用存活时点击通知;
  • 同一业务通知的更新、取消和重复消息合并;
  • 应用通知开启但业务通知槽关闭;
  • 修改时间、删除待办后提醒是否正确替换或取消;
  • 重启设备、切换时区、跨天后的提醒行为;
  • 长标题、多语言、隐私内容在锁屏上的展示;
  • 多账号切换后旧账号通知和提醒是否清理。

自动化测试可以覆盖 ID 映射、去重、参数构造和 ViewModel 行为;系统通知中心展示、声音、振动和点击拉起仍应保留真机验收。


总结

可靠的通知能力不是简单调用一次 publish(),而是一套完整链路:

  • 已发生的业务事件使用 NotificationKit 即时通知;
  • 未来时间点的用户提醒交给 ReminderAgent 托管;
  • 用通知槽区分业务类别,尊重用户的开关和提示偏好;
  • 用稳定 ID 更新和取消通知,避免重复堆积;
  • 通过 WantAgent 传递最小业务参数,并覆盖冷、热启动跳转;
  • 持久化提醒 ID,在业务修改或删除时同步维护系统提醒;
  • 把权限、错误、去重和测试纳入统一服务,而不是散落在页面中。

完成这些基础设施后,通知才能从“偶尔弹出一条消息”变成稳定、可维护、尊重用户的业务触达能力。

相关文章
|
8天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2188 12
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
8天前
|
云安全 人工智能 安全
|
8天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max-Preview深度全解析:2.4万亿参数旗舰MoE模型+Token Plan限时优惠完整落地指南
2026年7月,全新旗舰级混合专家大模型Qwen3.8-Max-Preview正式开放抢先体验,作为通义千问Qwen3系列规格最高、综合推理能力顶尖的新一代模型,该模型总参数量达到2.4万亿(2.4T),是当前线上可调用的原生多模态旗舰模型,综合推理水准对标海外顶级Fable 5模型,在复杂工程开发、长文档深度分析、多步骤智能体自治、跨境多语言创作、海量数据挖掘五大高难度业务场景实现跨越式性能提升。
986 1
|
10天前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
988 44
|
8天前
|
人工智能 自然语言处理 数据挖掘
最新版通义千问(Qwen3.8-Max-Preview)功能介绍
2026年,通义千问正式推出全新旗舰级大模型 **Qwen3.8-Max-Preview 预览版**,作为首款突破万亿参数规格的新一代基座模型,该模型总参数量达到**2.4万亿**,采用全新迭代的MoE混合专家架构,综合推理性能、长文本处理、多模态理解、复杂任务规划能力全面超越前代Qwen3.7-Max版本,整体实力跻身全球第一梯队,可对标海外顶级旗舰模型,是当前面向复杂工程开发、多智能体协同、超长文档解析、专业办公自动化场景的最优国产基座模型。
997 0
|
6天前
|
自然语言处理 测试技术 API
通义千问Qwen3.8-Max-Preview全功能解析:2.4万亿参数旗舰模型深度使用指南
在大模型技术持续迭代的当下,通义千问推出的Qwen3.8-Max-Preview作为新一代旗舰预览版模型,凭借2.4万亿参数的超大规模、多模态融合能力与全场景适配特性,成为开发者与企业用户探索AI应用的核心工具。该模型采用稀疏混合专家(MoE)架构,是通义千问首个突破万亿参数的多模态模型,可同时处理文本、图像、视频与文档等多种数据形态,在全栈代码开发、复杂逻辑推理、长文档分析与多智能体协作等场景实现跨越式升级。本文将全面拆解Qwen3.8-Max-Preview的核心功能,详解API调用流程与配置方法,覆盖多场景实战技巧,帮助用户快速掌握这款旗舰模型的使用方法,充分释放其性能潜力。
480 1
|
9天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max 预览版全解析:2.4 万亿参数旗舰模型,Token Plan 限时优惠指南
Qwen3.8-Max-Preview是通义千问Qwen3系列旗舰MoE大模型,参数达2.4万亿,综合推理能力居行业第一梯队。支持思考/快速双模式,擅长大模型五大高难场景。现于阿里云百炼Token Plan、Qoder及QoderWork上线体验,个人版低至39元/月。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
689 1
Qwen3.8-Max 预览版全解析:2.4 万亿参数旗舰模型,Token Plan 限时优惠指南