本系列针对 CPF-Flutter 组织适配鸿蒙的 fluttertpc 系列插件逐一进行源码分析与真机实测,本文的主角是 notification_permissions——一个用于查询与申请通知权限的轻量插件。

  • 一句话介绍:notification_permissions 是一个跨平台 Flutter 插件,用于查询当前应用的通知权限状态(granted / denied / unknown / provisional)并引导用户开启通知权限,鸿蒙侧由 CPF-Flutter 组织基于原版 notification_permissions 0.6.1 完成适配。

  • 为什么鸿蒙开发者需要它:鸿蒙与 Android 的通知权限模型完全不同——鸿蒙没有运行时 requestPermissions 式的通知授权弹窗 API,权限开关藏在系统设置的「通知管理」深处;适配库把 isNotificationEnabled() 查询和「跳转通知设置页」两个动作封装成了与 Android/iOS 完全一致的 Dart API,业务代码零改动即可跨平台。

  • 实测结论先行:在华为 TLR-AL00(HarmonyOS 6.1.0.145,API 24)真机上,权限查询、设置页跳转、开关切换后状态联动刷新全链路验证通过。

CPF-Flutter/fluttertpc_flutter_notification_permissions仓库地址:https://atomgit.com/CPF-Flutter/fluttertpc_flutter_notification_permissions

在这里插入图片描述


一、最终运行效果

1.1 实测截图

启动首页:应用未授权时,显示 denied 状态与申请按钮

请添加图片描述

点击「ASK FOR NOTIFICATION STATUS」后:直接跳转系统「通知管理」页(注意:不是系统弹窗)

请添加图片描述

手动打开「允许通知」开关

请添加图片描述

返回应用后:生命周期回调自动刷新状态,变为 granted

请添加图片描述

example 还内置了「质量加固」演示页,覆盖 6 个验证场景

请添加图片描述

1.2 验证点表格

功能点预期表现实测结果
首次查询权限状态(未授权)返回 PermissionStatus.denied,页面显示 “The permission status is denied”✅ 通过
点击申请按钮(未授权时)跳转到本应用对应的系统「通知管理」设置页✅ 通过(鸿蒙上不是弹窗,是设置页,见第七节分析)
系统页打开「允许通知」开关系统侧通知授权状态更新(hilog 可见 set ntf auht status ... 1)✅ 通过
返回应用后状态自动刷新通过 WidgetsBindingObserver 在 resumed 时重新查询,页面变为 “granted”✅ 通过
已授权时再次查询返回 PermissionStatus.granted(hilog isNotificationEnabled allowed=1)✅ 通过
已授权时点击申请按钮直接返回 granted,不再跳转✅ 通过(源码逻辑:isNotificationEnabled 为 true 时直接 result.success(granted),且授权后首页不渲染按钮)
unsigned HAP 直接安装——❌ 失败,错误码 9568320: no signature file,必须签名后安装,见第四节说明

二、库介绍与接口说明

notification_permissions 是对原版 pub.dev 插件(Vanethos/notification_permissions)的鸿蒙化适配,Dart 层 API 与原版完全一致,原业务代码无需任何修改。插件在 pubspec.yaml 中注册了三个平台实现:

flutter:
  plugin:
    platforms:
      android:
        package: com.vanethos.notification_permissions
        pluginClass: NotificationPermissionsPlugin
      ios:
        pluginClass: NotificationPermissionsPlugin
      ohos:
        package: com.vanethos.notification_permissions
        pluginClass: NotificationPermissionsPlugin

2.1 核心 API

方法平台支持(Android / iOS / ohos)说明
getNotificationPermissionStatus() → Future<PermissionStatus>✅ / ✅ / ✅查询当前通知权限状态。鸿蒙侧内部调用 notificationManager.isNotificationEnabled(),true → granted,false → denied
requestNotificationPermissions({iosSettings, openSettings}) → Future<PermissionStatus>✅ / ✅ / ✅请求通知权限。鸿蒙侧:未授权则跳转系统通知设置页并返回 denied;已授权直接返回 granted。iosSettings 在鸿蒙上被忽略
PermissionStatus 枚举✅ / ✅ / ✅granted / unknown / denied / provisional 四种状态。注意:鸿蒙永远不会返回 provisional(这是 iOS 临时授权概念)

