一、背景说明

HarmonyOS 7 近期持续推送补丁包,跨设备「通知流转」正在成为生态标配能力(如与 Apple Watch 的通知流转互联),而通知能力的第一步始终是应用内的本地通知发布。本文不展开跨设备流转,聚焦一个具体技术点:使用 Notification Kit(@kit.NotificationKit)实现鸿蒙应用本地通知的完整生命周期——授权申请、文本通知发布、带 WantAgent 的点击跳转、进度条通知的持续更新与取消。

二、技术原理

Notification Kit 提供应用本地通知的发布通道,核心概念如下:

  • 授权流程:应用通知能力默认关闭,先 isNotificationEnabled() 查询,未授权时 requestEnableNotification() 拉起授权框。
  • NotificationRequest:通知请求对象,id 标识一条通知;相同 id 再次 publish 会更新原通知,不同 id 则新建。
  • Slot(通知渠道):通过 addSlot() 声明渠道类型(社交通讯、服务提醒、内容资讯等),影响铃声/横幅等展示形态。
  • WantAgent:由 Ability Kit 创建的行为意图,封装进通知后,用户点击通知即可拉起应用指定页面。
  • 进度条模板:系统唯一模板 downloadTemplate,通过 template.data.progressValue 携带进度,配合 updateOnly: true 只更新不新建。

数据流:授权查询/申请 → 构造 NotificationRequest(可带 WantAgent / template)→ notificationManager.publish() → 通知中心展示 → publish 同 id 更新 / cancel 取消。

三、实现步骤

工程为 Stage 模型(API 12+)。通知能力无需在 module.json5 声明用户权限(requestEnableNotification 走系统授权框),保持默认配置即可。

1. 通知管理类 NotifyManager.ets
import { notificationManager } from '@kit.NotificationKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
import { Context } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class NotifyManager {
  // 第一步:确保通知权限已开启
  static async ensurePermission(context: Context): Promise<boolean> {
    const enabled = await notificationManager.isNotificationEnabled();
    if (enabled) {
      return true;
    }
    // 拉起系统授权框,由用户决定是否开启
    return await notificationManager.requestEnableNotification(context)
      .then(() => true)
      .catch((err: BusinessError) => {
        console.error(`auth denied, code=${err.code}, msg=${err.message}`);
        return false;
      });
  }

  // 第二步:发布普通文本通知
  static async publishText(id: number, title: string, text: string): Promise<void> {
    const request: notificationManager.NotificationRequest = {
      id: id,
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: title,
          text: text,
          additionalText: '鸿蒙通知实战'
        }
      }
    };
    await notificationManager.publish(request);
  }

  // 第三步:发布带点击跳转(WantAgent)与操作按钮的通知
  static async publishWithAction(id: number, title: string, text: string,
    abilityName: string): Promise<void> {
    const wantAgentInfo: wantAgent.WantAgentInfo = {
      wants: [
        {
          bundleName: 'com.example.notifydemo',
          abilityName: abilityName, // 点击通知拉起的目标 UIAbility
          entities: ['entity.system.home'],
          parameters: { 'from': 'notification' }
        }
      ],
      requestCode: 0,
      actionType: wantAgent.OperationType.START_ABILITY,
      actionFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG]
    };
    const agent: WantAgent = await wantAgent.getWantAgent(wantAgentInfo);

    const request: notificationManager.NotificationRequest = {
      id: id,
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: { title: title, text: text }
      },
      wantAgent: agent, // 点击通知本体触发
      actionButtons: [
        { title: '查看详情', wantAgent: agent } // 通知按钮
      ]
    };
    await notificationManager.publish(request);
  }

  // 第四步:发布进度条通知(下载场景)
  static async publishProgress(id: number, fileName: string, progressValue: number): Promise<void> {
    const request: notificationManager.NotificationRequest = {
      id: id,
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: '正在下载',
          text: fileName
        }
      },
      template: {
        name: 'downloadTemplate', // 模板名固定为 downloadTemplate
        data: {
          title: '文件下载',
          fileName: fileName,
          progressValue: progressValue // 0~100
        }
      }
    };
    await notificationManager.publish(request);
  }

  // 第五步:仅更新已有进度条通知(updateOnly 不存在同 id 时会失败)
  static async updateProgress(id: number, fileName: string, progressValue: number): Promise<void> {
    const request: notificationManager.NotificationRequest = {
      id: id,
      updateOnly: true, // API 18+:只更新已存在的通知
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: { title: '正在下载', text: fileName }
      },
      template: {
        name: 'downloadTemplate',
        data: { title: '文件下载', fileName: fileName, progressValue: progressValue }
      }
    };
    await notificationManager.publish(request);
  }

  // 取消指定通知
  static async cancel(id: number): Promise<void> {
    await notificationManager.cancel(id);
  }
}
2. UI 调用示例 Index.ets
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { NotifyManager } from '../model/NotifyManager';

