Flutter 三方库 OpenHarmony 鸿蒙适配实战:OneSignal 推送通知插件 ArkTS 原生适配全流程
基于 Flutter-OH 3.44.9-dev(Dart 3.12.2)在 Windows 10 22H2 上全程实测通过;开发工具 DevEco Studio 26,HarmonyOS SDK compatibleSdkVersion 5.1.0(18) / compileSdkVersion 26.0.0.105;真机环节在一台**鸿蒙 PC(OpenHarmony 6.1.1,API 24,arm64,2in1 形态)**上验证。文中所有源码分析、改动内容、构建输出、真机效果均为实际环境抓取,可放心对照复现。
前言
OpenHarmony × Flutter 社区资源
- 社区主页:CPF-Flutter —— OpenHarmony Flutter 生态维护组织
- Flutter-OH 主仓库:CPF-Flutter/flutter_flutter —— SDK 与 Engine 的 OpenHarmony 适配版本
- 三方库适配与开发文档:OpenHarmony-Flutter —— 生态库适配清单与开发规范
- 官方示例与开发指南:openharmony-tpc/flutter_samples —— 环境搭建指导、FAQ 与示例工程
- 鸿蒙化适配仓库:oh-flutter/onesignal_flutter —— 本文 onesignal_flutter 的 OpenHarmony 适配版本
环境搭好之后(还没搭的看这篇 Flutter 鸿蒙环境搭建指南),下一步就是给项目引入三方库。
本文以 onesignal_flutter(推送通知插件)为例,完整演示从fork 原仓库 → 编写 ArkTS 原生插件 → flutter create 生成宿主 → 构建 → 真机验证的全流程。这是一个9 个 MethodChannel、50+ 方法的复杂插件,比 in_app_update 的单 Channel 场景更具代表性。
OneSignal 是海外主流的免费推送通知服务(类似国内的极光推送、个推),提供推送通知、应用内消息、用户标签管理、会话追踪等能力。鸿蒙上没有 OneSignal SDK,必须用 ArkTS 原生桩实现来模拟全部接口。
一、插件分析:onesignal_flutter 做了什么
1.1 基本信息
onesignal_flutter 是 OneSignal 官方 Flutter SDK,Android 和 iOS 侧分别对接各自的 OneSignal Native SDK。
| 项目 | 内容 |
|---|---|
| 库名 | onesignal_flutter |
| 适配基于版本 | 5.6.10(上游 pub.dev 最新 5.7.1,适配仓库基于 5.6.10 fork) |
| 功能 | 推送通知、应用内消息、用户标签、会话追踪 |
| 主要 API | initialize()、login()、addTags()、consentGiven()、Notifications.addClickListener() 等 |
| 原生依赖 | Android: OneSignal Android SDK;iOS: OneSignal iOS SDK |
1.2 为什么这个库必须写原生代码
onesignal_flutter 的 Dart 层完全通过 9 个 MethodChannel 调用原生能力:
// lib/onesignal_flutter.dart(原插件源码) static MethodChannel _channel = const MethodChannel('OneSignal'); static Future<void> initialize(String appId) async { await _channel.invokeMethod('OneSignal#initialize', {'appId': appId}); await Future.wait([ InAppMessages.lifecycleInit(), User.lifecycleInit(), User.pushSubscription.lifecycleInit(), Notifications.lifecycleInit(), ]); }// lib/src/notifications.dart MethodChannel _channel = const MethodChannel('OneSignal#notifications'); Future<void> lifecycleInit() async { _permission = await _channel.invokeMethod("OneSignal#permission"); return await _channel.invokeMethod("OneSignal#lifecycleInit"); }// lib/src/pushsubscription.dart MethodChannel _channel = const MethodChannel('OneSignal#pushsubscription'); Future<void> lifecycleInit() async { _token = await _channel.invokeMethod("OneSignal#pushSubscriptionToken"); _id = await _channel.invokeMethod("OneSignal#pushSubscriptionId"); _optedIn = await _channel.invokeMethod("OneSignal#pushSubscriptionOptedIn"); return await _channel.invokeMethod("OneSignal#lifecycleInit"); }9 个 Channel 各自负责不同功能模块,Dart 侧只是一个"遥控器"——真正的推送注册、令牌获取、用户管理全部由原生侧完成。鸿蒙上没有 OneSignal SDK,必须用 hilog 日志桩模拟全部接口。
1.3 9 个 Channel 全景
| 序号 | Channel 名称 | 功能模块 | Dart 侧文件 |
|---|---|---|---|
| 1 | OneSignal | 主通道(初始化、登录、授权) | onesignal_flutter.dart |
| 2 | OneSignal#debug | 调试日志级别 | src/debug.dart |
| 3 | OneSignal#inappmessages | 应用内消息 | src/inappmessages.dart |
| 4 | OneSignal#liveactivities | 实时活动(iOS) | src/liveactivities.dart |
| 5 | OneSignal#pushsubscription | 推送订阅状态 | src/pushsubscription.dart |
| 6 | OneSignal#user | 用户管理 | src/user.dart |
| 7 | OneSignal#location | 位置共享 | src/location.dart |
| 8 | OneSignal#notifications | 通知管理 | src/notifications.dart |
| 9 | OneSignal#session | 会话追踪 | src/session.dart |
1.4 适配策略:fork 模式
和 in_app_update 一样,采用 fork 原仓库 + 添加 ohos 平台的方式:
- Fork 原仓库,保留全部原始文件(android/、ios/、lib/、test/ 等)不动
- 在 pubspec.yaml 中添加 ohos 平台声明
- 创建 ohos/ 目录作为 HAR 模块,编写 ArkTS 原生插件
- 用 flutter create --platforms ohos 生成标准 example 宿主工程
- 在生成的模板上添加插件注册代码
为什么不创建独立 OHOS 包?fork 模式的好处是原插件的 Dart 层代码(lib/)完全复用,Android/iOS 侧也不受影响,一个仓库同时支持三个平台。
二、适配流程:从 fork 到 ArkTS 原生插件
2.1 克隆原仓库
cd D:\Flutters git clone --depth 1 https://github.com/OneSignal/OneSignal-Flutter-SDK.git onesignal_flutter_ohos使用 --depth 1 浅克隆,因为原仓库历史较大。克隆后保留全部原始文件,在原有基础上添加 ohos 相关内容。
2.2 修改 pubspec.yaml:添加 ohos 平台声明
修改前:
name: onesignal_flutter version: 5.6.10 flutter: plugin: platforms: android: package: com.onesignal.flutter pluginClass: OneSignalPlugin ios: pluginClass: OneSignalPlugin修改后:
name: onesignal_flutter_ohos version: 5.6.10+ohos flutter: plugin: platforms: android: package: com.onesignal.flutter pluginClass: OneSignalPlugin ios: pluginClass: OneSignalPlugin ohos: pluginClass: OneSignalOhosPlugin三个关键改动:
- name 改为 onesignal_flutter_ohos(以 _ohos 结尾是社区约定)
- version 追加 +ohos 后缀
- ohos 平台声明 pluginClass: OneSignalOhosPlugin——这就是后面要写的 ArkTS 插件类名
2.3 创建 ohos/ HAR 模块
在项目根目录创建 ohos/ 目录,完整结构如下:
ohos/ ├── src/main/ets/components/plugin/ │ └── OneSignalOhosPlugin.ets ← ArkTS 原生插件核心实现(218 行) ├── src/main/module.json5 ← HAR 模块元信息 ├── index.ets ← HAR 模块入口 ├── oh-package.json5 ← HAR 依赖声明 ├── build-profile.json5 ← 构建配置 ├── hvigorfile.ts ← hvigor 构建脚本 └── BuildProfile.ets ← 构建变量oh-package.json5(HAR 依赖声明):
{ "name": "onesignal_flutter_ohos", "version": "1.0.0", "description": "HarmonyOS NEXT OneSignal plugin (hilog-based stub)", "main": "index.ets", "license": "MIT", "dependencies": { "@ohos/flutter_ohos": "file:../har" } }index.ets(HAR 入口,导出插件类):
import OneSignalOhosPlugin from './src/main/ets/components/plugin/OneSignalOhosPlugin'; export default OneSignalOhosPlugin;module.json5(HAR 模块元信息):
{ "module": { "name": "onesignal_flutter_ohos", "type": "har", "description": "OneSignal plugin for OpenHarmony", "deviceTypes": ["phone", "tablet", "2in1"] } }2.4 编写 ArkTS 原生插件(核心)
这是整个适配的核心文件 OneSignalOhosPlugin.ets。由于鸿蒙没有 OneSignal SDK,所有方法采用 hilog 日志桩实现——记录调用日志并返回安全默认值。
插件需要实现 FlutterPlugin 和 MethodCallHandler 两个接口,在 onAttachedToEngine 中创建全部 9 个 MethodChannel:
import { FlutterPlugin, FlutterPluginBinding, MethodCall, MethodCallHandler, MethodChannel, MethodResult, } from '@ohos/flutter_ohos'; import hilog from '@ohos.hilog'; const TAG = 'OneSignal'; // All 9 channel names matching Dart side const CHANNEL_MAIN = 'OneSignal'; const CHANNEL_DEBUG = 'OneSignal#debug'; const CHANNEL_INAPPMESSAGES = 'OneSignal#inappmessages'; const CHANNEL_LIVEACTIVITIES = 'OneSignal#liveactivities'; const CHANNEL_PUSHSUBSCRIPTION = 'OneSignal#pushsubscription'; const CHANNEL_USER = 'OneSignal#user'; const CHANNEL_LOCATION = 'OneSignal#location'; const CHANNEL_NOTIFICATIONS = 'OneSignal#notifications'; const CHANNEL_SESSION = 'OneSignal#session'; export default class OneSignalOhosPlugin implements FlutterPlugin, MethodCallHandler { private channels: Map<string, MethodChannel> = new Map(); private appId: string = ''; private loggedIn: boolean = false; private logLevel: number = 0; private consentRequired: boolean = false; private consentGiven: boolean = false; private paused: boolean = false; private triggers: Record<string, string> = {}; private tags: Record<string, string> = {}; private aliases: Record<string, string> = {}; getUniqueClassName(): string { return 'OneSignalOhosPlugin'; } onAttachedToEngine(binding: FlutterPluginBinding): void { const messenger = binding.getBinaryMessenger(); // Create all 9 channels const channelNames = [ CHANNEL_MAIN, CHANNEL_DEBUG, CHANNEL_INAPPMESSAGES, CHANNEL_LIVEACTIVITIES, CHANNEL_PUSHSUBSCRIPTION, CHANNEL_USER, CHANNEL_LOCATION, CHANNEL_NOTIFICATIONS, CHANNEL_SESSION ]; for (const name of channelNames) { const ch = new MethodChannel(messenger, name); ch.setMethodCallHandler(this); this.channels.set(name, ch); } hilog.info(0x0001, TAG, 'OneSignal OHOS plugin attached, %{public}d channels registered', channelNames.length); } onDetachedFromEngine(binding: FlutterPluginBinding): void { this.channels.forEach((ch) => { ch.setMethodCallHandler(null); }); this.channels.clear(); } onMethodCall(call: MethodCall, result: MethodResult): void { const method = call.method; hilog.debug(0x0001, TAG, 'onMethodCall: %{public}s', method); try { this.routeMethod(method, call, result); } catch (e) { hilog.error(0x0001, TAG, 'Error handling %{public}s: %{public}s', method, String(e)); result.success(null); } } }routeMethod 方法统一路由所有调用,按功能模块分组处理:
private routeMethod(method: string, call: MethodCall, result: MethodResult): void { // Main channel methods switch (method) { case 'OneSignal#initialize': this.appId = (call.argument('appId') as string) ?? ''; hilog.info(0x0001, TAG, 'Initialized with appId: %{public}s', this.appId); result.success(null); return; case 'OneSignal#login': this.loggedIn = true; hilog.info(0x0001, TAG, 'Login: %{public}s', call.argument('externalId') as string ?? ''); result.success(null); return; case 'OneSignal#loginWithJWT': this.loggedIn = true; result.success(null); return; case 'OneSignal#logout': this.loggedIn = false; result.success(null); return; case 'OneSignal#consentGiven': this.consentGiven = (call.argument('granted') as boolean) ?? false; result.success(null); return; case 'OneSignal#consentRequired': this.consentRequired = (call.argument('required') as boolean) ?? false; result.success(null); return; } // Debug channel methods if (method.startsWith('OneSignal#')) { const sub = method.substring('OneSignal#'.length); switch (sub) { case 'setLogLevel': this.logLevel = (call.argument('logLevel') as number) ?? 0; result.success(null); return; case 'getLogLevel': result.success({ 'console': this.logLevel, 'visual': this.logLevel }); return; // InAppMessages methods case 'addTrigger': case 'addTriggers': case 'removeTrigger': case 'removeTriggers': case 'clearTriggers': case 'paused': case 'arePaused': case 'lifecycleInit': if (sub === 'arePaused') { result.success(this.paused); return; } if (sub === 'paused') { this.paused = (call.args as boolean) ?? false; } result.success(null); return; // LiveActivities methods case 'enterLiveActivity': case 'exitLiveActivity': case 'setupDefault': case 'startDefault': result.success(null); return; // PushSubscription methods case 'pushSubscriptionToken': result.success(''); return; case 'pushSubscriptionId': result.success(''); return; case 'pushSubscriptionOptedIn': result.success(false); return; case 'getPermission': result.success({ 'status': 0, 'hasPermission': false }); return; case 'getSubscription': result.success({ 'id': '', 'token': '', 'enabled': false }); return; // User methods case 'addAlias': case 'addAliases': case 'removeAlias': case 'removeAliases': case 'addTag': case 'addTags': case 'removeTag': case 'removeTags': case 'getTags': result.success(this.tags); return; case 'addEmail': case 'removeEmail': case 'addSms': case 'removeSms': result.success(null); return; // Location methods case 'requestPermission': case 'isShared': case 'setShared': result.success(sub === 'isShared' ? false : null); return; // Notifications methods case 'permission': result.success(false); return; case 'permissionNative': result.success(false); return; case 'canRequest': result.success(true); return; case 'requestPermission': result.success(false); return; case 'clearAll': case 'removeNotification': case 'removeGroupedNotifications': result.success(null); return; case 'addForegroundLifecycleListener': case 'removeForegroundLifecycleListener': case 'addClickListener': case 'removeClickListener': case 'addWillDisplayListener': case 'removeWillDisplayListener': result.success(null); return; // Session methods case 'addOutcome': case 'addUniqueOutcome': case 'addOutcomes': result.success(null); return; } } // Default: not implemented but return success to avoid crashes hilog.warn(0x0001, TAG, 'Method not implemented: %{public}s', method); result.success(null); }2.5 关键设计决策解析
9 个 Channel 共用一个 Handler
和 in_app_update 的 1 个 MethodChannel + 1 个 EventChannel 不同,OneSignal 有 9 个 MethodChannel。ArkTS 侧用一个 OneSignalOhosPlugin 实例同时处理所有 Channel——所有 Channel 的 setMethodCallHandler 都指向 this,然后通过方法名前缀 OneSignal# 统一路由。
Channel 名称保持原样
const CHANNEL_MAIN = 'OneSignal'; const CHANNEL_NOTIFICATIONS = 'OneSignal#notifications'; const CHANNEL_PUSHSUBSCRIPTION = 'OneSignal#pushsubscription'; // ... 共 9 个所有 Channel 名称与原插件完全一致。这意味着 Dart 侧的 lib/ 目录下所有源码零改动——原插件的 Dart 代码不需要任何修改就能和 ArkTS 原生侧通信。
hilog 日志桩而非真实 SDK
鸿蒙上没有 OneSignal SDK,所以所有方法采用 hilog 日志桩实现:记录调用参数,返回安全默认值。这不是偷懒——对于插件适配验证来说,确保 MethodChannel 通信链路畅通是第一步,后续可以逐步替换为真实的推送能力(如鸿蒙的 pushKit)。
返回值类型必须严格匹配 Dart 侧
这是本次适配中最关键的发现。Dart 侧的变量类型决定了 ArkTS 侧必须返回对应类型:
// notifications.dart bool _permission = false; // 非空 bool _permission = await _channel.invokeMethod("OneSignal#permission");// pushsubscription.dart String? _token; // 可空 String _token = await _channel.invokeMethod("OneSignal#pushSubscriptionToken");如果 ArkTS 侧 permission 返回 0(int)而不是 false(bool),Dart 侧类型转换直接崩溃。同理 pushSubscriptionToken 返回 null 或空字符串都可以(因为 String? 可空),但 permission 必须返回 bool。
三、example 宿主工程:flutter create 生成标准模板
3.1 生成 ohos 宿主工程
原仓库的 example 目录是 examples/demo/(OneSignal 有多个示例应用)。在该目录下执行:
cd D:\Flutters\onesignal_flutter_ohos\examples\demo flutter create --platforms=ohos .这条命令会在 examples/demo/ 下生成 ohos/ 目录(约 39 个文件),包含标准的鸿蒙应用模板。
注意路径差异:OneSignal 原仓库使用 examples/demo/ 而不是常见的 example/,所以生成的宿主工程在 examples/demo/ohos/,比一般插件多一层目录。
3.2 修改 examples/demo/pubspec.yaml
原 demo 应用依赖了大量第三方插件(shared_preferences、provider、flutter_dotenv、http、url_launcher 等),这些插件均未适配 OHOS。直接用会导致启动崩溃。
将 pubspec.yaml 精简为最小依赖:
name: demo description: OneSignal OHOS Test App publish_to: 'none' version: 1.0.0+1 environment: sdk: ^3.7.0 dependencies: flutter: sdk: flutter onesignal_flutter_ohos: path: ../../ dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true同时替换 lib/main.dart 为最小化测试页面(仅依赖 flutter + onesignal_flutter_ohos),避免未适配插件导致启动崩溃。
3.3 修改 import 路径
// 原来: import 'package:onesignal_flutter/onesignal_flutter.dart'; // 改为: import 'package:onesignal_flutter_ohos/onesignal_flutter.dart';lib/ 目录下所有 Dart 文件的内部 import 也需要从 package:onesignal_flutter/ 改为 package:onesignal_flutter_ohos/。
3.4 补全 deviceTypes 和权限
// examples/demo/ohos/entry/src/main/module.json5 { "module": { "name": "entry", "type": "entry", "deviceTypes": ["phone", "tablet", "2in1"], "requestPermissions": [ {"name": "ohos.permission.INTERNET"} ] } }3.5 添加插件注册代码
// examples/demo/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets import { FlutterEngine, Log } from '@ohos/flutter_ohos'; import OneSignalOhosPlugin from 'onesignal_flutter_ohos'; const TAG = "GeneratedPluginRegistrant"; export class GeneratedPluginRegistrant { static registerWith(flutterEngine: FlutterEngine) { try { flutterEngine.getPlugins()?.add(new OneSignalOhosPlugin()); } catch (e) { Log.e(TAG, "Tried to register plugins with FlutterEngine (" + flutterEngine + ") failed."); Log.e(TAG, "Received exception while registering", e); } } }3.6 entry 模块依赖 HAR
// examples/demo/ohos/entry/oh-package.json5 { "name": "entry", "version": "1.0.0", "dependencies": { "onesignal_flutter_ohos": "file:../../../../ohos" } }注意路径深度:由于 demo 在 examples/demo/ 下,需要四级 …/ 才能回到项目根目录的 ohos/。
四、构建与真机验证
4.1 DevEco Studio 配置调试签名
- DevEco Studio → 文件 → 打开 → 选择 examples/demo/ohos 目录
- 文件 → 项目结构 → 签名配置 → 勾选自动生成签名
- 登录华为账号,证书自动填充,点确定
4.2 执行 flutter pub get
cd D:\Flutters\onesignal_flutter_ohos\examples\demo flutter pub get4.3 构建运行
在 DevEco Studio 中点运行按钮,或命令行构建:
flutter build hap --release编译输出(Dart + ArkTS 全部通过,仅命令行签名失败):
> hvigor Finished :entry:default@FlutterTask... after 16 s 768 ms > hvigor Finished :entry:default@CompileResource... after 355 ms > hvigor Finished :entry:default@BuildJS... after 5 ms4.4 真机效果验证
测试页面提供四个按钮,覆盖 OneSignal 的核心功能:
| 按钮 | 功能 | 调用方法 |
|---|---|---|
| 初始化 | SDK 初始化 + lifecycleInit | OneSignal.initialize() |
| 登录 | 用户登录 | OneSignal.login() |
| 设置标签 | 用户标签管理 | OneSignal.User.addTags() |
| 同意授权 | 隐私授权 | OneSignal.consentGiven() |
验证状态汇总:
| 验证项 | 状态 | 说明 |
|---|---|---|
| 依赖解析(flutter pub get) | 通过 | 依赖正常解析 |
| Dart 编译 | 通过 | 无编译错误 |
| ArkTS 编译 | 通过 | 218 行原生插件编译成功 |
| 真机运行 | 通过 | UI 正常显示,四个按钮可点击 |
| 9 个 Channel 注册 | 通过 | 全部 Channel 正常创建 |
| initialize + lifecycleInit | 通过 | 初始化链路完整执行 |
| Dart ↔ ArkTS 类型匹配 | 通过 | bool/int/string 返回值类型正确 |
五、常见问题 FAQ
Q1:OneSignal 原仓库有 10 个 Java 文件和 9 个 Channel,ArkTS 侧怎么对应?
ArkTS 侧用一个插件类处理全部 9 个 Channel。关键是在 onAttachedToEngine 中循环创建所有 Channel,并统一指向同一个 Handler:
// ohos/src/main/ets/components/plugin/OneSignalOhosPlugin.ets onAttachedToEngine(binding: FlutterPluginBinding): void { const messenger = binding.getBinaryMessenger(); const channelNames = [ CHANNEL_MAIN, CHANNEL_DEBUG, CHANNEL_INAPPMESSAGES, CHANNEL_LIVEACTIVITIES, CHANNEL_PUSHSUBSCRIPTION, CHANNEL_USER, CHANNEL_LOCATION, CHANNEL_NOTIFICATIONS, CHANNEL_SESSION ]; for (const name of channelNames) { const ch = new MethodChannel(messenger, name); ch.setMethodCallHandler(this); // 所有 Channel 共用一个 Handler this.channels.set(name, ch); } }然后在 routeMethod 中通过方法名前缀统一路由。这比创建 9 个独立 Handler 简洁得多。
Q2:鸿蒙没有 OneSignal SDK,推送功能能用吗?
当前实现是 hilog 日志桩——所有方法调用会被记录到系统日志,并返回安全默认值。推送注册、令牌获取等实际功能需要后续对接鸿蒙的 pushKit(华为推送服务)来真正实现。
适配验证阶段的重点是确保 MethodChannel 通信链路畅通、Dart 侧调用不会崩溃。功能实现可以逐步迭代。
Q3:初始化时遇到类型转换崩溃怎么办?
OneSignal 的 initialize() 会级联调用 4 个 lifecycleInit(),每个 lifecycleInit 都会通过 MethodChannel 获取返回值。Dart 侧对返回值类型有严格要求:
// notifications.dart — bool 类型,不能返回 int bool _permission = false; _permission = await _channel.invokeMethod("OneSignal#permission"); // pushsubscription.dart — String? 类型,可空 String? _token; _token = await _channel.invokeMethod("OneSignal#pushSubscriptionToken");ArkTS 侧必须返回完全匹配的类型:permission 返回 false(bool)而非 0(int),pushSubscriptionToken 返回 ‘’(string)而非 null。类型不匹配会直接导致 Dart 侧类型转换异常,表现为初始化失败或应用崩溃。
Q4:原 demo 应用依赖了 shared_preferences 等未适配插件,怎么处理?
OneSignal 原仓库的 examples/demo 依赖了 shared_preferences、provider、flutter_dotenv、http 等第三方插件,这些插件均未适配 OHOS。直接运行会因 MissingPluginException 崩溃。
解决方案是精简 pubspec.yaml,只保留 flutter SDK 和 onesignal_flutter_ohos,同时将 main.dart 替换为最小化测试页面。这样可以在不依赖任何未适配插件的情况下验证核心功能。
六、总结
本文完整记录了 onesignal_flutter 插件从 fork 到鸿蒙 PC 真机验证的全流程:采用 fork 模式保留原仓库全部文件,用 flutter create --platforms ohos 生成标准宿主工程模板,编写 218 行 ArkTS 原生插件代码,在一个插件类中处理 9 个 MethodChannel 的 50+ 方法调用,通过 hilog 日志桩模拟 OneSignal SDK 的全部接口。Dart 侧源码零改动,所有 Channel 名称与原插件保持一致。
核心经验:多 Channel 插件的适配关键在于统一路由设计——一个 Handler 处理所有 Channel,通过方法名前缀分发,比创建多个独立 Handler 更简洁。返回值类型必须严格匹配 Dart 侧的变量类型,bool 不能返回 int,string 不能返回 null(非空类型时)。动手前建议先查一眼 Flutter OH 三方库适配列表,很多热门库已有人适配过,别重复造轮子。
参考资料
- CPF-Flutter 社区主页
- CPF-Flutter/flutter_flutter(Flutter-OH 主仓库)
- oh-flutter/onesignal_flutter(鸿蒙化适配仓库)
- Flutter OH 三方库适配列表
- OneSignal/OneSignal-Flutter-SDK 原库源码
- oh-flutter 社区适配参考
- Flutter 鸿蒙环境搭建指南
- HarmonyOS pushKit 推送服务文档
- Flutter OH 官方示例与 FAQ