摘要

Notification 通知模块用于在系统状态栏弹出消息提醒,包含普通文本通知、多行详情通知、大图片横幅通知、常驻后台通知,适用于消息推送、定时提醒、任务进度、离线消息等场景。API Version23 重构通知渠道管理、权限校验、通知生命周期、点击跳转 Want 逻辑,修复低版本通知无法弹出、点击无响应、重复推送、后台进程通知消失、渠道删除残留等兼容缺陷。旧项目升级 API23 后常出现:发送通知无任何状态栏提示、点击通知无法跳转页面、退出应用通知自动清除、多次推送产生大量重复通知、无通知权限直接报错闪退等问题。本文基于 DevEco Studio 适配 OpenHarmony API23 及以上版本,封装全局通知工具类,实现普通文本通知、大图通知、常驻通知、通知点击路由跳转四大核心能力,搭配消息提醒、后台定时任务两套实战页面提供完整可运行代码,输出通知权限、渠道创建、资源销毁规范,汇总版本升级兼容故障解决方案,为鸿蒙消息提醒功能开发提供标准化实操模板。

关键词

OpenHarmony;ArkUI;API Version23;Notification;系统通知;消息推送;通知渠道;通知点击跳转

一、引言

1.1 通知开发背景

聊天消息、日程提醒、下载进度、离线推送都依赖系统通知能力,通知独立于应用页面,锁屏、应用切后台状态下仍可展示在状态栏。系统对通知实行渠道分组管理,同类型消息共用一个渠道,用户可在系统设置单独关闭某一类通知。 OpenHarmony API Version23 针对 Notification 底层调度引擎完成全面升级,核心变更点:

  1. 强制所有通知必须先创建 NotificationChannel 渠道,无渠道发送直接失败;
  2. 重构通知动态权限校验逻辑,未授予通知权限拦截推送并友好提示;
  3. 标准化点击通知 Want 跳转规则,区分打开页面、跳转 Ability;
  4. 优化通知缓存队列,限制同一渠道短时间重复推送频率,避免刷屏;
  5. 新增常驻通知(ONGOING)生命周期管控,退出应用不会自动移除;
  6. 修复通知 ID 复用覆盖原有通知、渠道删除后无法重建的 bug。

大量 API9~11 旧项目升级后,调用推送接口状态栏无消息,根源是缺少渠道创建逻辑、未申请通知动态权限,因此掌握 API23 标准通知封装规范是消息提醒类功能必备技能。

1.2 开发环境与测试场景

开发工具:DevEco Studio 5.0 及以上 适配系统:OpenHarmony API Version23、HarmonyOS NEXT 导入模块:@ohos.notification、@ohos.notificationManager 前置权限:动态通知权限 ohos.permission.NOTIFICATION_AGENT 测试场景:普通文字消息通知、大图预览通知、常驻后台提醒、点击通知跳转指定页面、清除全部通知、删除单条通知

二、API23+ Notification 通知核心 API 与版本变更说明

2.1 核心模块导入

ets

import notificationManager from '@ohos.notificationManager'
import notification from '@ohos.notification'

2.2 通知渠道创建(API23 强制前置)

ets

// 创建渠道配置
const channel: notification.NotificationChannel = {
  id: "msg_channel",
  name: "消息通知",
  description: "应用普通消息提醒",
  importance: notification.NotificationImportance.LEVEL_3
}
await notificationManager.createChannel(channel)

importance 优先级等级:

  • LEVEL_0:无提醒,静默存储
  • LEVEL_3:状态栏展示 + 提示音(常规消息推荐)
  • LEVEL_4:横幅弹窗强提醒

2.3 通知基础推送结构

  1. NotificationRequest:通知载体,包含 id、渠道 ID、内容、点击跳转 want
  2. NotificationContent:通知内容,支持普通文本、大图、多行文本
  3. Want:点击通知后跳转页面路由参数

2.4 常用控制 API

  1. notificationManager.publish (request):推送通知
  2. notificationManager.cancel (id):根据 ID 删除单条通知
  3. notificationManager.cancelAll ():清空本应用全部通知
  4. notificationManager.getChannels ():获取已创建渠道列表

2.5 API23 废弃与强制约束

  1. 废弃无渠道直接推送通知,必须提前创建对应 channel;
  2. 通知属于动态敏感权限,页面调用前必须申请 NOTIFICATION_AGENT;
  3. 相同 channel + 相同 id 会覆盖旧通知,不同 id 生成多条独立消息;
  4. 常驻 ONGOING 通知无法被用户手动清除,仅代码可 cancel;
  5. 后台无进程状态下部分设备限制普通通知推送,常驻通知不受限。

三、API23 标准基础示例代码

3.1 创建通知渠道 + 普通文本通知推送

ets