2.2 与原版的差异点

  • 原版 Android 实现会弹出系统授权对话框或跳转应用详情页;鸿蒙实现改为跳转专用的通知管理设置页(systemui_notification_settings),路径更短、更精准。
  • 鸿蒙版额外响应 getPlatformVersion 方法(返回 "OpenHarmony ^ ^ "),属于适配模板的保留调试接口。
  • openSettings 参数在鸿蒙上实际恒为「打开设置页」行为,不生效(原生端未读取该参数)。

三、环境准备

3.1 实测环境

项目版本/配置
Flutter3.47.7-ohos-1.0.0(channel oh-3.47-dev,Dart 3.13.3)
DevEco Studio26.0(SDK 目录 sdk/default,API 26 编译环境可用)
真机华为 TLR-AL00
系统版本HarmonyOS 6.1.0.145(API 24)
compatibleSdkVersion6.1.1(24)(example/ohos/build-profile.json5 实际值)
targetSdkVersion6.1.1(24)

3.2 鸿蒙通知权限机制速览

理解鸿蒙与 Android 的差异,是用好这个插件的前提:

  1. 鸿蒙的通知权限不是「运行时权限」。它不进入 requestPermissionsFromUser 那套 ACL/运行时权限体系,而是由系统通知服务(ANS,Advanced Notification Service)按应用维度管理一个布尔开关。
  2. 查询用 notificationManager.isNotificationEnabled():返回当前应用是否被允许发通知。该接口不需要额外权限,插件正是基于它实现状态查询。
  3. 鸿蒙没有一个通用的「弹窗申请通知授权」公开 API。早期 API 曾提供 requestEnableNotification() 弹窗授权,但它有严格限制(依赖 UIAbility 上下文、每次冷启动最多弹两次、且新版本已不推荐直接使用),所以适配库选择了更稳妥的方案:未授权时直接 startAbility 跳转到系统设置的应用通知管理页,让用户手动打开开关——这与原生鸿蒙应用的主流引导做法一致。
  4. 用户在设置页开关权限后,系统会立即更新授权状态,应用回到前台时再查询即可拿到新状态——这正是 example 首页 WidgetsBindingObserver 监听 resumed 刷新状态的原因。

四、引入依赖

⚠️ 重要提醒:README 中的 git 地址有误

仓库自带的 README.OpenHarmony.md 和 README.OpenHarmony_CN.md 中,依赖地址写的是 https://gitcode.com/CPF-Flutter/fluttertpc_flutter_notification_permissions。经 git remote -v 实际验证,仓库托管在 AtomGit,gitcode 地址并不可用。本文已顺手修正了仓库内两份 README,读者接入时请直接使用下面验证过的写法。

实际远程仓库与 TAG(以本仓库实际输出为准):

$ git remote -v
origin  https://atomgit.com/CPF-Flutter/fluttertpc_flutter_notification_permissions (fetch)
origin  https://atomgit.com/CPF-Flutter/fluttertpc_flutter_notification_permissions (push)

$ git tag
0.1.0
0.2.0
0.4.5
0.4.6
0.6.1-ohos-1.0.0        ← 鸿蒙适配版 TAG
0.6.1-ohos.1.0.0-beta.1
v0.4.8
v0.5.0
v0.6.0
v0.6.1

$ git branch -a
* br_3.27               ← 鸿蒙适配分支
  remotes/origin/br_3.27
  remotes/origin/br_3.27_dev
  ...

4.1 pubspec.yaml 写法

在应用工程(或插件的 example)的 pubspec.yaml 中添加:

dependencies:
  notification_permissions:
    git:
      url: https://atomgit.com/CPF-Flutter/fluttertpc_flutter_notification_permissions
      ref: 0.6.1-ohos-1.0.0

TAG 与分支对应关系(README 中表格列出的 Flutter 版本虽然不同,但 TAG/分支均相同,本仓库实际存在该 TAG 与分支):

