ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter 三方库 flutter_native_timezone 的 OpenHarmony 适配实战(0-1)

Flutter 三方库 flutter_native_timezone 的 OpenHarmony 适配实战(0-1) Flutter 三方库 flutter_native_timezone 的 OpenHarmony 适配实战0-1本文记录了将开源 Flutter 三方库flutter_native_timezone从零适配到 OpenHarmony / HarmonyOS 平台的完整过程包含上游代码基线替换、OHOS 平台脚手架生成、ArkTS 原生实现、构建验证、真机运行与踩坑复盘。一、背景1.1 三方库简介flutter_native_timezone 是一个 Flutter 社区广泛使用的设备时区获取插件提供以下能力getLocalTimezone()— 获取设备当前设置的本地时区IANA 格式如Asia/ShanghaigetAvailableTimezones()— 获取系统支持的可用时区 ID 列表同样为 IANA 格式该三方库最初支持 Android / iOS / macOS / Web 四个平台本次任务将其适配到OpenHarmony / HarmonyOS平台。项目地址oh-flutter/flutter_native_timezone1.2 适配目标维度要求功能一致性getLocalTimezone/getAvailableTimezones返回 IANA 时区 ID语义与 Android / iOS 完全一致Dart 层零改动Dart API 与方法通道契约保持不变仅新增 OHOS 平台实现工程规范遵循 OHOS 插件工程规范SDK / 版本信息动态读取、不硬编码中英文 README 同步交付质量完成一致性代码检查与真机验证输出 0-1 适配过程复盘二、适配路线图整个适配分为 5 个阶段第 1 阶段项目初始化 ── 以上游 pinkfish master 为干净基线生成 ohos 平台脚手架 第 2 阶段原生实现 ── 对照 Android/iOS 实现用 ArkTS 完成 MethodChannel 方法 第 3 阶段三方库注册 ── pubspec.yaml 注册 ohos 平台 Dart SDK 约束升级 第 4 阶段示例验证 ── 生成 example/ohos 宿主工程并构建签名 HAP 第 5 阶段真机验证 ── 安装运行、代码一致性检查、双语文档输出三、逐步适配过程第 1 阶段项目初始化本仓库此前的内容混杂了旧分支的历史与一次早期适配为获得干净的 0-1 起点先以官方上游pinkfish/flutter_native_timezonemasterpubspec 2.0.1为基线重置gitremoteaddupstream https://github.com/pinkfish/flutter_native_timezone.gitgitfetch upstream mastergitreset--hardupstream/master# main 指向 fcc1f3f随后使用 Flutter OHOS 工具链生成 OHOS 插件模板flutter create.--templateplugin--platformsohos--orgcom.whelksoft --no-pub踩坑仓库同时存在com.whelksoft插件与com.example示例两个组织名时flutter create会报Ambiguous organization必须显式传入--org。此外flutter create会按当前工具链的现代模板追加一批非目标文件Gradle kts、federated 骨架、iOS Swift 副本等需逐一甄别删除只保留ohos/、example/ohos/及必要的 pubspec 变更。该命令自动生成ohos/目录的标准模板结构ohos/ ├── index.ets # 模块入口导出插件类 ├── oh-package.json5 # HAR 包配置 ├── build-profile.json5 # 构建配置 ├── local.properties # 本地 SDK/Flutter 路径不入库 ├── src/main/ │ ├── module.json5 # HAR 模块配置 │ └── ets/components/plugin/ │ └── FlutterNativeTimezonePlugin.ets # 原生插件实现核心关键配置文件index.ets入口导出importFlutterNativeTimezonePluginfrom./src/main/ets/components/plugin/FlutterNativeTimezonePlugin;exportdefaultFlutterNativeTimezonePlugin;oh-package.json5包配置{ name: flutter_native_timezone, version: 1.0.0, description: A flutter plugin for getting the local timezone of the device., main: index.ets, author: nutpi, license: Apache-2.0, dependencies: {} }注ohos/flutter_ohos由 Flutter OHOS 引擎在构建时通过har/flutter.har自动链接flutter_tools会把ohos/flutter_ohos覆写为引擎产物插件dependencies无需显式声明。module.json5HAR 模块配置{ module: { name: flutter_native_timezone, type: har, deviceTypes: [default, tablet] } }第 2 阶段原生实现核心本插件属于典型的方法调用型插件Dart 通过MethodChannel主动调用原生无需事件流、无需AbilityAware。2.1 架构与通道契约平台插件类通道名编解码AndroidFlutterNativeTimezonePlugin : FlutterPlugin, MethodCallHandlerflutter_native_timezoneStandardMethodCodeciOSFlutterNativeTimezonePluginObjCflutter_native_timezoneStandardMethodCodecOHOSFlutterNativeTimezonePlugin : FlutterPlugin, MethodCallHandlerflutter_native_timezone默认 StandardMethodCodec契约通道名与方法名必须与 Dart 层完全一致否则调用会落到notImplemented()。2.2 方法实现对照方法AndroidiOSOHOSgetLocalTimezoneZoneId.systemDefault().idNSTimeZone.localTimeZone.namei18n.getTimeZone().getID()getAvailableTimezonesZoneId.getAvailableZoneIds()NSTimeZone.knownTimeZoneNamesi18n.TimeZone.getAvailableIDs()OHOS 核心实现FlutterNativeTimezonePlugin.etsimport{FlutterPlugin,FlutterPluginBinding,MethodCall,MethodCallHandler,MethodChannel,MethodResult,}fromohos/flutter_ohos;import{i18n}fromkit.LocalizationKit;exportdefaultclassFlutterNativeTimezonePluginimplementsFlutterPlugin,MethodCallHandler{privatechannel:MethodChannel|nullnull;getUniqueClassName():string{returnFlutterNativeTimezonePlugin}onAttachedToEngine(binding:FlutterPluginBinding):void{this.channelnewMethodChannel(binding.getBinaryMessenger(),flutter_native_timezone);this.channel.setMethodCallHandler(this)}onDetachedFromEngine(binding:FlutterPluginBinding):void{if(this.channel!null){this.channel.setMethodCallHandler(null)this.channelnull}}onMethodCall(call:MethodCall,result:MethodResult):void{try{switch(call.method){casegetLocalTimezone:result.success(this.getLocalTimezone());break;casegetAvailableTimezones:result.success(this.getAvailableTimezones());break;default:result.notImplemented();}}catch(err){result.error(Error,Failed to handle method${call.method}:${(errasError).message},null);}}// 获取本地时区返回 IANA 时区 ID如 Asia/ShanghaiprivategetLocalTimezone():string{consttimezone:i18n.TimeZonei18n.getTimeZone();returntimezone.getID();}// 获取系统支持的可用 IANA 时区 ID 列表privategetAvailableTimezones():Arraystring{returni18n.TimeZone.getAvailableIDs();}}关键差异点OHOS 用ohos/flutter_ohos的FlutterPlugin/MethodCallHandler接口替换 Android 的io.flutter.*时区能力用kit.LocalizationKit的i18n模块——i18n.getTimeZone()无参调用即返回系统当前时区的TimeZone对象与 AndroidZoneId.systemDefault()、iOSlocalTimeZone语义一致i18n.TimeZone.getAvailableIDs()返回系统支持的 IANA 时区 ID 列表。i18n相关 API 自 API 9 提供、crossplatform语义返回 ID 与 Android/iOS 同源。2.3 选型决策i18n vs systemDateTime方案优点缺点i18n.getTimeZone().getID()i18n.TimeZone.getAvailableIDs()✅一个模块同时覆盖本地时区与可用列表两个方法getAvailableIDs是唯一能取全量列表的 APIID 为 IANA 语义依赖国际化模块Kits 级无额外权限systemDateTime.getTimezoneSync()同步取值、调用更直接只能取本地时区无法提供可用时区列表仍需第二个 API 来源最终选择kit.LocalizationKit的i18n方案保证两个方法语义与 Android 一致。第 3 阶段三方库注册原上游 pubspec 的 Dart SDK 约束为2.12.0 3.0.0Dart 2.x与本机 Dart 3.11.5 冲突需同步升级约束并在flutter.plugin.platforms中新增ohosenvironment:sdk:3.4.0 4.0.0flutter:3.22.0flutter:plugin:platforms:android:package:com.whelksoft.flutter_native_timezonepluginClass:FlutterNativeTimezonePluginios:pluginClass:FlutterNativeTimezonePluginmacos:pluginClass:FlutterNativeTimezonePluginweb:pluginClass:FlutterNativeTimezonePluginfileName:flutter_native_timezone_web.dartohos:# ← 新增pluginClass:FlutterNativeTimezonePlugin# ← 与 index.ets 默认导出、getUniqueClassName() 一致说明Flutter OHOS 引擎构建时会读取pubspec.yaml的ohos配置通过GeneratedPluginRegistrant自动加载ohos/index.ets导出的插件类pluginClass必须与 ArkTS 类名、getUniqueClassName()返回值三者完全一致。第 4 阶段示例应用创建与构建flutter create . --templateplugin会同步生成example/ohos/宿主工程AppScope、entry 模块、ohosTest 测试模块、hvigor 配置等。example 侧pubspec.yaml的 Dart SDK 约束同样需要升级。构建签名 HAP示例工程的签名配置由 DevEco Studio 自动签名注入本地调试证书cdexample flutter pub get flutter build hap--debug成功产出example/ohos/entry/build/default/outputs/default/entry-default-signed.hap踩坑flutter build hap首次会报「请通过 DevEco Studio 配置调试签名」——编译本身已通过assembleHap成功但 HAP 必须带签名。用 DevEco Studio 打开example/ohos开启自动签名后signingConfigs会写入本地~/.ohos/config的调试证书重跑即可产出 signed HAP。另外flutter pub get若命中pub.flutter-io.cn镜像网络异常可临时PUB_HOSTED_URLhttps://pub.dev重试。第 5 阶段真机验证与收尾一致性代码检查ohos-flutter-code-check逐接口比对 Android/iOS/macOS/Web 与 OHOS 实现两个公开接口均判定 ✅ 一致/基本一致无需修复代码。双语文档按 flutter-library-document-optimization 规范生成README.OpenHarmony_CN.md与README.OpenHarmony.md兼容性信息中的 SDK 版本动态读取工程ohos/build-profile.json5不硬编码ROM 版本先标注未实测、真机验证后回填实测值。真机验证安装并运行 example确认本地时区与可用时区列表展示正常详见第六章环境与第七章截图。四、完整代码对照4.1 Android vs OHOS 完整实现对照维度Android (Kotlin)OHOS (ArkTS)语言Kotlin / JavaArkTS (TypeScript 语法)插件接口FlutterPlugin, MethodCallHandlerio.flutter.*FlutterPlugin, MethodCallHandlerohos/flutter_ohos通道创建MethodChannel(messenger, flutter_native_timezone)new MethodChannel(binding.getBinaryMessenger(), flutter_native_timezone)本地时区ZoneId.systemDefault().idi18n.getTimeZone().getID()可用时区ZoneId.getAvailableZoneIds()i18n.TimeZone.getAvailableIDs()生命周期onAttachedToEngine/onDetachedFromEngineonAttachedToEngine/onDetachedFromEngine额外置空 channel未知方法result.notImplemented()result.notImplemented()外层 try/catch 兜底兼容 v1 注册companion object registerWith(Registrar)无需OHOS 引擎统一走插件注册表4.2 关键 ArkTS 语法差异Android 语法ArkTS 语法备注import io.flutter.plugin.common.*import { FlutterPlugin, MethodChannel, ... } from ohos/flutter_ohosOHOS 使用显式具名导入import java.time.ZoneIdimport { i18n } from kit.LocalizationKit时区/区域能力来自 Kit 化模块binding.binaryMessengerbinding.getBinaryMessenger()OHOS 为方法调用类成员可空lateinitprivate channel: MethodChannel | null nullArkTS 显式联合类型 null 判空无接口方法级 throwstry { ... } catch (err) { result.error(...) }ArkTS 对平台调用建议统一兜底五、关键决策说明决策 1以官方上游 pinkfish master 为干净基线reset本仓库此前基于另一个 flutter_timezone 分支谱系做过一次适配历史混杂。为产出可长期跟随官方上游的 0-1 适配直接git reset --hard upstream/masterfcc1f3f2.0.1重建基线再叠加本次 OHOS 适配提交旧分支保留、默认分支切换为main。维护策略后续跟随pinkfish/flutter_native_timezonemaster 拉取更新OHOS 实现单独演进。决策 2保持通道名与方法名完全不变Dart 层MethodChannel(flutter_native_timezone)、方法getLocalTimezone/getAvailableTimezones是 Dart 与原生之间的通信契约OHOS 侧原样沿用保证Dart 层零改动。维护策略新增方法时三端Dart / Android / OHOS同步注册。决策 3时区能力统一走kit.LocalizationKit的i18n对比后采用i18n.getTimeZone().getID()与i18n.TimeZone.getAvailableIDs()同时覆盖两个方法ID 语义为 IANA与 Android/iOS 同源避免systemDateTime只能取单值的缺口。维护策略若 SDK 后续提供更全量的时区表 API可平滑替换getAvailableTimezones内部实现。决策 4SDK / 版本信息动态读取不硬编码兼容性与文档中的 SDK 版本一律读取工程ohos/build-profile.json5的compatibleSdkVersion/targetSdkVersion或本机~/Library/OpenHarmony/Sdk/version/目录名ROM 版本以真机hdc shell param get实测值回填未实测前明确标注未实测杜绝文档与环境脱节。维护策略升级 SDK 或真机环境变更时文档同步按实测值更新不沿用旧版本示例。决策 5错误处理采用 try/catch 兜底OHOS 原生方法统一包 try/catch异常时通过result.error回传而非静默避免 Dart 层收到异常类型崩溃同时保持与 Android/iOS 的返回结构一致。维护策略后续方法扩展沿用同一错误回传格式。六、测试与验证测试环境项目版本Flutter3.41.10-ohos-1.0.0channel [user-branch]Dart3.11.5HarmonyOS SDKcompatibleSdkVersion 5.1.0(18) / targetSdkVersion 26.0.0读取自ohos/build-profile.json5DevEco 默认 SDK 26.0.0.105IDEDevEco Studio 26.0.0设备 ROMOpenHarmony-7.0.0.105API 26真机 ALN-AL00const.ohos.fullname实测版本获取方式版本项获取方式Flutter / Dartflutter --versionHarmonyOS SDK读取example/ohos/build-profile.json5的compatibleSdkVersion/targetSdkVersion或~/Library/OpenHarmony/Sdk/version/目录名IDE/usr/libexec/PlistBuddy -c Print :CFBundleShortVersionString /Applications/DevEco-Studio.app/Contents/Info.plist设备 ROMhdc shell param get const.ohos.fullname/hdc shell param get const.ohos.apiversion先hdc list targets确认设备连接验证要点本地时区获取— 真机运行 examplegetLocalTimezone()返回Asia/Shanghai与设备设置一致可用时区列表—getAvailableTimezones()返回数百个 IANA 时区 ID 并正常渲染列表sort()后有序展示通道一致性— 未知方法返回notImplemented()原生异常经result.error回传不崩溃一致性代码检查— ohos-flutter-code-check 逐接口比对 Android/iOS/macOS/Web 与 OHOS两个公开接口均 ✅ 一致/基本一致无代码修复项构建验证—flutter build hap --debug成功产出entry-default-signed.hap插件注册— example 的.flutter-plugins-dependencies中flutter_native_timezone已含 ohos 平台与native_build: true七、运行效果真机ALN-AL00OpenHarmony-7.0.0.105运行 example示例页展示本地时区与可用时区列表。截图通过hdc shell snapshot_display抓取hdc shell snapshot_display-f/data/local/tmp/shot.jpeg# 注意后缀必须为 .jpeghdcfilerecv /data/local/tmp/shot.jpeg ./ohos_run_timezone.jpeg八、遗留问题与改进方向踩坑复盘踩坑点现象 / 报错根因与解法上游 SDK 约束过老flutter pub get解析失败上游 pubspecsdk: 2.12.0 3.0.0与 Dart 3.11 冲突升级为3.4.0 4.0.0、flutter 3.22.0flutter create组织名歧义Ambiguous organization in existing files: {com.whelksoft, com.example}显式指定--org com.whelksoft模板文件溢出生成 Gradle kts、federated 骨架、iOS Swift 副本等非目标文件逐项甄别删除仅保留ohos/、example/ohos/与 pubspec 变更pub 镜像网络异常Got socket error ... pub.flutter-io.cn临时PUB_HOSTED_URLhttps://pub.dev重试缺少调试签名请通过DevEco Studio打开ohos工程后配置调试签名编译已通过但 HAP 未签名DevEco Studio 自动签名注入~/.ohos/config调试证书后重跑截图命令报错fileName /data/local/tmp/x.png invalid, suffix must be .jpegsnapshot_display仅支持.jpeg后缀目标仓库已存在仓库创建报 422 Project already exists复用已存在的oh-flutter/flutter_native_timezone旧分支保留推送main并切换默认分支已知问题Web 平台可用时区列表退化— 上游 Web 实现受Intl限制getAvailableTimezones()仅返回本地时区单元素上游既有行为与本次 OHOS 适配无关OHOS 可用时区为系统裁剪集合—i18n.TimeZone.getAvailableIDs()返回系统支持的集合个别极冷门 IANA ID 可能不在其中常规场景不受影响未来优化补充 ohosTest— 为 example 的 ohos 测试模块补时区接口用例纳入自动化回归CI 集成— 在.github/workflows中增加 ohos 平台的构建校验防止后续改动破坏 OHOS 编译跟随上游— 定期同步pinkfish/flutter_native_timezone上游变更保持 Dart/Android/iOS 代码不落后九、总结将一个 Flutter 三方库适配到 OHOS 平台核心路径可以概括为三步走1. 找对应 ── 找到 OHOS 对每个 Android 原生 API 的等价实现ZoneId → i18n.TimeZone 2. 保契约 ── 确保方法通道名、方法名、返回值结构完全一致Dart 层零改动 3. 补缺口 ── 对于 OHOS 不提供的 API用合理方案弥补并如实记录差异对于flutter_native_timezone三方库适配新增ohos/7 个文件与example/ohos/30 个文件两个平台目录pubspec.yaml仅增加 2 行ohos注册并升级环境约束。Dart 层与其他平台的代码完全不受影响——这正是 Flutter 跨平台三方库生态的魅力所在。参考文档flutter_native_timezone 官方仓库pinkfish本仓库 AtomGit 镜像oh-flutter/flutter_native_timezoneHarmonyOS Flutter 适配指南CPF-Flutter/flutter_flutterOpenHarmony i18n 国际化模块 API 参考ohos-flutter-plugin-adapter skillCPF-Flutter/skills
返回列表