HarmonyOS应用<节气通>开发第40篇:通知推送功能实现——让应用主动触达用户

引言

通知推送(Notification)是现代移动应用与用户保持连接的核心渠道。通过本地通知,应用可以在特定时机向用户推送重要信息,如节气提醒、每日知识、系统公告等,从而提升用户活跃度和留存率。在HarmonyOS应用开发中,@ohos.notificationManager 模块提供了完整的本地通知管理能力,包括权限申请、通知发送、通知取消、分类管理等。

一个完善的通知推送系统应该具备以下特点:

  • 权限合规:正确申请和管理通知权限
  • 类型区分:支持多种通知类型(节气提醒、每日推送、系统公告)
  • 用户可控:提供开关设置,尊重用户选择
  • 内容丰富:支持标题、正文、附加信息等完整结构
  • 状态可查:能够查询和清理通知
  • 错误处理:完善的异常捕获和降级策略

本文为实战总结版本 ,所有代码均经过项目验证编译通过,包含完整的通知服务封装、权限处理、设置页面集成等实战内容。


学习目标

完成本文后,你将能够:

  • ✅ 理解HarmonyOS通知系统的架构与原理
  • ✅ 正确配置通知相关权限
  • ✅ 实现通知服务的单例模式封装
  • ✅ 发送多种类型的本地通知(文本通知)
  • ✅ 实现通知权限的检查与引导
  • ✅ 集成通知设置页面与持久化存储
  • ✅ 实现通知的取消与批量清理
  • ✅ 掌握通知ID生成与分组策略

需求分析

功能模块设计

模块 功能描述 技术要点
通知服务 管理所有通知操作 单例模式、notificationManager API
权限管理 检查和请求通知权限 isNotificationEnabled、系统设置引导
通知发送 发送各类本地通知 publish()、NotificationRequest 构建
节气提醒 节气当天自动推送 定时触发 + 类型判断
每日推送 每日知识定时推送 开关控制 + 内容截断
设置页面 用户自定义通知偏好 Toggle组件 + Preferences存储
通知管理 取消/清除通知 cancel() / cancelAll()

支持的通知类型

通知类型 标识码 触发场景 示例
节气提醒 holiday 当日为二十四节气之一 "今日立夏"
每日知识 daily 每天定时推送 "今日小满:麦粒渐满..."
系统公告 system 应用更新、活动通知 "新版本已发布"

通知设置项

设置项 默认值 说明
pushEnabled true 全局推送总开关
soundEnabled true 通知声音
vibrationEnabled true 震动反馈
dailyReminder true 每日知识推送
holidayNotification true 节气当天提醒

核心实现

步骤1: 配置模块权限

module.json5 中声明通知相关权限:

json5 复制代码
// entry/src/main/module.json5
{
  "module": {
    // ... 其他配置

    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        // 通知控制器权限:用于发布和管理本地通知
        "name": "ohos.permission.NOTIFICATION_CONTROLLER"
      }
    ]
  }
}
权限说明
权限名称 用途 必要性
ohos.permission.INTERNET 网络访问(项目基础依赖) 必需
ohos.permission.NOTIFICATION_CONTROLLER 发布和管理本地通知 本地通知必需

注意 :HarmonyOS 中通知权限属于敏感权限 ,需要在系统设置中由用户手动开启。应用无法直接通过代码强制开启,只能通过 isNotificationEnabled() 检测状态并引导用户前往设置。


步骤2: 设计通知数据模型

typescript 复制代码
// services/NotificationService.ets

/** 通知类型枚举 */
export enum NotificationType {
  /** 节气提醒 - 当天为二十四节气时触发 */
  HOLIDAY = 'holiday',
  /** 每日知识推送 - 每天定时推送节气相关知识 */
  DAILY = 'daily',
  /** 系统公告 - 应用更新、活动通知等 */
  SYSTEM = 'system'
}

/** 通知配置接口 */
export interface NotificationConfig {
  type: NotificationType;           // 通知类型
  title: string;                    // 通知标题
  content: string;                  // 通知正文
  extraInfo?: Record<string, string>; // 附加信息(用于点击跳转等)
}
设计要点

1. 通知类型枚举

使用字符串枚举定义三种通知类型,便于:

  • 在代码中通过类型进行条件分支
  • 作为通知的 groupName 进行分组显示
  • 与设置页面的开关一一对应
