给鸿蒙 App 增加本地通知的能力 —— local_notifier 的鸿蒙使用指南

本文配套仓库:https://atomgit.com/oh-flutter/local_notifier(TAG:0.1.6-ohos-1.0.0-beta.1,分支:feat/ohos_local_notifier_0.1.6),文中示例代码位于仓库 example/ 目录。

订单状态更新、消息提醒、下载完成、定时提醒……应用经常需要在用户不在前台时触达用户,这些都依赖"本地通知"能力。鸿蒙 Flutter 应用此前没有现成的跨平台本地通知插件可用:local_notifier(leanflutter,MIT 协议,0.1.6)原本只支持 Windows、macOS、Linux 桌面三端。鸿蒙化适配完成后,同一套 Dart 代码可以直接在鸿蒙真机上发出通知、响应点击与关闭回调。本文介绍它的接入方法,并附 HUAWEI nova 12 Ultra 真机的完整实测记录。

一、最终运行效果

接入后,通知由系统通知中心统一展示,点击与关闭事件回传到 Dart 侧:

验证点结果
调用 show 后通知出现在系统通知中心通过
通知发布成功触发 onShow 回调通过
点击通知主体,应用回前台并触发 onClick 回调通过
调用 close 后通知移除,触发 onClose 回调(userCanceled)通过

在这里插入图片描述

图一:show 后通知中心顶部出现"example / hello flutter!"通知

在这里插入图片描述

图二:点击通知主体,应用回到前台,页面弹出 SnackBar 提示 onClick 触发

在这里插入图片描述

图三:close 后 Event log 记录 onClose,关闭原因为 userCanceled

检查要点

  1. 通知能力走运行时授权,应用无需在 module.json5 中声明任何权限,首次 setup 由系统弹窗向用户申请;
  2. 点击回传通过 wantAgent 完成,不需要 EventChannel 之类的辅助通道;
  3. 完整实测过程见"六、运行与验证"。

二、local_notifier 是什么

local_notifier(pub.dev 0.1.6,作者 leanflutter,MIT 协议)是一个显示本地通知的 Flutter 插件:构造 LocalNotification 后调用 show() 即可发布,支持标题、副标题、正文与最多两个操作按钮,提供 onShow / onClick / onClickAction / onClose 四个回调。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持,Dart API 与上游完全一致。

几个对使用者友好的特点:

  1. 零权限声明:不需要在 module.json5 里声明通知权限,首次 setup 时由系统弹窗完成运行时授权;
  2. 点击回传无需 EventChannel:发布时为通知挂载 wantAgent,点击后经 onNewWant 回传到 Dart 回调;
  3. 接口与上游一致:setup、LocalNotification、show / close / destroy 的用法与桌面端完全相同,跨平台一套代码。

另外两个行为差异值得知道:正文超过 46 字符时自动改用 LONG_TEXT 样式,长文本在通知中心不被截断;close / destroy 主动关闭成功后触发 onClose(原因为 userCanceled),比上游 macOS、Linux 端(不触发该回调)提供更明确的生命周期信号。

接口说明:

名称描述类型参数类型返回值必填鸿蒙平台支持
localNotifier.setup初始化插件并确保通知授权methodappName: String, shortcutPolicy: ShortcutPolicyFuture<void>
localNotifier.notify / notification.show发出通知methodnotification: LocalNotificationFuture<void>
localNotifier.close / notification.close关闭通知methodnotification: LocalNotificationFuture<void>
localNotifier.destroy / notification.destroy关闭并销毁通知methodnotification: LocalNotificationFuture<void>
LocalNotification.onShow通知显示回调propertyvoid Function()-
LocalNotification.onClick通知点击回调propertyvoid Function()-
LocalNotification.onClickAction操作按钮点击回调propertyvoid Function(int)-
LocalNotification.onClose通知关闭回调propertyvoid Function(LocalNotificationCloseReason)-是(主动关闭时)

其中 LocalNotification 模型支持字段:identifier(唯一标识,未传时自动生成 UUID v4)、title(标题)、subtitle(副标题)、body(正文)、silent(仅桌面端生效,鸿蒙侧忽略)、actions(操作按钮列表,鸿蒙侧最多 2 个)。

三、环境准备

本文所有实测均在以下环境完成:

版本说明
Flutter(ohos 版)3.41.10-ohos-1.0.1主验证环境,真机实测
编译 SDK5.1.0(18)example 宿主工程 compatibleSdkVersion 同值
真机HUAWEI nova 12 Ultra(ADA-AL10)HarmonyOS 6.1.0.135 / API 24,分辨率 1224 × 2776