Flutter 框架版本TAG分支
3.7 / 3.22 / 3.27 / 3.350.6.1-ohos-1.0.0br_3.27

然后执行:

flutter pub get

4.2 关于签名的实战提醒(本次实测踩坑实录)

仓库 example 预编译产物只有 unsigned HAP,直接安装会被拒绝:

$ hdc install -r entry-default-unsigned.hap
[Info]App install path:...\entry-default-unsigned.hap
msg:error: failed to install bundle. code:9568320 error: no signature file.

另外,用 OpenHarmony SDK 自带的调试材料本地自签 profile 的方案在零售 HarmonyOS 真机上同样会被拒绝(错误码 9568257: fail to verify pkcs7 file)——零售机不信任 OpenHarmony 调试证书链。正确做法是在 DevEco Studio 中登录华为开发者账号,使用 File → Project Structure → Signing Configs → Automatically generate signature 自动签名,它签发的 .p7b Profile 会绑定你的 bundleName 与设备 UDID。本次实测即采用自动签名材料完成构建安装(详见第八节 FAQ-1)。


五、代码接入

example 的完整代码在 example/lib/main.dart,下面按功能分小节拆解。

5.1 初始化:进应用先查一次权限

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  Future<String> permissionStatusFuture = Future.value('');

  
  void initState() {
    super.initState();
    // 启动即查询一次通知权限状态
    permissionStatusFuture = getCheckNotificationPermStatus();
    // 注册生命周期观察者,便于从设置页返回后刷新
    WidgetsBinding.instance.addObserver(this);
  }

代码逐段分析

  • _MyAppState 混入 WidgetsBindingObserver,这是跨平台权限类插件的经典套路:权限的授予动作发生在「应用外」(系统设置页),应用自身无法监听,只能靠生命周期回前台的时机重新查询。
  • permissionStatusFuture 是一个 Future<String>,交给 FutureBuilder 渲染,避免在 initState 里直接 setState。
  • initState 里同时完成「首查」与「注册观察者」两件事,顺序无所谓,但缺一不可。

5.2 查询权限状态

/// 查询通知权限状态,把枚举转成字符串便于展示
Future<String> getCheckNotificationPermStatus() {
  return NotificationPermissions.getNotificationPermissionStatus()
      .then((status) {
    switch (status) {
      case PermissionStatus.denied:
        return permDenied;
      case PermissionStatus.granted:
        return permGranted;
      case PermissionStatus.unknown:
        return permUnknown;
      case PermissionStatus.provisional:
        return permProvisional;
      default:
        return Future.value('unknown');
    }
  });
}

代码逐段分析

  • NotificationPermissions.getNotificationPermissionStatus() 是静态方法,一行即可完成查询,返回 PermissionStatus 枚举。
  • 枚举四个值的来源:Dart 层把 MethodChannel 返回的字符串(granted/denied/provisional/其他)映射成枚举,鸿蒙原生端只会返回 granted 和 denied 两种,provisional 仅供 iOS 使用。
  • example 把枚举转回字符串仅用于 UI 展示,业务代码一般直接 switch (status) 分支处理即可。

5.3 生命周期联动:回前台自动刷新


void didChangeAppLifecycleState(AppLifecycleState state) {
  if (state == AppLifecycleState.resumed) {
    setState(() {
      permissionStatusFuture = getCheckNotificationPermStatus();
    });
  }
}


void dispose() {
  WidgetsBinding.instance.removeObserver(this);
  super.dispose();
}

代码逐段分析

  • 只有 resumed 状态才触发重查:用户从通知设置页返回应用的那一刻,权限可能已经变了。
  • 每次 setState 都给 permissionStatusFuture 赋一个新的 Future,FutureBuilder 检测到 future 实例变化会重新走 loading → data 流程,实现 UI 刷新。
  • dispose 里务必 removeObserver,防止页面销毁后回调空指针。

5.4 申请权限