typescript 复制代码
export enum NotificationType {
  HOLIDAY = 'holiday',   // 节气提醒
  DAILY = 'daily',       // 每日推送
  SYSTEM = 'system'      // 系统公告
}

2. 通知配置接口

统一的配置接口确保所有通知遵循相同的数据结构:

typescript 复制代码
export interface NotificationConfig {
  type: NotificationType;            // 分类标识
  title: string;                     // 标题(显示在通知栏顶部)
  content: string;                   // 正文(标题下方的内容)
  extraInfo?: Record<string, string>; // 扩展字段,如跳转参数
}

步骤3: 实现通知服务核心类

typescript 复制代码
// services/NotificationService.ets

import notificationManager from '@ohos.notificationManager';
import { common } from '@kit.AbilityKit';
import promptAction from '@ohos.promptAction';
import { StorageService } from './StorageService';

/**
 * 通知推送服务(单例模式)
 *
 * 职责:
 * 1. 管理通知权限的检查与引导
 * 2. 封装 notificationManager 的 publish/cancel 操作
 * 3. 提供语义化的高层API(sendHolidayNotification 等)
 * 4. 结合 StorageService 实现用户设置的读取与过滤
 *
 * 使用方式:
 * ```typescript
 * const service = NotificationService.getInstance();
 * await service.init(context, storageService);
 * await service.sendHolidayNotification('立夏', '2026-05-05');
 * ```
 */
export class NotificationService {
  private static instance: NotificationService;
  private context: common.Context | null = null;
  private storageService: StorageService | null = null;
  private isInitialized: boolean = false;

  private constructor() {}

  /** 获取单例实例 */
  static getInstance(): NotificationService {
    if (!NotificationService.instance) {
      NotificationService.instance = new NotificationService();
    }
    return NotificationService.instance;
  }

  /**
   * 初始化服务
   *
   * 应在 EntryAbility 的 onCreate 或首页 aboutToAppear 中调用。
   * 会自动检测通知权限状态并在未授权时给出提示。
   *
   * @param context 应用上下文(UIAbilityContext)
   * @param storageService 存储服务实例(用于读取通知设置)
   */
  async init(context: common.Context, storageService: StorageService): Promise<void> {
    this.context = context;
    this.storageService = storageService;

    try {
      const enabled = await this.checkNotificationEnabled();
      if (!enabled) {
        // 首次启动或权限被关闭时,引导用户
        await this.requestNotificationPermission();
      }
      this.isInitialized = true;
      console.info('NotificationService 初始化完成');
    } catch (error) {
      console.error('NotificationService 初始化失败:', error);
    }
  }

  /**
   * 检查当前通知是否已启用
   *
   * @returns true=已开启,false=未开启
   */
  async checkNotificationEnabled(): Promise<boolean> {
    try {
      return await notificationManager.isNotificationEnabled();
    } catch (error) {
      console.error('检查通知状态失败:', error);
      return false;
    }
  }

  /**
   * 请求通知权限
   *
   * HarmonyOS 中通知权限需用户在系统设置中手动开启,
   * 此方法会弹出 Toast 提示用户前往设置页面。
   */
  async requestNotificationPermission(): Promise<void> {
    try {
      const isEnabled = await notificationManager.isNotificationEnabled();
      if (isEnabled) {
        return; // 已开启则直接返回
      }
      promptAction.showToast({
        message: '请在系统设置中开启通知权限',
        duration: 3000
      });
    } catch (error) {
      console.error('请求通知权限失败:', error);
    }
  }

  // ========== 核心发送方法 ==========

