Flutter/OpenHarmony 三方库适配实战:flutter_local_notifications 本地通知

通知看起来只是“弹一条消息”,但在不同系统上,权限、通知槽位、通知 ID、取消策略和点击事件分别由不同 API 管理。flutter_local_notifications 的跨平台 API 已经把这些差异封装在 Dart 层,本次 OHOS 适配的任务就是把公开的 Dart 契约接到 @ohos.notificationManager,同时明确哪些能力首版不做。

一、项目信息与适配目标

项目内容
仓库名称flutter_local_notifications
AtomGit 适配仓库https://atomgit.com/oh-flutter/flutter_local_notifications
上游版本22.3.2
OHOS 适配 TAG22.3.2-ohos-1.0.0-beta.1
适配分支main
方法通道dexterous.com/flutter/local_notifications
原生模块OHOS HAR,入口类 FlutterLocalNotificationsPlugin

首版支持:

能力OHOS 实现
初始化查询通知开关状态
请求权限调用系统通知授权页面
show()发布立即通知,同 ID 更新
cancel()按 ID 取消
cancelAll()取消当前应用全部通知

首版不支持定时通知、重复通知、后台启动、通知点击回调和 payload 启动事件。未实现的方法返回明确错误,不会伪造成功。

二、保留上游调用方式

业务代码仍然使用原来的 FlutterLocalNotificationsPlugin:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

Future<void> showOrderNotification() async {
  final FlutterLocalNotificationsPlugin plugin =
      FlutterLocalNotificationsPlugin();

  final OhosFlutterLocalNotificationsPlugin? ohos =
      plugin.resolvePlatformSpecificImplementation<
          OhosFlutterLocalNotificationsPlugin>();
  final enabled = await ohos?.requestPermissions() ?? false;
  if (!enabled) {
    return;
  }

  await plugin.show(
    id: 1,
    title: '订单状态',
    body: '订单已发货',
    payload: 'orders/1',
  );
}

initialize() 在 OHOS 分支直接进入 OHOS 实现,不需要业务代码再创建一套 OHOS 专用对象:

if (isOhosPlatform()) {
  return await resolvePlatformSpecificImplementation<
        OhosFlutterLocalNotificationsPlugin
      >()
      ?.initialize();
}

pubspec.yaml 中的 OHOS 声明是插件被 Flutter 工具链识别的关键:

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: FlutterLocalNotificationsPlugin
        dartPluginClass: OhosFlutterLocalNotificationsPlugin

三、Dart 注册类只负责契约转换

OHOS Dart 类实现初始化、授权、发布和取消四个公开能力:

class OhosFlutterLocalNotificationsPlugin
    extends FlutterLocalNotificationsPlatform {
  static const MethodChannel channel = MethodChannel(
    'dexterous.com/flutter/local_notifications',
  );

  static void registerWith() {
    FlutterLocalNotificationsPlatform.instance =
        OhosFlutterLocalNotificationsPlugin();
  }

  Future<bool> initialize() async {
    return await channel.invokeMethod<bool>('initialize') ?? false;
  }

  Future<bool> requestPermissions() async {
    return await channel.invokeMethod<bool>('requestPermissions') ?? false;
  }

  
  Future<void> show({
    required int id,
    String? title,
    String? body,
    String? payload,
  }) {
    return channel.invokeMethod('show', <String, Object?>{
      'id': id,
      'title': title,
      'body': body,
      'payload': payload ?? '',
    });
  }

  
  Future<void> cancel({required int id}) {
    return channel.invokeMethod('cancel', id);
  }

  
  Future<void> cancelAll() {
    return channel.invokeMethod('cancelAll');
  }
}

这里保留 payload 字段是为了兼容上游调用签名,但 OHOS 首版没有把 payload 注入系统通知点击事件。文档明确这一点,避免业务把“发布成功”误认为“点击回调已接通”。

四、ArkTS 层:方法分发和生命周期

原生入口实现 FlutterPlugin、MethodCallHandler 和 AbilityAware:

const CHANNEL = 'dexterous.com/flutter/local_notifications';

export default class FlutterLocalNotificationsPlugin
  implements FlutterPlugin, MethodCallHandler, AbilityAware {
  private channel: MethodChannel | null = null;
  private uiAbilityContext: common.UIAbilityContext | null = null;

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(
      binding.getBinaryMessenger(),
      CHANNEL,
    );
    this.channel.setMethodCallHandler(this);
  }

  onAttachedToAbility(binding: AbilityPluginBinding): void {
    this.uiAbilityContext = binding.getAbility().context;
  }

  onDetachedFromAbility(): void {
    this.uiAbilityContext = null;
  }
}

initialize 直接读取通知开关:

case 'initialize':
  notificationManager.isNotificationEnabled()
    .then((enabled: boolean): void => result.success(enabled))
    .catch((error: ESObject): void => this.errorResult(result, error));
  return;

通知授权需要当前 UIAbilityContext。已授权时立即返回 true,未授权时请求系统通知页面:

private requestPermissions(result: MethodResult): void {
  notificationManager.isNotificationEnabled()
    .then((enabled: boolean): void => {
      if (enabled) {
        result.success(true);
        return;
      }

      const context = this.uiAbilityContext;
      if (context === null) {
        result.error(
          'ability_context_missing',
          'UIAbilityContext is not available.',
          null,
        );
        return;
      }

      notificationManager.requestEnableNotification(context)
        .then((): Promise<boolean> =>
          notificationManager.isNotificationEnabled())
        .then((nowEnabled: boolean): void => result.success(nowEnabled));
    });
}

这样返回值反映用户完成授权后的真实状态,而不是请求页面是否曾经显示过。

五、发布一条通知

show() 先再次检查通知开关,避免用户在设置页关闭通知后静默失败。通知内容使用基础文本类型,槽位使用服务信息:

private show(call: MethodCall, result: MethodResult): void {
  notificationManager.isNotificationEnabled()
    .then((enabled: boolean): void => {
      if (!enabled) {
        result.error(
          'notification_disabled',
          'Notification permission is not enabled.',
          null,
        );
        return;
      }

      const request: notificationManager.NotificationRequest = {
        id: Number(call.argument('id')),
        notificationSlotType:
          notificationManager.SlotType.SERVICE_INFORMATION,
        content: {
          notificationContentType:
            notificationManager.ContentType
              .NOTIFICATION_CONTENT_BASIC_TEXT,
          normal: {
            title: String(call.argument('title') ?? ''),
            text: String(call.argument('body') ?? ''),
          },
        },
      };

      notificationManager.publish(request)
        .then((): void => result.success(null));
    });
}

同一个 ID 再次 publish 时由系统按通知 ID 更新,业务不需要额外维护 OHOS 专用 ID 表。取消和全部取消直接使用系统 API:

case 'cancel':
  notificationManager.cancel(Number(call.argument('id')))
    .then((): void => result.success(null));
  return;
case 'cancelAll':
  notificationManager.cancelAll()
    .then((): void => result.success(null));
  return;

六、为什么首版不做定时和点击回调

上游 API 的 zonedSchedule()、periodicallyShow() 依赖后台调度和重复任务;点击回调还涉及系统通知事件、进程启动方式和 payload 恢复。OHOS 首版只实现了前台立即通知,未实现的方法走 notImplemented() 或明确错误,这样业务可以在日志中看到“能力缺失”,而不是收到一个永远不触发的回调。

当前能力边界:

立即通知       支持
按 ID 取消     支持
全部取消       支持
定时通知       不支持
重复通知       不支持
点击回调       不支持
后台启动       不支持
payload 恢复   不支持

后续版本如果接入 OHOS 后台任务和通知事件,需要同时补充 Dart 启动详情、后台 isolate 回调和系统事件桥接,不能只把定时方法改成 show()。

七、测试、构建和真实截图

新增测试覆盖 OHOS 注册、初始化、授权、show、cancel 和 cancelAll 通道:

flutter test \
  test/ohos_flutter_local_notifications_test.dart \
  test/ohos_plugin_metadata_test.dart

结果:All tests passed。

flutter analyze

结果:无 error。

示例工程的 OHOS HAP 已执行:

cd flutter_local_notifications/example
flutter build hap --debug
hdc install -r ohos/entry/build/default/outputs/default/entry-default-unsigned.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_local_notifications_example

在这里插入图片描述

截图中可以看到示例首页和通知操作入口。实际发布通知时,系统通知中心的表现还取决于用户是否授权、系统版本和通知设置。

八、常见问题

  • 页面启动后立即调用 show(),但没有通知:先调用 OHOS 的 requestPermissions() 并检查 isNotificationEnabled()。
  • requestPermissions() 返回 ability_context_missing:检查 Ability 是否绑定,确认插件没有只保存 ApplicationContext。
  • 取消无效:检查传入的 ID 是否与发布时的 id 完全一致。
  • 点击通知没有跳转:首版尚未接通通知点击事件,应使用业务自己的页面恢复机制。
  • 定时通知无效:当前版本不支持,需要等待后续后台任务适配。

社区

  • OpenHarmony Flutter 社区:https://atomgit.com/oh-flutter
  • CPF-Flutter 社区:https://atomgit.com/CPF-Flutter
  • 本适配仓库讨论区:https://atomgit.com/oh-flutter/flutter_local_notifications/issues

欢迎提交设备型号、系统版本、通知设置和 hilog 日志,一起完善 OHOS 本地通知适配。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