TextButton(
  child: Text("Ask for notification status".toUpperCase()),
  onPressed: () {
    NotificationPermissions.requestNotificationPermissions(
            iosSettings: const NotificationSettingsIos(
                sound: true, badge: true, alert: true))
        .then((_) {
      // 申请流程结束后立即复查状态
      setState(() {
        permissionStatusFuture = getCheckNotificationPermStatus();
      });
    });
  },
)

代码逐段分析

  • requestNotificationPermissions 传入的 NotificationSettingsIos(sound/alert/badge)是 iOS 专属参数,在鸿蒙上会被原生端完全忽略,但为了三端代码统一,建议照传。
  • 在鸿蒙上的实际行为链:插件先查 isNotificationEnabled(),已授权 → 直接返回 granted;未授权 → 跳转系统通知设置页并返回 denied。方法返回时用户还没做选择,所以 .then 里必须再查一次状态。
  • FutureBuilder 的渲染逻辑:状态为 granted 时只显示文本;否则额外渲染这个申请按钮——这就是授权后按钮自动消失的原因。

5.5 UI 渲染骨架

FutureBuilder(
  future: permissionStatusFuture,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    }
    if (snapshot.hasError) {
      return Text('error while retrieving status: ${snapshot.error}');
    }
    if (snapshot.hasData) {
      var textWidget = Text(
        "The permission status is ${snapshot.data}",
        style: TextStyle(fontSize: 20),
      );
      // 已授权:只显示文本;未授权:文本 + 申请按钮
      if (snapshot.data == permGranted) {
        return textWidget;
      }
      return Column(children: <Widget>[textWidget, /* 申请按钮 */]);
    }
    return Text("No permission status yet");
  },
)

代码逐段分析

  • 三态渲染:waiting 转圈、error 报错、hasData 出内容,是 FutureBuilder 的标准防御写法。
  • 原生端返回 PERMISSION_CHECK_FAILED 等错误码时,会走 snapshot.hasError 分支,实际项目中建议在此给用户友好提示。

六、运行与验证

6.1 构建、安装与启动

使用华为开发者账号自动签名材料完成签名构建后(构建命令 flutter build hap --debug,产物 entry-default-signed.hap):

$ hdc install -r entry-default-signed.hap
[Info]App install path:...\entry-default-signed.hap msg:install bundle successfully.

$ hdc shell aa start -a EntryAbility -b dev.kakzaki.color_brightness_example
start ability successfully.

说明:本次实测用的调试签名 Profile 绑定的 bundleName 为 dev.kakzaki.color_brightness_example(自动签名 Profile 与 bundleName 一一绑定,见 FAQ-1),因此将 example 的 AppScope/app.json5 中 bundleName 临时改为了该值。这对插件行为无任何影响——插件跳转设置页时用的是运行时动态获取的 abilityInfo.bundleName,而非硬编码。

6.2 hilog 实测日志原文

① 点击申请按钮:MethodChannel 消息到达 → 原生查询 → 未授权(allowed=0)→ 跳转设置页(应用切 inactive/paused):

10-22 01:56:09.702  31217  31217 D A000FF/.../Flutter: FlutterNapi --> called handlePlatformMessage
    Channel: notification_permissions. Response ID: 11
10-22 01:56:09.702  31217  31217 D A000FF/.../Flutter: DartMessenger --> Received message from Dart
    over channel 'notification_permissions'
10-22 01:56:09.704  31217  31622 I C01203/.../Ans: isNotificationEnabled code=0,allowed=0
10-22 01:56:09.897  31217  31217 I A000FF/.../Flutter: LifecycleChannel --> Sending
    AppLifecycleState.inactive message.
10-22 01:56:09.899  31217  31217 I A000FF/.../Flutter: LifecycleChannel --> Sending
    AppLifecycleState.paused message.

② 在设置页打开开关:系统通知服务写入授权状态(foundation 进程日志):

10-22 01:56:24.226   1496  32212 I C01203/foundation/Ans: bundelName:dev.kakzaki.color_brightness_example,
    uid:20020231, state[1]
10-22 01:56:24.231   1496  32212 I C01203/foundation/Ans: set ntf auht status
    dev.kakzaki.color_brightness_example 20020231 1 0

③ 返回应用:resumed → 自动复查 → allowed=1(对应页面立即变为 granted):