  /**
   * 发送本地通知(通用方法)
   *
   * 内部流程:
   * 1. 检查服务是否初始化
   * 2. 读取全局推送开关(pushEnabled)
   * 3. 根据通知类型读取对应开关(holidayNotification/dailyReminder)
   * 4. 构建 NotificationRequest 并调用 publish()
   *
   * @param config 通知配置
   * @returns 是否发送成功
   */
  async sendNotification(config: NotificationConfig): Promise<boolean> {
    try {
      if (!this.isInitialized) {
        console.warn('NotificationService 未初始化');
        return false;
      }

      // 检查全局推送开关
      if (this.storageService) {
        const pushEnabled = await this.storageService.getNotificationSetting('pushEnabled', true);
        if (!pushEnabled) {
          console.info('推送通知已关闭,跳过发送');
          return false;
        }
      }

      // 根据类型检查对应开关
      if (config.type === NotificationType.HOLIDAY && this.storageService) {
        const holidayNotif = await this.storageService.getNotificationSetting('holidayNotification', true);
        if (!holidayNotif) return false;
      }
      if (config.type === NotificationType.DAILY && this.storageService) {
        const dailyReminder = await this.storageService.getNotificationSetting('dailyReminder', true);
        if (!dailyReminder) return false;
      }

      // 构建并发送通知
      const request: notificationManager.NotificationRequest = {
        id: this.generateNotificationId(config.type),
        content: this.buildContent(config),
        deliveryTime: Date.now(),
        tapDismissed: true,
        classification: notificationManager.Classification.SOCIAL,
        groupName: config.type
      };

      await notificationManager.publish(request);
      console.info(`通知发送成功: [${config.type}] ${config.title}`);
      return true;
    } catch (error) {
      console.error(`通知发送失败:`, error);
      return false;
    }
  }

  // ========== 便捷发送方法 ==========

  /**
   * 发送节气提醒通知
   *
   * @param solarName 节气名称(如"立夏"、"小满")
   * @param date 日期字符串(如"2026-05-05")
   * @param description 可选的节气描述
   */
  async sendHolidayNotification(
    solarName: string,
    date: string,
    description?: string
  ): Promise<boolean> {
    const config: NotificationConfig = {
      type: NotificationType.HOLIDAY,
      title: `今日${solarName}`,
      content: description || `${date},${solarName}到了!点击查看节气详情。`,
      extraInfo: { solarName, date, type: 'holiday' }
    };
    return this.sendNotification(config);
  }

  /**
   * 发送每日知识推送
   *
   * @param title 知识标题
   * @param content 知识内容(超过50字符自动截断)
   */
  async sendDailyKnowledge(title: string, content: string): Promise<boolean> {
    const config: NotificationConfig = {
      type: NotificationType.DAILY,
      title: title,
      content: content.length > 50 ? content.substring(0, 50) + '...' : content,
      extraInfo: { type: 'daily' }
    };
    return this.sendNotification(config);
  }

  /**
   * 发送系统公告
   *
   * @param title 公告标题
   * @param content 公告内容
   */
  async sendSystemNotice(title: string, content: string): Promise<boolean> {
    const config: NotificationConfig = {
      type: NotificationType.SYSTEM,
      title: title,
      content: content,
      extraInfo: { type: 'system' }
    };
    return this.sendNotification(config);
  }

  // ========== 通知管理方法 ==========

  /**
   * 取消指定ID的通知
   *
   * @param id 通知ID(publish 时生成的 id)
   */
  async cancelNotification(id: number): Promise<void> {
    try {
      await notificationManager.cancel(id);
      console.info(`通知已取消: id=${id}`);
    } catch (error) {
      console.error('取消通知失败:', error);
    }
  }

  /**
   * 清除本应用的所有通知
   */
  async cancelAllNotifications(): Promise<void> {
    try {
      await notificationManager.cancelAll();
      console.info('所有通知已清除');
    } catch (error) {
      console.error('清除所有通知失败:', error);
    }
  }

  /**
   * 获取当前活跃通知数量
   *
   * @returns 未消除的通知数量
   */
  async getActiveNotificationCount(): Promise<number> {
    try {
      const notifications = await notificationManager.getActiveNotifications();
      return notifications.length;
    } catch (error) {
      console.error('获取活跃通知数量失败:', error);
      return 0;
    }
  }

  // ========== 私有辅助方法 ==========

  /**
   * 构建基础文本通知内容
   */
  private buildContent(config: NotificationConfig): notificationManager.NotificationContent {
    return {
      contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
      normal: {
        title: config.title,
        text: config.content,
        additionalText: '节气通'
      }
    };
  }

  /**
   * 生成唯一通知ID
   *
   * 策略:类型前缀 + 时间戳后4位
   * - holiday: 1000~1999
   * - daily:  2000~2999
   * - system: 3000~3999
   */
  private generateNotificationId(type: NotificationType): number {
    const typeHash: Record<NotificationType, number> = {
      [NotificationType.HOLIDAY]: 1000,
      [NotificationType.DAILY]: 2000,
      [NotificationType.SYSTEM]: 3000
    };
    const baseId = typeHash[type] || 1000;
    return baseId + (Date.now() % 10000);
  }