环境搭建步骤参考官方指南:Flutter OHOS 开发环境搭建指南。两点提醒:

  1. 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
  2. 真机安装需要签名的 hap:在 DevEco Studio 中为宿主工程勾选 Automatically generate signature 后,flutter build hap 会直接产出签好名的包。通知授权弹窗只在未授权时出现,若此前拒绝过,需到"设置 → 通知管理"打开本应用通知后重新 setup(见 FAQ Q2)。

四、引入依赖

进入工程目录,在 pubspec.yaml 中添加 git 依赖:

dependencies:
  local_notifier:
    git:
      url: https://atomgit.com/oh-flutter/local_notifier.git
      # ref: 根据下方表格选择不同框架适配的 TAG 版本
      ref: 0.1.6-ohos-1.0.0-beta.1

执行命令拉取依赖:

flutter pub get

TAG 命名规则:原库版本-ohos-版本号-beta.x

Flutter 框架版本TAG 名称分支名
3.410.1.6-ohos-1.0.0-beta.1feat/ohos_local_notifier_0.1.6

说明:该 TAG 已在 3.41 系 stable(3.41.10-ohos-1.0.1)真机上实测通过,3.41 用户可直接使用;其他框架版本的适配 TAG 发布后在此表补充。

五、代码接入

5.1 初始化

导入库,并在 main 中完成初始化:

import 'package:local_notifier/local_notifier.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await localNotifier.setup(
    appName: 'local_notifier_example',
    // The parameter shortcutPolicy only works on Windows
    shortcutPolicy: ShortcutPolicy.requireCreate,
  );

  runApp(const MyApp());
}

鸿蒙上 setup 做两件事:查询本应用的通知开关(isNotificationEnabled);开关未开启时弹出系统授权弹窗(requestEnableNotification)由用户决定。返回值表示通知开关是否可用:

  • 返回 true:可以正常发通知;
  • 返回 false:用户拒绝了授权(或查询异常),后续调用 show 会抛出未初始化异常,恢复方式见 FAQ Q2。

shortcutPolicy 是 Windows 专属概念,鸿蒙侧忽略,可按 example 的写法原样携带。

5.2 发出一条通知

构造 LocalNotification,挂回调,调用 show:

final notification = LocalNotification(
  title: '订单提醒',
  subtitle: '本地通知演示',
  body: '您购买的商品已由顺丰揽收,点击查看物流详情',
);

notification.onShow = () {
  // 通知发布成功
  print('onShow ${notification.identifier}');
};
notification.onClick = () {
  // 用户点击了通知主体,可跳转对应业务页
  print('onClick ${notification.identifier}');
};

await notification.show();

三个要点:

  1. identifier 不传时自动生成 UUID v4,是后续 close / destroy 与回调分发的唯一凭据,需要关闭某条通知时先把它保存下来;
  2. onShow 在通知发布成功后触发;onClick 在用户点击通知主体时触发,应用在后台时点击会把应用拉回前台;
  3. subtitle 映射为通知卡片上的附加文本,仅在非空时展示。

5.3 操作按钮与 onClickAction

LocalNotification.actions 可以为通知添加操作按钮:

final notification = LocalNotification(
  title: '会议邀请',
  body: '15:00 架构评审会议,是否参加?',
  actions: [
    LocalNotificationAction(text: '接受'),
    LocalNotificationAction(text: '拒绝'),
  ],
);

notification.onClickAction = (actionIndex) {
  // actionIndex 为按钮下标:0 接受、1 拒绝
  print('onClickAction $actionIndex');
};

await notification.show();

鸿蒙通知最多展示 2 个操作按钮,超出部分会被截断,与上游桌面端行为一致。按钮点击与通知主体点击共用同一条回传链路,仅多携带一个 actionIndex(见"七、工作原理")。

5.4 关闭与销毁

await notification.close();    // 关闭通知,实例仍保留
await notification.destroy();  // 关闭并销毁:解除回调监听、从内部缓存移除

两者都会把通知从通知中心移除,成功取消后触发 onClose 回调,关闭原因固定为 LocalNotificationCloseReason.userCanceled。区别在实例的后续可用性:close 之后同一实例可以再次 show;destroy 之后实例不可再复用。example 演示页对固定通知提供的就是这两个按钮,destroy 后演示页上的卡片随之消失。

注意:用户在通知中心手动左滑删除通知不会触发 onClose,原因见 FAQ Q1。

5.5 跨平台一套代码

同一套 Dart 代码在各端的行为:

平台是否需要 setuponClose 触发
Windows需要触发
macOS / Linux无需(自动初始化)不触发(上游行为)
OpenHarmony / HarmonyOS需要主动关闭时触发(userCanceled)

鸿蒙端的授权检查、点击回传与 onClose 信号均经真机验证,行为与上游 Windows 端对齐,比上游 macOS、Linux 端多出明确的生命周期信号。

5.6 实战:订单发货通知

实际业务中,通知发出前要处理"用户没开通知"的情况。下面是订单详情页的发货提醒片段:

Future<void> notifyOrderShipped(String orderId) async {
  final notification = LocalNotification(
    title: '订单已发货',
    body: '订单 $orderId 的包裹已由顺丰揽收,点击查看物流详情',
    actions: [
      LocalNotificationAction(text: '查看物流'),
      LocalNotificationAction(text: '知道了'),
    ],
  );

  notification.onClick = () {
    // 跳转物流详情页
  };
  notification.onClickAction = (actionIndex) {
    if (actionIndex == 0) {
      // 跳转物流详情页
    }
  };

  try {
    await notification.show();
  } catch (e) {
    // setup 返回 false 后调用 show 会走到这里,
    // 可在此引导用户到系统设置打开通知开关
  }
}

catch 分支对应"用户关闭了通知开关"的情况,业务可弹窗引导用户前往设置开启通知,再重新调用 setup。

六、运行与验证

以下为 example 的真机实测记录。仓库中 example 的 Bundle Name 为 com.example.local_notifier_example;本轮测试时签名配置临时使用了包名 com.example.demo,仅影响安装包标识、不影响功能验证,仓库已还原为 com.example.local_notifier_example

设备项
机型HUAWEI nova 12 Ultra(ADA-AL10)
系统版本HarmonyOS 6.1.0.135(SP8C00E120R2P6)
API 版本24
分辨率1224 × 2776

6.1 安装与启动

构建并安装 example:

cd example
flutter pub get
flutter build hap --release
hdc install build/app/outputs/default/entry-default-signed.hap
hdc shell aa start -b com.example.local_notifier_example -a EntryAbility

安装后应用出现在桌面:

在这里插入图片描述

图四:example 安装后出现在 HUAWEI nova 12 Ultra 桌面

启动应用完成 setup:首次运行会先弹系统授权弹窗,截图设备此前已授权,直接进入演示页(固定通知卡片、New a notification 动态新建入口与 Event log 事件日志):

在这里插入图片描述

图五:example 启动后的演示页

6.2 验证一:发出通知

点击固定通知卡片的 show 按钮,通知出现在系统通知中心顶部(现象见图一),Event log 记录 onShow 回调:

在这里插入图片描述

图六:show 后 Event log 记录 onShow _exampleNotification

6.3 验证二:点击通知

在通知中心点击通知主体,应用回到前台,页面弹出 SnackBar 提示,Event log 记录 onClick(现象见图二)。

6.4 验证三:关闭通知

点击卡片的 close 按钮,通知从通知中心移除(下图),Event log 记录 onClose 与 userCanceled 关闭原因(回调内容见图三):

在这里插入图片描述

图七:close 后通知中心已无 example 通知

6.5 验证四:动态新建与销毁

点击 New a notification 卡片,demo 动态创建一条新通知(identifier 自动生成 UUID v4,标题 example - 0),演示页新增一张卡片展示它:

在这里插入图片描述

图八:动态新建通知后演示页新增卡片,identifier 为自动生成的 UUID

对新通知调用 show,它同样出现在通知中心顶部:

在这里插入图片描述

图九:新建通知 show 后出现在通知中心顶部

点击固定通知卡片的 destroy 按钮,通知被关闭并销毁,演示页固定卡片随之消失:

在这里插入图片描述

图十:destroy 后演示页固定通知卡片消失,Event log 保留 onClose 记录

实测结论:

操作现象结果
show通知中心出现通知,onShow 回调通过
点击通知主体应用回前台,onClick 回调通过
close通知移除,onClose(userCanceled)通过
notify(动态新建)新通知创建并可 show通过
destroy通知销毁,演示页卡片消失通过

操作按钮 onClickAction 的机制说明见 5.3:按钮位于通知的展开形态,自动化模拟点击受限,本文未留下按钮点击瞬间的截图,读者可在真机上展开通知后点击 Yes / No 按钮自行验证。

七、工作原理

一次完整的通知发布调用链如下:

Dart: notification.show()
  → MethodChannel('local_notifier')            # 上游已有通道,鸿蒙插件注册同名通道
    → ArkTS: handleNotify
      → buildWantAgent(...)                    # 为通知主体挂载点击回传的 wantAgent
      → buildContent(...)                      # 组装标题/正文/副标题,超 46 字符切 LONG_TEXT
      → notificationManager.publish(request)
        → 通知出现在系统通知中心
          ← invokeMethod('onLocalNotificationShow') → Dart onShow 回调

点击与关闭的回传链路:

点击通知主体 / 操作按钮
  → wantAgent 拉起宿主 UIAbility,携带 parameters:
      notificationId(identifier)、action(local_notifier.click)、actionIndex?(仅按钮)
    → 插件监听 onNewWant,按有无 actionIndex 分发
      → invokeMethod('onLocalNotificationClick')        → Dart onClick
      → invokeMethod('onLocalNotificationClickAction')  → Dart onClickAction