10-22 01:57:37.506  31217  31217 I A000FF/.../Flutter: LifecycleChannel --> Sending
    AppLifecycleState.resumed message.
10-22 01:57:37.509  31217  31217 D A000FF/.../Flutter: DartMessenger --> Received message from Dart
    over channel 'notification_permissions'
10-22 01:57:37.519  31217  32779 I C01203/.../Ans: isNotificationEnabled code=0,allowed=1

日志里的 A000FF/.../Flutter、C01203/.../Ans 是 hilog 的 domain/tag:前者是 Flutter 通道层日志(DartMessenger/FlutterNapi/LifecycleChannel),后者是系统通知服务 ANS 的日志。code=0 表示调用成功,allowed 即授权结果。

6.3 全流程截图

完整截图序列(denied → 设置页 → 开关打开 → 返回 granted → 质量加固演示页)见第一节;所有截图与 hilog 原文均存档于仓库 screenshots/ohos/ 目录(np_hilog_evidence.txt 为日志取证汇总)。


七、工作原理

7.1 MethodChannel 通道与调用链路

Dart 层定义(lib/notification_permissions.dart):

static const MethodChannel _channel =
    const MethodChannel('notification_permissions');

通道名为 notification_permissions,与 Android/iOS/ohos 三端原生实现保持一致。完整调用链路:

┌────────────────────────────── Dart (UI 线程) ──────────────────────────────┐
│  NotificationPermissions.getNotificationPermissionStatus()                 │
│      └─ _channel.invokeMethod('getNotificationPermissionStatus')           │
└──────────────────────────────────┬─────────────────────────────────────────┘
                                   │ BinaryMessenger (平台通道)
                                   ▼
┌────────────────────────── ArkTS 插件层 (ohos) ─────────────────────────────┐
│  NotificationPermissionsPlugin.onMethodCall(call, result)                  │
│      ├─ "getNotificationPermissionStatus"                                  │
│      │      └─ notificationManager.isNotificationEnabled()                 │
│      │             ├─ true  → result.success("granted")                    │
│      │             └─ false → result.success("denied")                     │
│      └─ "requestNotificationPermissions"                                   │
│             ├─ isNotificationEnabled() == true                             │
│             │      └─ result.success("granted")                            │
│             └─ == false → startAbility(Want{                               │
│                    uri: "systemui_notification_settings",                  │
│                    parameters: { pushParams: { bundleName: 本应用 } }       │
│                 })  → result.success("denied")                             │
└──────────────────────────────────┬─────────────────────────────────────────┘
                                   │ result.success/error
                                   ▼
                    Dart 层 _getPermissionStatus() 映射为枚举

关键步骤拆解:

  1. Dart 调 invokeMethod,消息经 Flutter OHOS embedder 的 DartMessenger 送达平台线程(对应日志 Received message from Dart over channel 'notification_permissions')。
  2. 插件在 onAttachedToEngine 时注册了 MethodCallHandler,按 call.method 字符串分发。
  3. 查询方法直接对接系统 ANS 服务,一次异步调用即返回。
  4. 申请方法在未授权时通过 隐式 Want(uri: "systemui_notification_settings" + pushParams.bundleName)拉起系统设置的通知管理页——这是华为系统设置对外暴露的内部页面路由,精准定位到「本应用」的通知开关。

7.2 原生 ArkTS 插件实现分析

插件源码只有约 150 行(ohos/src/main/ets/components/plugin/NotificationPermissionsPlugin.ets),核心结构:

export default class NotificationPermissionsPlugin
    implements FlutterPlugin, MethodCallHandler, AbilityAware {

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.context = binding.getApplicationContext();
    this.channel = new MethodChannel(
        binding.getBinaryMessenger(), "notification_permissions");
    this.channel.setMethodCallHandler(this);
  }

  onAttachedToAbility(binding: AbilityPluginBinding): void {
    this.ability = binding.getAbility();   // 保存 UIAbility 引用备用
  }
  ...
}