  /** 销毁服务 */
  destroy(): void {
    this.context = null;
    this.storageService = null;
    this.isInitialized = false;
  }
}
核心技术点解析

1. 单例模式

全局唯一的 NotificationService 实例,避免重复初始化:

typescript 复制代码
private static instance: NotificationService;
private constructor() {}

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

2. 权限检查流程

复制代码
应用启动 → init()
         → checkNotificationEnabled()
         → 已开启? ──是──→ 完成
              │
             否
              ↓
         requestNotificationPermission()
         → 弹出 Toast 引导用户去设置

3. 通知发送前的双重过滤

每次发送通知前,依次检查两级开关:

typescript 复制代码
// 第一级:全局推送开关
const pushEnabled = await storageService.getNotificationSetting('pushEnabled', true);

// 第二级:类型专属开关(仅对 holiday 和 daily 生效)
if (type === HOLIDAY) {
  const holidayOn = await storageService.getNotificationSetting('holidayNotification', true);
}

这种设计让用户既能一键关闭所有通知,也能精细化控制每种通知类型。

4. NotificationRequest 关键字段

字段 说明 本文取值
id 通知唯一标识 基于类型+时间戳生成
contentType 内容类型 NOTIFICATION_CONTENT_BASIC_TEXT
deliveryTime 投递时间 Date.now()(立即投递)
tapDismissed 点击后是否消失 true
classification 通知分类 SOCIAL(社交类)
groupName 分组名称 通知类型字符串

5. ID生成策略

使用类型前缀 + 时间戳后4位,保证:

  • 不同类型的通知不会ID冲突
  • 同一类型短时间内多次推送也有不同ID
  • ID范围固定,便于管理和调试

步骤4: 存储层扩展

StorageService 中添加通知设置相关的存取方法:

typescript 复制代码
// services/StorageService.ets(已有代码)

/** 通知设置存储键前缀 */
const NOTIFICATION_KEY = 'notification_settings';

/**
 * 获取单个通知设置项
 *
 * @param key 设置键名(如 'pushEnabled'、'dailyReminder')
 * @param defaultValue 未找到时的默认值
 * @returns 设置值
 */
async getNotificationSetting(key: string, defaultValue: boolean): Promise<boolean> {
  try {
    const prefs = await this.getPreferences();
    const fullKey = `${NOTIFICATION_KEY}_${key}`;
    return prefs.get(fullKey, defaultValue) as boolean;
  } catch (error) {
    console.error(`获取通知设置 ${key} 失败:`, error);
    return defaultValue;
  }
}

/**
 * 保存单个通知设置项
 *
 * @param key 设置键名
 * @param value 设置值
 */
async setNotificationSetting(key: string, value: boolean): Promise<void> {
  try {
    const prefs = await this.getPreferences();
    const fullKey = `${NOTIFICATION_KEY}_${key}`;
    await prefs.put(fullKey, value);
    await prefs.flush();
  } catch (error) {
    console.error(`保存通知设置 ${key} 失败:`, error);
  }
}

/**
 * 获取所有通知设置
 *
 * @returns 所有设置项的键值对
 */
async getAllNotificationSettings(): Promise<Record<string, boolean>> {
  const keys = ['pushEnabled', 'soundEnabled', 'vibrationEnabled',
                'dailyReminder', 'holidayNotification'];
  const result: Record<string, boolean> = {};
  for (const key of keys) {
    result[key] = await this.getNotificationSetting(key, true);
  }
  return result;
}
存储键命名规范
完整键名 对应设置 默认值
notification_settings_pushEnabled 全局推送开关 true
notification_settings_soundEnabled 通知声音 true
notification_settings_vibrationEnabled 震动反馈 true
notification_settings_dailyReminder 每日提醒 true
notification_settings_holidayNotification 节气提醒 true

使用 notification_settings_ 前缀统一命名空间,避免与其他功能的数据冲突。


步骤5: 通知设置页面

typescript 复制代码
// pages/NotificationSettings.ets

import router from '@ohos.router';
import { common } from '@kit.AbilityKit';
import promptAction from '@ohos.promptAction';
import { StorageService } from '../services/StorageService';
import { I18nService } from '../services/I18nService';

