ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

OpenHarmony下Flutter插件包创建与注册:以flutter_libphonenumber适配为例

OpenHarmony下Flutter插件包创建与注册:以flutter_libphonenumber适配为例 做 Flutter 的人这两年肯定绕不开 OpenHarmony 这个话题。随着鸿蒙生态越来越完整很多公司开始要求把现有 Flutter 应用迁移到 OpenHarmony 设备上跑。迁移本身不难最烦的是三方库适配。我这段时间正好把 flutter_libphonenumber 这个库在 OpenHarmony 上完整走了一遍核心工作就是鸿蒙平台插件包的创建与注册。整个过程比想象中麻烦但思路理清楚之后几乎所有纯 Dart 库都能用同一套方案搞定。flutter_libphonenumber 是一个手机号解析库负责号码校验、格式化、时区和运营商信息解析这些事。它看起来是纯 Dart 实现但真正用起来会发现它内部通过 libphonenumber_platform 这个接口层对接不同的平台后端默认会尝试调用 Android/iOS 的原生能力。到了 OpenHarmony 上如果不做适配运行时会直接报“MissingPluginException”或者压根拿不到正确的解析结果。这篇文章我就把这个适配过程完整拆一遍把鸿蒙插件包的目录结构、pubspec 配置、插件注册表机制、验证流程和踩坑记录都写清楚给后面要适配其他 Flutter 库的人一个可以照抄的模板。1. 项目背景与适配思路1.1 flutter_libphonenumber 到底是怎么设计的先把这个库的设计看清楚否则适配无从下手。flutter_libphonenumber 的架构并不是一个铁板一块的整体而是分了三层外层 API暴露PhoneNumberUtil这类给业务方用的类提供parse、format、isValidNumber等方法。中间接口层libphonenumber_platform定义了一个抽象基类PhoneNumberPlatform所有实际解析能力都通过这个接口去调用。具体实现层接口层只定义能力不干活。真正干活的是libphonenumber_plugin走 Android 原生 libphonenumber Java API / iOS libphonenumber-iOS 库或者libphonenumber_runtime纯 Dart 移植。这种设计很常见Flutter 官方很多插件也这么搞比如camera_platform_interface、shared_preferences_platform_interface。好处是方便在不同平台切换实现坏处是每新增一个平台就得额外提供一个对应的实现包。到了 OpenHarmony 上问题就来了。OpenHarmony 没有 Android 的 libphonenumber Java 库也没有 iOS 的 libphonenumber-iOS 库。默认情况下flutter_libphonenumber 在鸿蒙环境上找不到任何可用的后端实现功能就直接瘫痪。1.2 纯 Dart 库为什么也需要“适配”有人会问libphonenumber_runtime不是纯 Dart 吗纯 Dart 库难道不是跨平台直接跑理论上是的但实际工程里有个容易被忽略的细节flutter_libphonenumber 并不会自动选择后端实现。它需要业务方在代码里明确指定PhoneNumberPlatform.instance指向哪一个后端。也就是说就算libphonenumber_runtime在 OpenHarmony 上能跑你也得在项目初始化时手动注册它。每个项目都写这么一段平台判断逻辑代码会越来越散后面多个库都这么做维护成本直线上升。更规范的思路是单独创建一个鸿蒙插件包把“为 OpenHarmony 选择并注册后端实现”这件事封装在插件包内部。业务侧只需要像引入普通插件一样把这个包加到 dependencies 里不用关心底层用的是 runtime 还是原生桥接。这才符合标题里说的“鸿蒙平台插件包的创建与注册”——插件包负责兼容注册负责生效。1.3 两条路线怎么选我当时面临两个选择方案做法优点缺点方案 A直接用 runtime 后端在业务工程的 main.dart 里设置PhoneNumberPlatform.instance LibPhoneNumberPlatform()改动最小10 分钟跑通每个接入项目都要改代码不规范方案 B创建鸿蒙插件包新建一个flutter_libphonenumber_ohos插件包在包内部完成后端绑定业务工程只加依赖接入方零改动适配能力可复用其他库可套用同一模板需要理解插件包工程结构首次搭建成本高我自己选了方案 B。原因很简单公司不止一个 App 要迁移到 OpenHarmony后面还有一堆类似的三方库要适配沉淀出一套“鸿蒙插件包模板”比每次都改业务代码要值得多。2. 环境准备与基础工程搭建2.1 OpenHarmony Flutter 开发环境的硬性要求在动手创建插件包之前得先把环境搞对。OpenHarmony 的 Flutter 开发和普通 Android Flutter 开发不太一样最大的区别是 Flutter SDK 要用 OpenHarmony SIG 维护的 fork 版本不能用官方版本直接编译 HAP 包。我这边最终稳定的搭配是OpenHarmony SDK 5.0 Release对应 API 12DevEco Studio 5.0 及以上用来管理和编译 ohos 模块Flutter SDK 使用 OpenHarmony-SIG 的 flutter_flutter 分支版本号要对齐鸿蒙 SDKJDK 17hvigor 构建强制要求JDK 8 会直接报错VS Code 可选装好 Flutter 和 Dart 插件主要用来写 Dart 层代码很多人会在这里卡住明明配置了 Flutter SDK 路径VS Code 里跑flutter doctor还是报错。尤其 Windows 上常见的一条报错是 “unable to find suitable visual studio toolc”。这个不是 Flutter 本身的问题而是 Flutter 检查 Visual Studio C 工具链失败。不用慌OpenHarmony 的 Flutter 构建根本不依赖 MSVC直接跳过这条告警继续用就行真遇到的编译失败大多不是这个原因。2.2 拉取 Flutter SDK 与环境变量配置SDK 用 git 拉取的时候注意分支选择。以我的环境为例OpenHarmony 5.0 对应的是 oh-3.7 系列分支拉完之后把bin目录加进PATHgit clone -b oh-3.7.12 https://gitee.com/openharmony-sig/flutter_flutter.git export PATH$PATH:$PWD/flutter_flutter/bin注意别用太新的 Flutter branchOpenHarmony 官方插件包的适配进度通常滞后于上游。选太新的版本很有可能编译到最后发现某些 engine 接口对不上。环境变量方面除了ANDROID_HOME某些插件仍会检查还要额外设置 OpenHarmony 的 SDK 路径DevEco Studio 里配置好之后会写入local.properties也可以手动指定sdk.dir/path/to/ohos-sdk hwsdk.dir/path/to/ohos-sdk这一步很关键。很多人创建插件包之后编译报 “Unable to find OpenHarmony SDK”就是sdk.dir没配或者配成了 Android SDK 的路径。2.3 验证环境创建最小 Flutter 工程跑一次 HAP正式适配之前强烈建议先创建一个全新的 Flutter 测试工程在 OpenHarmony 模拟器或真机上跑通一次空应用。这一步能排除一堆环境变量、版本匹配问题避免后面把问题全混在一起。我当时的跑通命令是flutter create test_ohos cd test_ohos flutter build hap --debug如果这一步能顺利产出 HAP 包说明 Flutter SDK、OpenHarmony SDK、JDK 这三大件基本没有大问题了。如果这里就报错先解决环境再回来做插件适配。我见过太多人没做这一步最后插件和环境的报错混在一起排查了一整天。3. 鸿蒙平台插件包的创建3.1 插件包目录结构到底长什么样OpenHarmony 的 Flutter 插件包结构上很像“Android 插件 鸿蒙原生模块”的合体。我要创建的flutter_libphonenumber_ohos完整结构如下flutter_libphonenumber_ohos/ ├── pubspec.yaml # 插件包 Dart 侧声明 ├── lib/ │ └── flutter_libphonenumber_ohos.dart ├── ohos/ │ ├── build-profile.json5 # ohos 模块构建配置 │ ├── hvigorfile.ts # hvigor 构建入口 │ ├── oh_modules/ # ohos 依赖模块 │ └── entry/ │ └── src/main/ │ ├── module.json5 # 鸿蒙模块配置 │ ├── ets/ │ │ └── plugin/ │ │ └── LibPhoneNumberOhosPlugin.ets │ └── resources/ └── example/ # 可选插件的示例工程这个结构并不需要完全从零手写可以先用 DevEco Studio 创建一个标准 ohos library 模块再把 Flutter 插件包的pubspec.yaml和lib/目录加进去组合成一个完整的 Flutter 插件包。也可以直接把 Android 插件包的目录复制过来手动加一个ohos/模块目录。记住一个原则lib/是给 Dart 侧用的ohos/是给 OpenHarmony 原生侧用的pubspec.yaml是连接两者的桥梁。3.2 pubspec.yaml 里的插件声明是核心Flutter 工具链判断一个包是不是插件包关键看pubspec.yaml里有没有flutter.plugin这一段。我的写法name: flutter_libphonenumber_ohos description: OpenHarmony platform implementation for flutter_libphonenumber. version: 0.1.0 publish_to: none environment: sdk: 3.0.0 4.0.0 dependencies: flutter: sdk: flutter flutter_libphonenumber: ^7.0.0 libphonenumber_platform: ^0.4.0 dev_dependencies: flutter_test: sdk: flutter flutter: plugin: platforms: ohos: package: com.example.flutter_libphonenumber_ohos pluginClass: LibPhoneNumberOhosPlugin dartPluginClass: LibPhoneNumberOhos注意几个容易踩坑的字段publish_to: none表示这是一个本地包不发布到 pub.dev。pluginClass是鸿蒙原生侧注册用的类名要和ets/plugin/目录下的类完全一致。dartPluginClass是 Dart 侧插件类用于在 Dart 层完成逻辑OpenHarmony 支持这种“dart plugin class”后缀定义之后 Flutter 工具链会调用它的registerWith()方法。这个dartPluginClass是 OpenHarmony 适配里比 Android 多出来的东西也是整个适配中最重要的注册点。它让我在没有原生 Java/Kotlin 代码的情况下也能完成插件注册。3.3 ohos 模块的配置细节ohos/build-profile.json5定义了鸿蒙模块的构建参数一个能过编译的最小配置长这样{ app: { signingConfigs: [], products: [ { name: default, signingConfig: default, compatibleSdkVersion: 5.0.0(12), runtimeOS: OpenHarmony, buildOption: { strictMode: { caseSensitiveCheck: true } } } ] }, modules: [ { name: libphonenumber_ohos, srcPath: ./, targets: [ { name: default, applyToProducts: [default] } ] } ] }module.json5里的配置同样容易踩坑重点要声明这个模块是 library 而不是 entry{ module: { name: libphonenumber_ohos, type: har, deviceTypes: [phone, tablet], deliveryWithInstall: true, installationFree: false } }type必须是har如果写成entry后面集成到业务工程时会和主工程的 entry 模块冲突直接编译失败。一个真实的教训我最初照着网上模板写成了type: sharedDevEco 里看起来一切正常但flutter build hap的时候业务工程死活识别不到插件最后排查半天发现是模块类型导致插件没有被链接进主 HAP。所以记住Flutter 插件在 OpenHarmony 上基本对应 HAR 模块静态共享库不是动态共享库。4. 注册机制与平台实现绑定4.1 Flutter 插件注册表从入口到生效的过程理解注册机制才算真正看懂插件包。在 OpenHarmony 上Flutter 插件注册分为两个层面第一层是 Dart 侧的dartPluginClass。Flutter 工具链在编译时扫描pubspec.yaml生成GeneratedPluginRegistrant注册表把插件包列表加进去。当 App 启动时Flutter engine 会调用每个插件的registerWith()完成 Dart 侧初始化。第二层是鸿蒙原生侧的pluginClass。GeneratedPluginRegistrant通过反射或者显式调用把原生侧实例注册到 Flutter engine 的 method channel 分发器上。在 OpenHarmony 里由于dartPluginClass的被支持我可以把大部分工作放到 Dart 层完成原生侧甚至可以不写 ArkTS 代码。这也算是一个合理的产品型做法毕竟 OpenHarmony 上原生 Channel 通信能力还在完善中能放 Dart 层就尽量不放原生层。4.2 在业务工程里注册插件包插件包创建好之后要在业务工程里注册。这个方法其实很笨但如果漏掉一切白搭。在业务工程的pubspec.yaml里dependencies: flutter: sdk: flutter flutter_libphonenumber: ^7.0.0 flutter_libphonenumber_ohos: path: ../flutter_libphonenumber_ohos这里用path依赖本地路径是开发期最方便的方式。改完记得执行flutter pub get。执行完pub get之后可以检查.dart_tool目录下的package_config.json确认flutter_libphonenumber_ohos已经被正确识别。如果这个文件里没有那证明pubspec.yaml里的flutter.plugin.platforms.ohos配置有问题flutter这个键拼接不正确工具链就没把它当插件处理。4.3 在 Dart 层绑定 Platform 实现现在到了整个适配最关键的一步把libphonenumber_platform的接口实例绑定到我们的实现上。在插件包的 Dart 文件lib/flutter_libphonenumber_ohos.dart里import package:flutter_libphonenumber/flutter_libphonenumber.dart; import package:libphonenumber_platform/libphonenumber_platform.dart; import package:libphonenumber_runtime/libphonenumber_runtime.dart; class LibPhoneNumberOhos extends PhoneNumberPlatform { final LibPhoneNumberPlatform _delegate LibPhoneNumberPlatform(); /// 注册入口Flutter toolchain 会自动调用该方法 static void registerWith() { if (PhoneNumberPlatform.instance is! LibPhoneNumberOhos) { PhoneNumberPlatform.instance LibPhoneNumberOhos(); } } override FuturePhoneNumber parsePhoneNumber({ required String phoneNumber, String? region, bool ignoreType false, }) { return _delegate.parsePhoneNumber( phoneNumber: phoneNumber, region: region, ignoreType: ignoreType, ); } override FutureString formatPhoneNumber({ required String phoneNumber, required String region, }) { return _delegate.formatPhoneNumber( phoneNumber: phoneNumber, region: region, ); } // 其余方法全部代理给 _delegate }这里有一个细节特别值得说registerWith()方法是 OpenHarmony Flutter 插件模板约定的静态方法Flutter 工具链会根据dartPluginClass找到这个类在启动时自动调registerWith()。我在这里做了一次“幂等判断”防止在测试环境里被多次注册导致重复实例。业务侧完全不用感知这件事。它只需要正常使用PhoneNumberUtilfinal util PhoneNumberUtil(); final parsed await util.parse(8613812345678); print(parsed.countryCode); // 86底层是 runtime 实现还是原生桥接业务侧一律不需要关心。这就是插件包封装的价值。5. 编译运行与验证流程5.1 构建 HAP 包的命令与流程插件包和业务工程的依赖关系配好之后回到业务工程执行构建flutter pub get flutter build hap --debug第一次构建通常会比较慢因为要同时编译 Flutter engine、Dart 代码和鸿蒙侧的 HAR 模块。构建成功后产物路径类似build/ohos/entry/build/outputs/default/entry-default-unsigned.hap另外这个过程中大概率会遇到hvigor相关错误。hvigor 是 OpenHarmony 的构建引擎它对 JDK 版本敏感JDK 17 是必须的。我最初用的 JDK 11报错信息极其误导“Could not find or load main class”。这个报错会让人以为是代码问题实际上是 JDK 版本不匹配。5.2 Demo 验证解析、格式化、有效性判断构建产物有了之后我用一个极简页面做验证。因为主要是验证底层逻辑UI 层没有放太多东西import package:flutter/material.dart; import package:flutter_libphonenumber/flutter_libphonenumber.dart; class VerifyPage extends StatefulWidget { override StateVerifyPage createState() _VerifyPageState(); } class _VerifyPageState extends StateVerifyPage { String _result ; override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _verify, child: const Text(开始验证), ), const SizedBox(height: 24), Text(_result), ], ), ), ); } Futurevoid _verify() async { final util PhoneNumberUtil(); final parsed await util.parse(8613812345678); final formatted await util.format(parsed, PhoneNumberFormat.INTERNATIONAL); final isValid await util.isValidNumber(8612345678901); setState(() { _result countryCode${parsed.countryCode}\n formatted$formatted\n isValid$isValid; }); } }预期输出是countryCode86 formatted86 138 1234 5678 isValidtrue如果这三项都能正常输出说明 flutter_libphonenumber 在 OpenHarmony 上的核心链路已经跑通了。5.3 日志查看与调试技巧OpenHarmony 上的日志系统和 Android 不太一样不能用adb logcat拿到 Flutter 的 stdout。正确的姿势是用hiloghilog | grep flutterFlutter 引擎在 OpenHarmony 上的日志 tag 通常带flutter前缀Dart 侧的print也会通过这个通道打出来。如果发现日志刷得太快可以加过滤hilog -x | grep -E flutter|libphonenumber还有一个经验Debug 包下 Flutter engine 的日志比较全Release 包会裁剪很多调试信息。真机验证建议先用 Debug确认功能没问题之后再出 Release。6. 常见问题与排查技巧实录6.1 插件未找到MissingPluginException这是所有适配工作里最典型的报错现象是运行时抛出异常提示找不到指定 Channel 的实现。绝大多数原因都是pubspec.yaml的flutter.plugin.platforms.ohos配置不对或者插件包的ohos/目录不存在。排查步骤我总结成一个流程检查pubspec.yaml的platforms是否包含ohos。检查插件包中是否存在ohos/目录以及module.json5的type是否为har。检查业务工程.dart_tool/package_config.json中该插件是否被识别。检查生成的GeneratedPluginRegistrant中是否包含该插件的注册代码。清理再构建flutter clean flutter pub get flutter build hap --debug这五步走完90% 的 MissingPluginException 都能解决。6.2 编译报错与依赖冲突OpenHarmony 的 Flutter 生态还比较年轻第三方库的版本兼容问题比 Android 上明显得多。我遇到的一个典型问题flutter_libphonenumber 最新版依赖的plugin_platform_interface版本和 OpenHarmony Flutter SDK 内置的版本冲突编译时提示无法满足约束。这种问题没有银弹只能锁定兼容版本。我当时在插件包的pubspec.yaml里强制指定了plugin_platform_interface的版本范围不要一味追新dependency_overrides: plugin_platform_interface: 2.1.8如果你在适配别的库这个思路通用优先看 flutter_libphonenumber 的老版本搭配什么依赖就用什么依赖别用最新。6.3 真机调试与渲染异常处理在真机上调试 OpenHarmony 应用时我遇到过界面渲染滞后、画面撕裂的问题。这多半不是插件适配的问题而是 GPU 硬件加速和 Flutter 引擎的兼容问题。遇到这种情况可以尝试在AndroidManifest.xml对应的鸿蒙版本配置里关闭硬件加速或者在 Flutter 启动时加软件渲染参数// 临时方案强制软件渲染排查渲染异常 // 实际做法是在启动参数或鸿蒙侧配置中调整但我不建议一上来就关闭硬件加速因为软件渲染性能损耗很大只适合定位问题。确认是硬件加速问题之后再考虑升级 OpenHarmony SDK 版本或者换一个设备验证。最后再说一个比较隐蔽的坑OpenHarmony 的 Flutter 包管理对flutter命令和 hvigor 的版本有绑定要求。如果你用 fvm 管理多个 Flutter 版本非常容易在切换版本之后出现“生成了错误的注册表”这种问题。根据我自己的经验最好在适配期间固定一个 fvm 版本所有工程和命令都通过fvm flutter调用避免系统 PATH 里的 Flutter 和 fvm 指定的版本不一致带来各种莫名其妙的问题。
返回列表