代码逐段分析

  • 插件同时实现 FlutterPlugin(引擎生命周期)、MethodCallHandler(消息分发)、AbilityAware(获取 UIAbility)三个接口,是 flutter_ohos 插件的标准三件套。
  • onAttachedToEngine 只拿到 ApplicationContext,而跳转设置页必须用 UIAbility 的 context(startAbility 是 Ability 级能力),所以要通过 AbilityAware.onAttachedToAbility 额外把 UIAbility 存下来。这也是为什么该插件在 Ability 未附着时会返回 UNAVAILABLE 错误。

查询实现:

private async getNotificationPermissionStatus(result: MethodResult): Promise<void> {
  try {
    const isEnabled: boolean = await notificationManager.isNotificationEnabled();
    const status: string = isEnabled ? PERMISSION_GRANTED : PERMISSION_DENIED;
    result.success(status);
  } catch (error) {
    result.error("PERMISSION_CHECK_FAILED", "Failed to check notification permission", null);
  }
}

代码逐段分析

  • isNotificationEnabled() 不带参数时查询的是当前应用自身的通知开关,无需任何 ohos.permission 声明,普通应用可直接调用。
  • 返回值被压缩成 granted/denied 二值——鸿蒙没有 iOS 的 provisional(临时授权)概念,也查询不出 Android 的「永久拒绝」状态,Dart 层的 unknown 只在通道异常时兜底出现。
  • 异常统一转成 result.error(错误码, 描述, null),Dart 侧表现为 Future 抛 PlatformException。

申请实现(鸿蒙版的核心差异所在):

private async requestNotificationPermissions(result: MethodResult): Promise<void> {
  const isEnabled: boolean = await notificationManager.isNotificationEnabled();
  if (!isEnabled) {
    if (this.ability === null) {
      result.error("UNAVAILABLE", "Ability is unavailable, cannot open settings", null);
      return;
    }
    const openSuccess = await this.openNotificationSettings();
    if (openSuccess) {
      result.success(PERMISSION_DENIED);   // 注意:跳转成功也返回 denied
    } else {
      result.error("SETTINGS_ERROR", "Failed to open notification settings", null);
    }
  } else {
    result.success(PERMISSION_GRANTED);
  }
}

private async openNotificationSettings(): Promise<boolean> {
  const want: Want = {
    uri: "systemui_notification_settings",
    parameters: {
      pushParams: {
        bundleName: this.ability.context.abilityInfo.bundleName
      }
    }
  } as Want;
  await this.ability.context.startAbility(want);
  return true;
}

代码逐段分析

  • 与 Android 版最大的差异:Android 版会弹系统授权对话框(Android 13+ 的 POST_NOTIFICATIONS 运行时权限弹窗),而鸿蒙公开 API 没有「弹窗申请通知授权」的能力(历史上的 requestEnableNotification() 弹窗有调用次数限制且新版本不推荐),所以适配版选择直接跳系统设置页。这决定了鸿蒙上 requestNotificationPermissions 返回值语义是「跳转动作的结果」而非「授权结果」——跳转成功返回的仍是 denied,真正的授权结果要等用户操作后回前台再查。
  • Want 的 uri 使用系统设置注册的 systemui_notification_settings 路由,pushParams.bundleName 让设置页直接定位到本应用的通知管理卡片(实测截图第二节第二张就是落到这个页面的效果)。
  • bundleName 取自运行时 abilityInfo,不是硬编码——所以改 bundleName 打包完全不影响功能。
  • 整个文件没有请求任何权限、没有在 module.json5 声明 requestPermissions,因为查询接口本身就是免权限的,这比 Android 版还要省事。

7.3 鸿蒙通知权限模型 vs Android

维度AndroidHarmonyOS
权限归属运行时权限(Android 13+ POST_NOTIFICATIONS)系统通知服务 ANS 按应用管理的开关
申请方式弹授权对话框 / 跳应用详情页只能跳转系统通知设置页(无公开弹窗 API)
查询接口NotificationManager.areNotificationsEnabled()notificationManager.isNotificationEnabled()
状态粒度granted / denied(可细分永久拒绝)granted / denied 二值
权限声明需在 AndroidManifest 声明无需任何权限声明
申请返回值语义弹窗结果(用户已选择)跳转结果(用户尚未选择)