import notificationManager from '@ohos.notificationManager'
import notification from '@ohos.notification'
import Want from '@ohos.app.ability.Want'
import router from '@ohos.router'

async function createMsgChannel() {
  const channel: notification.NotificationChannel = {
    id: "msg_default",
    name: "通用消息",
    description: "应用各类消息推送提醒",
    importance: notification.NotificationImportance.LEVEL_3
  }
  await notificationManager.createChannel(channel)
}

async function sendSimpleNotify(id: number, title: string, content: string) {
  await createMsgChannel()
  // 点击跳转首页Want
  let want: Want = {
    bundleName: getContext(this).bundleName,
    abilityName: "EntryAbility",
    uri: "pages/index/index"
  }
  const notifyRequest: notification.NotificationRequest = {
    id: id,
    channelId: "msg_default",
    content: {
      contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
      normal: { title: title, text: content }
    },
    want: want
  }
  await notificationManager.publish(notifyRequest)
}

3.2 清除指定 ID 通知

ets

async function cancelSingleNotify(notifyId: number) {
  await notificationManager.cancel(notifyId)
}

async function clearAllNotify() {
  await notificationManager.cancelAll()
}

四、两大业务完整实战案例(全兼容 API23)

4.1 实战一:全局通知工具类封装(utils/notify_util.ets)

ets

import notificationManager from '@ohos.notificationManager'
import notification from '@ohos.notification'
import Want from '@ohos.app.ability.Want'
import common from '@ohos.app.ability.common'
import promptAction from '@ohos.promptAction'
import PermissionUtil, { PERMISSION } from './permission_util'

// 通知渠道常量统一管理
export const NOTIFY_CHANNEL = {
  MSG: "channel_msg",
  TASK: "channel_task"
}

class NotifyUtil {
  private static instance: NotifyUtil
  private ctx: common.UIAbilityContext | null = null

  static getInstance(): NotifyUtil {
    if (!NotifyUtil.instance) {
      NotifyUtil.instance = new NotifyUtil()
    }
    return NotifyUtil.instance
  }

  setContext(context: common.UIAbilityContext) {
    this.ctx = context
  }

  // 初始化消息通知渠道
  private async createMsgChannel() {
    const channel: notification.NotificationChannel = {
      id: NOTIFY_CHANNEL.MSG,
      name: "消息通知",
      description: "聊天、资讯类消息提醒",
      importance: notification.NotificationImportance.LEVEL_3
    }
    await notificationManager.createChannel(channel)
  }

  // 初始化后台任务常驻渠道
  private async createTaskChannel() {
    const channel: notification.NotificationChannel = {
      id: NOTIFY_CHANNEL.TASK,
      name: "后台任务",
      description: "下载、同步常驻提醒",
      importance: notification.NotificationImportance.LEVEL_2
    }
    await notificationManager.createChannel(channel)
  }

  // 推送普通文本通知
  async sendBasicNotify(notifyId: number, title: string, text: string, pageUrl: string) {
    if (!this.ctx) return
    // 前置校验通知权限
    const hasPerm = PermissionUtil.checkSingle("ohos.permission.NOTIFICATION_AGENT")
    if (!hasPerm) {
      promptAction.showToast({ message: "未开启通知权限,无法推送消息" })
      return
    }
    await this.createMsgChannel()
    // 构建跳转Want
    const want: Want = {
      bundleName: this.ctx.bundleName,
      abilityName: "EntryAbility",
      uri: pageUrl
    }
    const request: notification.NotificationRequest = {
      id: notifyId,
      channelId: NOTIFY_CHANNEL.MSG,
      content: {
        contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: { title, text }
      },
      want: want
    }
    try {
      await notificationManager.publish(request)
      promptAction.showToast({ message: "通知已发送至状态栏" })
    } catch (err) {
      promptAction.showToast({ message: "通知推送失败" })
      console.error("推送通知异常", err)
    }
  }

  // 推送常驻后台通知(无法手动清除)
  async sendOngoingTaskNotify(notifyId: number, title: string, text: string) {
    if (!this.ctx) return
    const hasPerm = PermissionUtil.checkSingle("ohos.permission.NOTIFICATION_AGENT")
    if (!hasPerm) return
    await this.createTaskChannel()
    const request: notification.NotificationRequest = {
      id: notifyId,
      channelId: NOTIFY_CHANNEL.TASK,
      content: {
        contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: { title, text }
      },
      ongoing: true // 标记为常驻通知
    }
    await notificationManager.publish(request)
  }

  // 删除单条通知
  async cancelNotify(notifyId: number) {
    await notificationManager.cancel(notifyId)
    promptAction.showToast({ message: "已移除该条通知" })
  }

  // 清空全部通知
  async clearAll() {
    await notificationManager.cancelAll()
    promptAction.showToast({ message: "已清空所有通知" })
  }
}

export default NotifyUtil.getInstance()