@Entry
@Component
export struct NotificationSettings {
  // i18n 动态文本变量
  @State pageTitle: string = '通知设置';
  @State pushGroupTitle: string = '推送通知';
  @State pushTitle: string = '允许推送';
  @State pushDesc: string = '接收应用推送消息';
  @State soundTitle: string = '通知声音';
  @State soundDesc: string = '收到通知时播放声音';
  @State vibrationTitle: string = '震动反馈';
  @State vibrationDesc: string = '收到通知时震动提醒';
  @State reminderGroupTitle: string = '提醒设置';
  @State dailyReminderTitle: string = '每日提醒';
  @State dailyReminderDesc: string = '每天定时推送节气知识';
  @State holidayNotifTitle: string = '节气提醒';
  @State holidayNotifDesc: string = '节气当天发送祝福消息';

  // 开关状态
  @State pushEnabled: boolean = true;
  @State soundEnabled: boolean = true;
  @State vibrationEnabled: boolean = true;
  @State dailyReminder: boolean = true;
  @State holidayNotification: boolean = true;

  private storageService: StorageService | null = null;
  private i18nService: I18nService | null = null;

  async aboutToAppear() {
    const context = getContext(this) as common.UIAbilityContext;

    // 初始化国际化服务
    this.i18nService = I18nService.getInstance();
    await this.i18nService.init(context, undefined);
    this.loadStrings();
    this.i18nService.addLanguageChangeListener(() => {
      this.loadStrings();
    });

    // 初始化存储服务并加载设置
    this.storageService = new StorageService(context);
    await this.storageService.init();
    await this.loadSettings();
  }

  /** 从存储中加载所有设置项 */
  async loadSettings() {
    if (!this.storageService) return;
    try {
      this.pushEnabled = await this.storageService.getNotificationSetting('pushEnabled', true);
      this.soundEnabled = await this.storageService.getNotificationSetting('soundEnabled', true);
      this.vibrationEnabled = await this.storageService.getNotificationSetting('vibrationEnabled', true);
      this.dailyReminder = await this.storageService.getNotificationSetting('dailyReminder', true);
      this.holidayNotification = await this.storageService.getNotificationSetting('holidayNotification', true);
    } catch (error) {
      console.error('加载通知设置失败:', error);
    }
  }

  /** 保存单个设置项到存储 */
  async saveSetting(key: string, value: boolean) {
    if (!this.storageService) return;
    try {
      await this.storageService.setNotificationSetting(key, value);
    } catch (error) {
      console.error('保存通知设置失败:', error);
    }
  }

  /** 加载国际化文本 */
  loadStrings(): void {
    const service = this.i18nService;
    if (!service) return;
    this.pageTitle = service.getString('notification_settings');
    this.pushGroupTitle = service.getString('push_notifications');
    this.pushTitle = service.getString('allow_push');
    this.pushDesc = service.getString('allow_push_desc');
    this.soundTitle = service.getString('notification_sound');
    this.soundDesc = service.getString('notification_sound_desc');
    this.vibrationTitle = service.getString('vibration_feedback');
    this.vibrationDesc = service.getString('vibration_feedback_desc');
    this.reminderGroupTitle = service.getString('reminder_settings');
    this.dailyReminderTitle = service.getString('daily_reminder');
    this.dailyReminderDesc = service.getString('daily_reminder_desc');
    this.holidayNotifTitle = service.getString('holiday_notification');
    this.holidayNotifDesc = service.getString('holiday_notification_desc');
  }