Dart: notification.close() / destroy()
  → ArkTS: handleClose
    → notificationManager.cancel(DJB2 哈希值, identifier)
      → invokeMethod('onLocalNotificationClose', { closeReason: 'userCanceled' })
        → Dart onClose

授权链路:setup 先查 isNotificationEnabled,未开启时 requestEnableNotification 弹系统授权窗。查询与弹窗是两条异步路径,插件用 replyOnce 标志位互斥,保证 setup 的 Future 只回一次。

几个实现细节,使用时心中有数即可:

鸿蒙侧行为说明
通知标识 (id, tag)鸿蒙通知 id 是 int32,插件用 DJB2 哈希把 UUID 字符串压进 int32 作 id,identifier 本身作 tag,cancel(id, tag) 精确移除
正文样式分界 46 字符不超过 46 字符用 BASIC_TEXT 单行卡片;超过自动切 LONG_TEXT,展开可见全文
subtitle → additionalText副标题映射为卡片附加文本,仅在非空时携带
点击后自动收起tapDismissed: true,点击通知后默认从通知中心移除

DJB2 哈希是逐字符的 hash = (hash << 5) - hash + charCode 运算并把结果压回 32 位整数,同一 identifier 每次计算结果一致,因此发布与取消能对上同一条通知。

两个使用边界与实现直接相关,已列入 FAQ:手动清除通知不触发 onClose(监听系统删除事件需要 SUBSCRIBE_NOTIFICATION 系统权限,三方应用拿不到);点击通知冷启动时 Dart 侧还没有该通知的实例,回调静默忽略。

八、常见问题

Q1:用户在通知中心手动删除了通知,为什么没有触发 onClose?

onClose 只覆盖主动关闭:close / destroy 成功取消后触发(closeReason 固定 userCanceled)。监听系统侧的通知删除事件需要 SUBSCRIBE_NOTIFICATION 系统权限,三方应用无法获取,因此手动清除不产生回调。依赖其它 closeReason 分支(如 timedOut)的业务代码在鸿蒙平台不会执行,需要另行设计兜底逻辑。

Q2:setup 返回 false 是什么意思?怎么恢复?

false 表示用户在授权弹窗中拒绝了通知,或授权查询异常。恢复路径:引导用户到"设置 → 通知管理"打开本应用的通知开关,之后重新调用 setup 返回 true 即可恢复正常。在此之前调用 show 会抛出未初始化异常(见 FAQ Q4)。

Q3:点击通知冷启动应用,为什么没有触发 onClick?

点击通知冷启动时进程是新建的,Dart 侧还没有注册任何 LocalNotification 实例,插件对回调做了空值保护,该次点击被静默忽略,应用本身会正常被拉起;热启动(应用在后台)点击可正常回调。如需感知冷启动来源,可在 UIAbility 的启动 want 中识别 local_notifier.click 这个 action(参数见"七、工作原理")。

Q4:调用 show 抛出 “Not initialized, please call localNotifier.setup first to initialize”?

说明尚未调用 setup,或 setup 的返回值为 false(通知开关未开启)。检查两点:main 中是否在 runApp 前完成 setup;setup 返回 false 时按 FAQ Q2 恢复授权。该行为与上游桌面端(Linux、Windows)一致。

Q5:silent 和 shortcutPolicy 参数在鸿蒙上生效吗?

不生效,鸿蒙侧忽略这两个参数。silent 是桌面端"仅入栏、不弹横幅"的开关,鸿蒙通知中心没有等效机制;shortcutPolicy 是 Windows 快捷方式策略。字段可以照常传,不影响其余功能。

Q6:正文很长会被通知中心截断吗?

不会。正文不超过 46 字符映射 BASIC_TEXT,超过 46 字符自动映射 LONG_TEXT,通知中心展开后可见全文;subtitle 始终显示为卡片附加文本。

九、结语

回顾一下:在 pubspec.yaml 中以 git TAG 引入 local_notifier,main 里一次 setup 完成运行时授权,之后构造 LocalNotification、挂回调、调用 show 即可在鸿蒙通知中心发出通知;点击与按钮点击经 wantAgent 回传到 onClick / onClickAction,close / destroy 触发 onClose。整条链路零权限声明、无需 EventChannel,与上游桌面端共用同一套 Dart 代码,已在 nova 12 Ultra 真机(HarmonyOS 6.1.0 / API 24)完整实测。

插件实现与适配过程(Dart 侧改动、ArkTS 实现、适配说明文件)见姊妹篇《local_notifier 的鸿蒙适配教程》。使用中发现问题欢迎到配套仓库提 Issue,也欢迎发 PR 共建。

相关链接

欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:

Logo

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

更多推荐