4.2 实战二:通知推送演示页面(普通通知 + 常驻任务通知)

ets

import NotifyUtil from '../utils/notify_util'
import PermissionUtil from '../utils/permission_util'

@Entry
@Component
struct NotificationPage {
  notifyId: number = 1001
  taskNotifyId: number = 9001

  aboutToAppear() {
    // 绑定页面上下文
    const ctx = getContext(this)
    NotifyUtil.setContext(ctx)
    PermissionUtil.setContext(ctx)
  }

  // 发送普通资讯通知,点击跳转首页
  async sendMsgNotify() {
    await NotifyUtil.sendBasicNotify(
      this.notifyId++,
      "新资讯推送",
      "今日鸿蒙技术更新,点击查看详情",
      "pages/index/index"
    )
  }

  // 发送常驻后台同步通知
  async sendSyncTaskNotify() {
    await NotifyUtil.sendOngoingTaskNotify(
      this.taskNotifyId,
      "文件同步中",
      "后台正在同步本地笔记数据"
    )
  }

  // 清除常驻任务通知
  async removeTaskNotify() {
    await NotifyUtil.cancelNotify(this.taskNotifyId)
  }

  // 清空全部通知
  async clearAllNotify() {
    await NotifyUtil.clearAll()
  }

  build() {
    Column({ space: 20 }) {
      Text("系统通知Notification演示")
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 })

      Button("推送普通资讯通知")
        .width(300)
        .height(46)
        .backgroundColor("#007DFF")
        .onClick(async () => await this.sendMsgNotify())

      Button("推送常驻后台同步通知")
        .width(300)
        .height(46)
        .backgroundColor("#009966")
        .onClick(async () => await this.sendSyncTaskNotify())

      Button("移除常驻同步通知")
        .width(300)
        .height(46)
        .backgroundColor("#ff9500")
        .onClick(async () => await this.removeTaskNotify())

      Button("清空全部应用通知")
        .width(300)
        .height(46)
        .backgroundColor("#f56c6c")
        .onClick(async () => await this.clearAllNotify())
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .padding(20)
    .backgroundColor("#F5F5F5")
  }
}

五、API23+ 通知适配与性能优化规范

5.1 渠道管理规范

  1. 按业务类型拆分渠道:消息、后台任务、活动推送分开创建;
  2. 页面推送前自动检测并创建渠道,无需手动提前执行;
  3. 同一渠道相同 ID 通知会覆盖,不同 ID 独立多条展示。

5.2 权限交互规范

  1. 推送通知前校验 NOTIFICATION_AGENT 动态权限,无权限阻断推送并提示;
  2. 应用首次使用通知功能主动申请权限,不静默失败;
  3. 用户永久拒绝权限时提示前往系统设置开启通知。

5.3 常驻通知业务规范

  1. 下载、同步、录音等后台服务使用 ongoing 常驻通知;
  2. 常驻通知用户无法手动清除,业务完成必须代码调用 cancel 删除;
  3. 普通聊天、资讯消息不使用常驻类型,允许用户上滑清除。

5.4 跳转 Want 规范

  1. Want 必须填写当前应用 bundleName 与 EntryAbility;
  2. uri 填写目标页面路由地址,点击通知自动跳转对应业务页面;
  3. 需要携带参数可在 want.parameters 中追加自定义键值。

六、API23 升级高频兼容问题与解决方案

问题 1:调用推送接口,状态栏完全不显示通知 解决:1. 创建对应 NotificationChannel 渠道;2. 授予 NOTIFICATION_AGENT 通知权限;3. 渠道 importance 等级≥LEVEL_2。

问题 2:多次推送通知只会显示一条,新消息覆盖旧消息 解决:每次推送使用不同 notifyId,相同 ID 会覆盖同渠道旧通知。

问题 3:点击通知无任何页面跳转 解决:完整配置 want 的 bundleName、abilityName、uri 页面路由。

问题 4:退出应用后常驻通知自动消失 解决:推送时设置 ongoing:true 标记为后台常驻通知。

问题 5:升级 API23 旧代码直接推送报错 解决:全部补充渠道创建逻辑,API9 及以下无强制渠道要求,API23 必须创建。

问题 6:短时间连续推送多条通知系统拦截不展示 解决:拆分不同 channel 或增加推送间隔,避免高频刷屏触发系统限流。

七、总结

Notification 系统通知是鸿蒙应用消息提醒标准实现方案,API Version23 强制引入通知渠道前置创建、动态权限校验机制,统一通知内容、点击跳转、常驻任务的标准化配置,彻底解决旧版无消息、点击失效、通知丢失等核心兼容问题。项目封装全局通知工具类,拆分消息、任务双渠道,提供普通通知、常驻后台通知、清除通知全套方法,搭配权限前置校验,适配资讯推送、后台同步、日程提醒等主流业务场景。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