  build() {
    Column() {
      // 导航栏
      Row() {
        Image($r('app.media.icon_back'))
          .width(24)
          .height(24)
          .fillColor('#333333')
          .onClick(() => router.back())

        Text(this.pageTitle)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
          .layoutWeight(1)
          .textAlign(TextAlign.Center)

        Blank().width(24)
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })
      .backgroundColor('#FFFFFF')

      // 设置列表
      Scroll() {
        Column({ space: 16 }) {
          // 推送通知组
          this.buildSwitchGroup(this.pushGroupTitle, [
            { title: this.pushTitle, desc: this.pushDesc, key: 'pushEnabled', value: this.pushEnabled },
            { title: this.soundTitle, desc: this.soundDesc, key: 'soundEnabled', value: this.soundEnabled },
            { title: this.vibrationTitle, desc: this.vibrationDesc, key: 'vibrationEnabled', value: this.vibrationEnabled }
          ])

          // 提醒设置组
          this.buildSwitchGroup(this.reminderGroupTitle, [
            { title: this.dailyReminderTitle, desc: this.dailyReminderDesc, key: 'dailyReminder', value: this.dailyReminder },
            { title: this.holidayNotifTitle, desc: this.holidayNotifDesc, key: 'holidayNotification', value: this.holidayNotification }
          ])

          // 底部说明
          Column({ space: 8 }) {
            Text('通知说明')
              .fontSize(14)
              .fontWeight(FontWeight.Medium)
              .fontColor('#333333')
            Text('开启通知后,您将及时收到节气相关的提醒和推送消息。您可以根据喜好自定义通知类型。')
              .fontSize(13)
              .fontColor('#999999')
              .lineHeight(20)
          }
          .padding(16)
          .backgroundColor('#F8F7F2')
          .borderRadius(12)
          .width('100%')
        }
        .padding({ left: 16, right: 16, top: 16, bottom: 40 })
      }
      .width('100%')
      .height('100%')
      .scrollBar(BarState.Off)
      .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  @Builder
  buildSwitchGroup(title: string, items: { title: string; desc: string; key: string; value: boolean }[]) {
    Column({ space: 0 }) {
      Text(title)
        .fontSize(14)
        .fontColor('#999999')
        .width('100%')
        .padding({ left: 4, bottom: 8 })

      Column({ space: 0 }) {
        ForEach(items, (item, index) => {
          this.buildSwitchItem(item.title, item.desc, item.key, item.value, index, items.length)
        })
      }
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
    }
  }

  @Builder
  buildSwitchItem(title: string, desc: string, key: string, isOn: boolean, index: number, total: number) {
    Column() {
      Row() {
        Column({ space: 4 }) {
          Text(title).fontSize(15).fontColor('#333333')
          Text(desc).fontSize(12).fontColor('#999999')
        }
        .layoutWeight(1)
        .alignItems(HorizontalAlign.Start)

        Toggle({ type: ToggleType.Switch, isOn: isOn })
          .selectedColor('#4A9B6D')
          .onChange((value: boolean) => {
            this.saveSetting(key, value);
            switch (key) {
              case 'pushEnabled': this.pushEnabled = value; break;
              case 'soundEnabled': this.soundEnabled = value; break;
              case 'vibrationEnabled': this.vibrationEnabled = value; break;
              case 'dailyReminder': this.dailyReminder = value; break;
              case 'holidayNotification': this.holidayNotification = value; break;
            }
            promptAction.showToast({ message: value ? '已开启' : '已关闭' });
          })
      }
      .width('100%')
      .height(64)
      .padding({ left: 16, right: 16 })

      if (index < total - 1) {
        Divider().height(1).color('#F0F0F0').margin({ left: 16 })
      }
    }
  }
}
页面架构说明
复制代码
NotificationSettings 页面
├── 导航栏(返回按钮 + 标题)
└── Scroll 滚动区域
    ├── 推送通知组(buildSwitchGroup)
    │   ├── 允许推送(Toggle Switch)
    │   ├── 通知声音(Toggle Switch)
    │   └── 震动反馈(Toggle Switch)
    ├── 提醒设置组(buildSwitchGroup)
    │   ├── 每日提醒(Toggle Switch)
    │   └── 节气提醒(Toggle Switch)
    └── 底部说明卡片

步骤6: 在EntryAbility中初始化

typescript 复制代码
// entryability/EntryAbility.ets

import { NotificationService } from '../services/NotificationService';

export default class EntryAbility extends UIAbility {
  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');

    // 初始化各服务...
    const storageService = new StorageService(this.context);

    // 初始化通知服务
    const notifService = NotificationService.getInstance();
    await notifService.init(this.context, storageService);

    // 将服务实例存入 AppStorage,供全局使用
    AppStorage.setOrCreate('notificationService', notifService);
  }
}

步骤7: 业务场景调用示例

场景一:节气当天自动推送
typescript 复制代码
// 在首页或节气检测逻辑中调用

import { NotificationService } from '../services/NotificationService';

/** 检测今天是否为节气,若是则发送通知 */
async function checkAndNotifyTodaySolarTerm() {
  const todaySolarTerm = getTodaySolarTerm(); // 自定义函数:获取今天的节气
  if (todaySolarTerm) {
    const service = NotificationService.getInstance();
    const success = await service.sendHolidayNotification(
      todaySolarTerm.name,
      formatDate(new Date()),
      `${todaySolarTerm.description}`
    );
    if (success) {
      console.info(`节气通知已发送: ${todaySolarTerm.name}`);
    }
  }
}
场景二:每日定时知识推送
typescript 复制代码
// 使用后台任务或 Timer 触发