八、FAQ

Q1:hdc install 报 code:9568320 error: no signature file 怎么办?

HAP 没有签名。在 DevEco Studio 中打开 ohos 工程,File → Project Structure → Signing Configs 勾选「Automatically generate signature」,登录华为开发者账号后自动生成 .p12/.cer/.p7b 并写入 build-profile.json5 的 signingConfigs,再重新构建。注意:用 OpenHarmony SDK 调试材料手工自签 profile 在零售 HarmonyOS 真机上不可用(实测报 9568257: fail to verify pkcs7 file,零售机不信任 OpenHarmony 调试证书链),必须走 DevEco 自动签名。另外自动签名生成的 Profile 与 bundleName 一一绑定,改了 bundleName 就得重新生成签名。

Q2:为什么鸿蒙上点申请按钮不弹系统授权弹窗,直接跳到了设置页?

这是鸿蒙平台特性而非 bug。鸿蒙没有公开的「通知授权弹窗」API(requestEnableNotification() 有调用次数限制且新版不推荐),适配库选择直接 startAbility 跳转系统通知管理页。开发者应在 UI 上预期「跳设置页」的交互,并在回前台后复查状态。

Q3:为什么 requestNotificationPermissions 返回 denied,用户明明等下会点「允许」?

方法返回时只代表「跳转动作完成」,用户还没做选择。正确姿势是在 WidgetsBindingObserver.didChangeAppLifecycleState 的 resumed 时机重新调用 getNotificationPermissionStatus(),example 首页就是这么做的。

Q4:provisional 状态在鸿蒙上会出现吗?

不会。provisional 是 iOS 12+ 的「临时静默授权」概念,鸿蒙原生端只会返回 granted/denied,unknown 仅在通道调用失败兜底时出现。跨平台代码不要依赖 provisional 做鸿蒙逻辑分支。

Q5:NotificationSettingsIos 的 sound/alert/badge 参数在鸿蒙上需要传吗?

传了也会被鸿蒙原生端忽略(原生端只读 openSettings,而 openSettings 实际也未生效)。为了三端代码一致可以照传;如果只做鸿蒙,直接 NotificationPermissions.requestNotificationPermissions() 即可。

Q6:鸿蒙上需要像 Android 那样在 module.json5 里声明通知权限吗?

不需要。isNotificationEnabled() 查询自身开关与 startAbility 跳系统设置都是免权限操作,插件的 ohos/src/main/module.json5 没有声明任何 requestPermissions。但如果你要真正发送通知,发送侧(如 notificationManager.publish)对应用自身的通知开关状态有要求,即必须处于 granted 状态——这正是本插件存在的意义。


九、结语

notification_permissions 的鸿蒙适配是一次「小而美」的移植:Dart 层 API 与原版零差异,ArkTS 侧仅用约 150 行代码对接了 isNotificationEnabled() 与系统设置页跳转,干净地绕开了鸿蒙「无通知授权弹窗 API」的平台限制。真机实测全链路(查询 → 跳转 → 开关 → 回前台刷新)均符合预期,可放心在鸿蒙 Flutter 项目中使用。

需要留意的点集中在工程侧而非代码侧:仓库 README 的 git 地址需以 atomgit 为准(本文已修正)、unsigned HAP 无法直装、调试签名与 bundleName/设备 UDID 强绑定。理解第七节的「鸿蒙通知权限模型 vs Android」差异后,这套插件的返回值语义与生命周期联动套路就完全透明了。

能力对照表

能力AndroidiOSHarmonyOS(实测)
查询通知权限状态✅✅✅ isNotificationEnabled()
申请通知权限(弹窗)✅(Android 13+ 弹窗)✅❌ 平台无此公开 API
申请通知权限(跳设置页)✅✅✅ systemui_notification_settings Want
provisional 临时授权❌✅❌
免权限声明调用❌(需声明 POST_NOTIFICATIONS)✅✅
openSettings 参数控制✅✅⚠️ 不生效(恒为跳转行为)
Logo

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

更多推荐