@Entry
@Component
struct Index {
  private context = getContext(this) as common.UIAbilityContext;
  @State progress: number = 0;
  private timerId: number = -1;

  // 模拟下载:进度条通知从 0 更新到 100
  startDownload(): void {
    this.progress = 0;
    NotifyManager.publishProgress(1001, 'music.mp4', 0)
      .catch((err: BusinessError) => console.error(`publish failed: ${err.code}`));
    this.timerId = setInterval(() => {
      this.progress += 10;
      if (this.progress >= 100) {
        clearInterval(this.timerId);
        // 下载完成:更新为 100 后延时取消通知
        NotifyManager.updateProgress(1001, 'music.mp4', 100)
          .then(() => {
            setTimeout(() => { NotifyManager.cancel(1001); }, 2000);
          });
        return;
      }
      NotifyManager.updateProgress(1001, 'music.mp4', this.progress)
        .catch((err: BusinessError) => {
          clearInterval(this.timerId);
          console.error(`update failed, code=${err.code}`);
        });
    }, 1000);
  }

  build() {
    Column({ space: 16 }) {
      Button('发布文本通知')
        .onClick(() => {
          NotifyManager.ensurePermission(this.context)
            .then((ok: boolean) => {
              if (ok) {
                NotifyManager.publishText(1000, '新消息提醒', '您有一条新的待办事项');
              }
            });
        })

      Button('发布可点击跳转的通知')
        .onClick(() => {
          NotifyManager.ensurePermission(this.context)
            .then((ok: boolean) => {
              if (ok) {
                NotifyManager.publishWithAction(1002, '作业提醒',
                  '点击查看今日任务详情', 'EntryAbility');
              }
            });
        })

      Button('开始模拟下载(进度条通知)')
        .onClick(() => {
          NotifyManager.ensurePermission(this.context)
            .then((ok: boolean) => {
              if (ok) { this.startDownload(); }
            });
        })

      Button('取消全部通知')
        .onClick(() => {
          notificationManager.cancelAll();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
四、应用场景示例

下载管理器中监听真实下载进度并同步到通知栏:

import { request } from '@kit.BasicServicesKit';

// 下载任务回调中同步通知进度
downloadTask.on('progress', (received: number, total: number) => {
  const percent = Math.floor(received * 100 / total);
  NotifyManager.updateProgress(1001, 'update.zip', percent)
    .catch(() => { /* 通知已被用户移除时静默处理 */ });
});
五、技术要点
1. 频次限制
  • 单应用发布新通知累计不超过 10 条/秒,更新通知不超过 20 条/秒,超限会返回错误码——进度更新节流到 500ms 以上一次即可安全覆盖;
  • 单应用在通知中心留存上限 24 条,通知体序列化后不能超过 200KB。
2. 授权被拒后的降级

requestEnableNotification 被用户拒绝后再次调用会返回 1600004(用户拒绝),此时不应反复拉起授权框,应引导用户到设置页手动开启。

3. 错误处理
try {
  await NotifyManager.publishProgress(1001, 'a.mp4', 50);
} catch (err) {
  const e = err as BusinessError;
  if (e.code === 1600004) {
    console.error('通知授权被拒绝');
  } else if (e.code === 1600018) {
    console.error('进度更新过于频繁,需节流重试');
  } else {
    console.error(`notify error code=${e.code}, msg=${e.message}`);
  }
}
4. 模板使用前提

进度条模板需先 isSupportTemplate('downloadTemplate') 确认设备支持(当前系统模板仅此一种),不支持时降级为普通文本通知并在 additionalText 中携带百分比。

六、扩展建议
  1. 多行文本通知:notificationContentType 使用 NOTIFICATION_CONTENT_MULTILINE,最多展示三行内容;
  2. 后台定时通知:进程退后台后需要定时提醒(抢购、日程),接入 Background Tasks Kit 的提醒类任务而非常驻进程轮询;
  3. 通知分组:同组通知用 groupName 聚合,并用 cancelGroup 一键撤销:
const request: notificationManager.NotificationRequest = {
  id: 2001,
  groupName: 'download_group', // 同组通知在通知中心折叠展示
  content: { /* ... */ }
};
结语

本文用 Notification Kit 完成了鸿蒙应用本地通知从授权、发布、WantAgent 跳转到进度条模板更新的完整实现,核心代码约 180 行,均可直接编译运行。掌握这条链路后,下载进度、消息提醒、任务催办等场景都能以系统级通知的形态触达用户,而不需要引入任何推送服务。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