import { NotificationService } from '../services/NotificationService';

/** 每日知识推送任务 */
async function dailyKnowledgeTask() {
  const service = NotificationService.getInstance();
  const knowledge = getRandomDailyKnowledge(); // 获取随机知识条目
  await service.sendDailyKnowledge(knowledge.title, knowledge.content);
}
场景三:应用更新通知
typescript 复制代码
// 在 AppUpdateService 检测到新版本时

async function notifyAppUpdate(version: string) {
  const service = NotificationService.getInstance();
  await service.sendSystemNotice(
    '发现新版本',
    `节气通 v${version} 已发布,点击前往更新。`
  );
}

架构总览

复制代码
┌─────────────────────────────────────────────────────┐
│                    用户界面层                         │
│  ┌──────────────────┐  ┌─────────────────────────┐  │
│  │ NotificationSet- │  │  首页 / 详情页 / 设置页  │  │
│  │ tings.ets        │  │  (业务场景调用)          │  │
│  └────────┬─────────┘  └────────────┬────────────┘  │
├───────────┼────────────────────────┼───────────────┤
│           ▼                        ▼                │
│  ┌─────────────────────────────────────────────┐   │
│  │              服务层                           │   │
│  │  ┌─────────────────┐  ┌──────────────────┐  │   │
│  │  │ NotificationS-  │  │  StorageService  │  │   │
│  │  │ ervice (单例)   │◄─┤  (设置持久化)     │  │   │
│  │  └────────┬────────┘  └──────────────────┘  │   │
│  │           │                                   │   │
│  │           ▼                                   │   │
│  │  ┌──────────────────────────────────────┐   │   │
│  │  │  @ohos.notificationManager (系统API)  │   │   │
│  │  └──────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────┘   │
├───────────────────────────────────────────────────┤
│                   系统层                            │
│  ┌────────────────┐  ┌──────────────────────────┐  │
│  │ module.json5   │  │  系统 Settings (通知权限) │  │
│  │ (权限声明)     │  │  通知中心 UI             │  │
│  └────────────────┘  └──────────────────────────┘  │
└─────────────────────────────────────────────────────┘

数据流

复制代码
用户切换开关 → saveSetting() → Preferences 存储
                                                    │
业务触发通知 ← sendNotification() ← NotificationService
                    │
                    ├─ 读取 pushEnabled (全局开关)
                    ├─ 读取 holidayNotification (类型开关)
                    ├─ 构建 NotificationRequest
                    └─ notificationManager.publish()
                         │
                         ▼
                    系统通知中心展示

关键注意事项

1. 权限声明不可遗漏

module.json5 中必须声明 ohos.permission.NOTIFICATION_CONTROLLER,否则 publish() 调用会抛出权限异常。

2. 单例初始化时机

NotificationService.getInstance().init() 必须在使用任何发送方法之前调用。推荐在 EntryAbility.onCreate() 中统一初始化。

3. 设置变更实时生效

NotificationSettings 页面中的 Toggle 变更后立即写入 Preferences,下次发送通知时会重新读取最新值,无需重启应用。

4. 通知ID唯一性

同一ID的通知会被覆盖而非新增。本文采用「类型前缀 + 时间戳」策略确保每次发送都是新通知。

5. 异步方法全部使用 await

notificationManager.publish()isNotificationEnabled()、Preferences 操作均为异步方法,必须使用 await 确保顺序执行。


最佳实践清单

在实现通知推送功能时,请逐项检查:

  • module.json5 中已声明 ohos.permission.NOTIFICATION_CONTROLLER
  • NotificationService 采用单例模式,全局唯一实例
  • init() 方法在 EntryAbility.onCreate() 中调用
  • 发送通知前检查 isInitialized 状态
  • 发送通知前读取用户设置(全局开关 + 类型开关)
  • 通知ID基于类型+时间戳生成,避免冲突
  • 所有异步操作均有 try-catch 错误处理
  • 设置页面使用 Toggle 组件,变更即时保存
  • 设置页面支持 i18n 动态文本(loadStrings() + 监听器)
  • extraInfo 字段携带足够信息用于后续点击跳转
  • 通知内容长度合理(正文建议不超过50字符)

相关链接