给鸿蒙 App 增加本地通知的能力 —— local_notifier 的鸿蒙使用指南
给鸿蒙 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
检查要点:
- 通知能力走运行时授权,应用无需在 module.json5 中声明任何权限,首次 setup 由系统弹窗向用户申请;
- 点击回传通过 wantAgent 完成,不需要 EventChannel 之类的辅助通道;
- 完整实测过程见"六、运行与验证"。
二、local_notifier 是什么
local_notifier(pub.dev 0.1.6,作者 leanflutter,MIT 协议)是一个显示本地通知的 Flutter 插件:构造 LocalNotification 后调用 show() 即可发布,支持标题、副标题、正文与最多两个操作按钮,提供 onShow / onClick / onClickAction / onClose 四个回调。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持,Dart API 与上游完全一致。
几个对使用者友好的特点:
- 零权限声明:不需要在 module.json5 里声明通知权限,首次 setup 时由系统弹窗完成运行时授权;
- 点击回传无需 EventChannel:发布时为通知挂载 wantAgent,点击后经 onNewWant 回传到 Dart 回调;
- 接口与上游一致:setup、LocalNotification、show / close / destroy 的用法与桌面端完全相同,跨平台一套代码。
另外两个行为差异值得知道:正文超过 46 字符时自动改用 LONG_TEXT 样式,长文本在通知中心不被截断;close / destroy 主动关闭成功后触发 onClose(原因为 userCanceled),比上游 macOS、Linux 端(不触发该回调)提供更明确的生命周期信号。
接口说明:
| 名称 | 描述 | 类型 | 参数类型 | 返回值 | 必填 | 鸿蒙平台支持 |
|---|---|---|---|---|---|---|
| localNotifier.setup | 初始化插件并确保通知授权 | method | appName: String, shortcutPolicy: ShortcutPolicy | Future<void> | 是 | 是 |
| localNotifier.notify / notification.show | 发出通知 | method | notification: LocalNotification | Future<void> | 是 | 是 |
| localNotifier.close / notification.close | 关闭通知 | method | notification: LocalNotification | Future<void> | 是 | 是 |
| localNotifier.destroy / notification.destroy | 关闭并销毁通知 | method | notification: LocalNotification | Future<void> | 是 | 是 |
| LocalNotification.onShow | 通知显示回调 | property | void Function() | - | 否 | 是 |
| LocalNotification.onClick | 通知点击回调 | property | void Function() | - | 否 | 是 |
| LocalNotification.onClickAction | 操作按钮点击回调 | property | void Function(int) | - | 否 | 是 |
| LocalNotification.onClose | 通知关闭回调 | property | void Function(LocalNotificationCloseReason) | - | 否 | 是(主动关闭时) |
其中 LocalNotification 模型支持字段:identifier(唯一标识,未传时自动生成 UUID v4)、title(标题)、subtitle(副标题)、body(正文)、silent(仅桌面端生效,鸿蒙侧忽略)、actions(操作按钮列表,鸿蒙侧最多 2 个)。
三、环境准备
本文所有实测均在以下环境完成:
| 项 | 版本 | 说明 |
|---|---|---|
| Flutter(ohos 版) | 3.41.10-ohos-1.0.1 | 主验证环境,真机实测 |
| 编译 SDK | 5.1.0(18) | example 宿主工程 compatibleSdkVersion 同值 |
| 真机 | HUAWEI nova 12 Ultra(ADA-AL10) | HarmonyOS 6.1.0.135 / API 24,分辨率 1224 × 2776 |
环境搭建步骤参考官方指南:Flutter OHOS 开发环境搭建指南。两点提醒:
- 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
- 真机安装需要签名的 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.41 | 0.1.6-ohos-1.0.0-beta.1 | feat/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();
三个要点:
- identifier 不传时自动生成 UUID v4,是后续 close / destroy 与回调分发的唯一凭据,需要关闭某条通知时先把它保存下来;
- onShow 在通知发布成功后触发;onClick 在用户点击通知主体时触发,应用在后台时点击会把应用拉回前台;
- 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 代码在各端的行为:
| 平台 | 是否需要 setup | onClose 触发 |
|---|---|---|
| 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 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:
更多推荐


所有评论(0)