ARTICLE DETAIL

资讯详情

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

OpenHarmony Flutter插件适配实战:以get_ip_address为例实现MethodChannel链路

OpenHarmony Flutter插件适配实战:以get_ip_address为例实现MethodChannel链路 最近在做 OpenHarmony 项目的 Flutter 改造卡在了最不起眼的地方——拿 IP 地址。项目面板需要展示设备当前的 IPv4随手从 pub.dev 拉了 get_ip_address结果一跑方法调用直接石沉大海没有异常日志也没有返回值界面上的 IP 永远是一片空白。查了一圈才反应过来这个库只注册了 Android 和 iOS 两个平台的原生实现在 OpenHarmony 上根本没有对应的实现类。也就是说Dart 侧把消息发出去了但对面没人接电话。这篇文章就是把这个适配过程完整复盘一遍从插件链路拆解、ohos 平台声明、ArkTS 原生实现到权限配置、实测验证和踩坑排查。如果你手上的 Flutter 工程也需要跑在 OpenHarmony 上或者你也想把手头那些只支持 Android/iOS 的三方库一个个补上 ohos 实现这篇内容可以直接拿去当作业参考。1. 为什么要动这个手OpenHarmony 上的 Flutter 三方库现状OpenHarmony 能跑 Flutter 这件事本身已经不稀奇了。从 OpenHarmony 3.x 时代开始社区里就有维护的 Flutter 引擎分支和配套的构建工具链我用过的几个版本都能把 Hello World 和纯 Dart 应用跑得很稳。但真正进入业务阶段后你会发现一个很现实的问题pub.dev 上大量插件默认只支持 Android、iOS、WebOpenHarmony 不在它们的平台清单里。纯 Dart 的包还好说因为它们不碰原生能力加载到哪都能跑。一旦涉及到系统能力——Wi-Fi、定位、存储、相机——这些插件就需要为每个平台单独写一层“翻译代码”。而当初作者写插件的时候大概率完全没有 OpenHarmony 这个概念自然也就没有这层实现。1.1 为什么选 get_ip_address 这个库当切入口我选 get_ip_address 作为第一个动手对象原因无非三点接口极简整个库就一个方法 getIpAddress()没有复杂的状态管理和生命周期。链路完整从 Dart MethodChannel 到原生实现、权限声明、构建注册一个普通插件该有的环节它全都有。拿 IP 这件事没有想象中那么简单Android、iOS、OpenHarmony 各自系统 API 的数据结构和字节序都不一样练手价值很高。直白点说如果你连这个库都适配不下来后面想动 connectivity_plus、device_info 这种复杂一点的包难度会更上头。把最小的闭环跑通是建立信心的最好方式。2. 适配前先拆链路MethodChannel 与 get_ip_address 的跨平台分发机制在动手写任何代码之前必须先把插件的跨平台机制搞清楚。很多适配做得痛苦的人都是上来就想在 OpenHarmony 里造一套“等效 Java 代码”结果把 Dart 侧和原生侧的契约完全搞丢了。2.1 MethodChannel 的“接头暗号”Flutter 插件之所以能在多个平台工作靠的是 MethodChannel 这套消息机制。Dart 侧发起一个方法调用框架会根据插件在 pubspec.yaml 里声明的平台信息自动把消息分发到对应平台的原生实现类上。去看 get_ip_address 的 Dart 源码核心其实非常短import package:flutter/services.dart; class GetIpAddress { static const MethodChannel _channel MethodChannel(get_ip_address); static FutureString getIpAddress() async { final String? ip await _channel.invokeMethod(getIpAddress); return ip ?? ; } }这里有两个关键信息Channel 名称是get_ip_address它决定了消息往哪条通道上发。方法名是getIpAddress它决定了原生侧在通道里要匹配哪个 method。适配的终极目标就是在 OpenHarmony 原生侧实现一个同名通道、接住同名方法。Dart 代码一行都不用改上层业务也感知不到任何变化。2.2 get_ip_address 在 Android 和 iOS 侧的原理为了对齐行为我翻了一下这个库在现有平台的实现发现它的“平台差异”非常典型。Android 侧核心是拿 WifiManager 的 connectionInfo再取出 ipAddress 字段。但这个字段是 Int 类型Android 系统用它存 IPv4 地址时是按小端字节序排的。所以你不能直接打印这个 Int得手动拆成四个字节private fun getCurrentIpv4(): String { val wifiManager context.applicationContext .getSystemService(Context.WIFI_SERVICE) as WifiManager val ipInt wifiManager.connectionInfo.ipAddress return String.format( %d.%d.%d.%d, ipInt and 0xff, (ipInt shr 8) and 0xff, (ipInt shr 16) and 0xff, (ipInt shr 24) and 0xff ) }iOS 侧没有 WiFiManager 这种东西而是用 getifaddrs 去遍历系统网卡接口找到 AWDL、en0 等带 IPv4 地址的接口再拼出来。相似的逻辑完全不同的 API这就是所谓“同一种能力每个平台都有自己的实现方式”。看完这两个实现你应该更清楚一件事适配 OpenHarmony不是复刻 Android 代码而是要在 OpenHarmony 的系统能力里找到那个“等价的取 IP 方法”。2.3 OpenHarmony 对应的系统能力在哪OpenHarmony 系统能力里面负责 Wi-Fi 信息获取的是ohos.wifiManager新版 Kit 写法是kit.ConnectivityKit。它提供了getIpInfo()方法返回一个 IpInfo 对象里面包含字段含义ipAddressIPv4 地址存储格式不同版本有差异netmask子网掩码gateway默认网关dns1 / dns2DNS 服务器地址要调用这套 API前置条件是在 module.json5 里声明ohos.permission.GET_WIFI_INFO权限和ohos.permission.INTERNET网络权限。这一步后面专门说先把链路对齐这件事说透。所以整个适配地图就出来了Dart 侧不动Android 侧继续用 WifiManageriOS 侧继续用 getifaddrsOpenHarmony 侧新增一个实现——拿到getIpInfo()的结果转成点分十进制字符串通过 MethodChannel 返回给 Dart。三方各走各的原生 API互不干扰。3. 手写 ohos 平台实现pubspec、ArkTS 与权限三步走链路理清楚之后我花了不到半天把代码补完。整个改动可以分成三步每一步都有它必须做对的理由。3.1 修改 pubspec.yaml把 ohos 平台声明进去一个 Flutter 插件能支持哪些平台老框架看的是 pubspec.yaml 里flutter.plugin.platforms这段。原版 get_ip_address 只有 android 和 ios我需要在原来基础上加一个 ohos 块flutter: plugin: platforms: android: package: com.example.get_ip_address pluginClass: GetIpAddressPlugin ios: pluginClass: GetIpAddressPlugin ohos: package: com.example.get_ip_address pluginClass: GetIpAddressPlugin三个字段的含义分别是packageOpenHarmony 模块里的包名标识它要和你后面 ArkTS 源码里的包路径对齐。pluginClass你新建的插件实现类名框架会通过这个名字完成注册。如果你用的是新版 Dart-only 插件的dartPluginClass方案这里不需要填因为 get_ip_address 走的是传统原生通道方案咱们按老的来。我一开始漏掉了package字段结果构建时插件没被扫描到报的是 pluginClass not found后面在坑位复盘里会详细说。3.2 搭建 ohos/ 目录工程结构不靠手搓OpenHarmony 插件的原生代码不是随便放一个 .ets 文件就行它要符合 DevEco Studio 的工程约定。完整的结构大概是ohos/ ├── build-profile.json5 ├── hvigorfile.ts ├── oh-package.json5 └── src/main/ ├── ets/ │ └── GetIpAddressPlugin.ets └── module.json5这些配置文件的写法最靠谱的来源是 OpenHarmony Flutter 插件模板工程。我不建议你手搓 JSON5反正 DevEco Studio 里新建插件模块会自动生成你只需要把这些文件里样例逻辑替换成 get_ip_address 的实现就行。个人经验是从带模板的项目复制工程骨架比自己猜字段格式快得多。需要注意oh-package.json5 里要声明对 Flutter 引擎基础库的依赖。我这个项目用的依赖名是ohos/flutter_ohos不同 Flutter 分支版本可能叫法略不同插件类要继承它提供的 Plugin 基类否则框架不知道你这个类怎么作为插件注册进来。3.3 ArkTS 实现登记通道、接住方法、返回 IP核心实现文件 GetIpAddressPlugin.ets 的代码大致如下import { MethodCall, MethodResult } from ohos/flutter_ohos; import { FlutterPluginBinding, Plugin } from ohos/flutter_ohos; import { wifiManager } from kit.ConnectivityKit; const CHANNEL_NAME: string get_ip_address; const METHOD_GET_IP: string getIpAddress; export default class GetIpAddressPlugin extends Plugin { onAttachedToEngine(binding: FlutterPluginBinding): void { binding.getMethodChannel(CHANNEL_NAME).setMethodCallHandler( (call: MethodCall, result: MethodResult) { if (call.method METHOD_GET_IP) { result.success(this.getIpAddress()); } else { result.notImplemented(); } } ); } onDetachedFromEngine(binding: FlutterPluginBinding): void { binding.getMethodChannel(CHANNEL_NAME).setMethodCallHandler(null); } private getIpAddress(): string { const ipInfo wifiManager.getIpInfo(); return this.intToIp(ipInfo.ipAddress); } private intToIp(value: number): string { const bytes new Uint8Array(4); bytes[0] (value 24) 0xff; bytes[1] (value 16) 0xff; bytes[2] (value 8) 0xff; bytes[3] value 0xff; return ${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}; } }这里有一个最容易翻车的细节ipInfo.ipAddress在部分 OpenHarmony SDK 版本里返回的是 number而且是按四字节位域存储的你直接 toString 只会得到一串“看起来像 IP 但又根本不是 IP”的整数。必须手动拆字节。字节拆出来的顺序不同设备还不一定完全一致这就是我在第 5 章要单独写一个“字节序坑位”的原因。这里先按大端序处理跑完真机验证如果发现地址是反的拆字节顺序调一下即可。3.4 权限声明OpenHarmony 的权限不在 AndroidManifest 里很多 Flutter 开发者会惯性去找 Android 的权限写法但 OpenHarmony 的权限体系是独立的得在 ohos 模块的 module.json5 里声明。这一步漏了不会立刻报权限错误而是返回一个空串非常坑。{ module: { requestPermissions: [ { name: ohos.permission.GET_WIFI_INFO }, { name: ohos.permission.INTERNET }, { name: ohos.permission.GET_NETWORK_INFO } ] } }GET_WIFI_INFO是拿 Wi-Fi 信息包括 IP的必须权限INTERNET是网络访问基础权限很多系统 API 会隐式依赖它GET_NETWORK_INFO用于获取网络连接状态。后两个不一定每个 SDK 版本都强制要但加上能少一个暗坑。4. 实测记录从 HAP 构建到真机拿到 IP代码补完之后最紧张的就是实测环节。适配项目成不成立不是看代码编译通过而是看它能不能在 OpenHarmony 设备上把一个真实 IP 返回给 Flutter 页面。4.1 环境准备与构建方式我的环境如下供参考项目版本开发 IDEDevEco Studio 5.0.xOpenHarmony SDKAPI 12Flutter SDKOpenHarmony 维护分支测试设备API 12 模拟器 一台真机平板构建 HAP 的流程是先在 Flutter 工程里跑flutter pub get然后打开 DevEco Studio 加载整个工程使用它的构建配置把 Flutter 插件和 ohos 原生模块一起打包成 HAP。注意 OpenHarmony 目前的 Flutter 构建链路和标准 Flutter 不完全一样不要指望单独执行flutter build apk之类的命令能产出 OpenHarmony 包。这是两套构建体系。模拟器装应用的方式和普通 OpenHarmony 应用一样通过 DevEco Studio 的远程设备调试直接跑。第一次跑起来的时候我很紧张地看着空白界面——直到页面上的 Text 组件刷新出来一个 IPv4 地址。4.2 页面上的调用代码Dart 侧完全沿用原库 APIimport package:get_ip_address/get_ip_address.dart; FutureString _loadIp() async { final ip await GetIpAddress.getIpAddress(); return ip; }真机上的返回结果是192.168.1.103。看到这个字符串的一瞬间整个适配闭环就算通了Dart 方法调用 → MethodChannel 分发 → ohos 原生实现取 Wi-Fi IP → 字节转换 → 返回 Dart → 界面显示。一条完整链路全流程无改动上游代码。4.3 模拟器与真机的差异必须单独说一句模拟器和真机拿到的 IP 往往不一样。模拟器里 Wi-Fi 模块通常走的是虚拟网络getIpInfo()拿到的可能是10.0.2.x这类地址真机拿到才是局域网真实 IP。测试的时候如果发现“IP 拿到了但和预期不符”先确认自己是不是在模拟器上再怀疑代码问题。5. 踩坑复盘日志、字节序与模拟器网络这一章是我最想写的部分。适配过程中遇到的三个问题每个都逼着我重新翻了一遍源码和日志。5.1 坑一明明写了 pluginClass日志还是提示 channel not implemented现象第一版代码跑起来后页面继续空白Flutter 引擎日志里出现类似方法未实现、channel 没有 handler 的信息。初步假设我以为是 pubspec.yaml 的 ohos 段写错了或者 pluginClass 类名拼写不对于是反复检查了三遍大小写。排查链路我先在 GetIpAddressPlugin.ets 里加了大量日志输出确认 onAttachedToEngine 有没有被调用。结果日志里连一条都没出现说明插件根本没有被框架加载。于是问题从“通道没匹配上”变成了“插件类没注册进来”。这时候去看构建产物发现 HAP 里根本没打进去 ohos 插件的编译结果。再回头看 pubspec.yaml果然package字段没填导致构建时插件被框架忽略。根因Flutter 构建工具是根据 pubspec 里声明的内容去收集插件原生模块的。platforms 块里少了 ohos 或者字段不完整即便你把 .ets 文件放在 ohos/ 目录下它也不会被纳入编译。修复把package和pluginClass两个字段补全干净同步构建一次。日志里看到GetIpAddressPlugin onAttachedToEngine之后和 Dart 的通道才真正建立起连接。经验遇到 channel 相关的诡异报错先用日志确认原生侧生命周期方法到底有没有被触发。如果没触发问题基本不在你的原生代码而在插件注册和构建环节。5.2 坑二IP 转换结果变成 127.0.0.1 的反向版本现象把ipInfo.ipAddress直接转成字符串后拿到的地址完全不对比如本应是192.168.1.103吐出来却像103.1.168.192的某种排列组合。初步假设权限没给返回了一个异常字段。排查链路先打印ipInfo.ipAddress原始值确认它确实是一个正整数不是 0 也不是空。然后拿一个小工具把整数拆成二进制和期望的 IP 字节段比对发现字节是倒序排放的。查了 OpenHarmony SDK 文档里 IpInfo 的说明确认不同小版本对 ipAddress 位域字节序的约定存在差异甚至和 Android 那边 WifiManager 的小端序也不一致。根因处理器大小端、系统存储约定、API 版本差异三个因素叠加导致同一个字段在不同设备上拆出来的字节序不稳定。修复我的 intToIp 里先按大端序拆再看设备表现决定是否反转字节顺序。为了不让自己再被坑我加了一个辅助的自检逻辑如果转换结果是127.0.0.1的反向版本就在日志里直接警告字节序异常提醒自己查设备规格。经验拿 IP 的代码永远带着一个字节序转换的小测试一起写。你用0x7f000001当成 127.0.0.1 测一次就知道当前设备的字节序是否符合预期。5.3 坑三模拟器上 getIpInfo() 返回 0.0.0.0现象在 API 12 模拟器上代码没有抛异常但 getIpInfo() 返回的 ipAddress 是 0转换结果是 0.0.0.0。初步假设权限没配上或者 Wi-Fi 没有连接。排查链路我已经在权限声明里加了三个权限排除了权限问题。接着我去看模拟器设置发现它根本没有启用“Wi-Fi 已连接”这个状态。OpenHarmony 模拟器对 Wi-Fi 的模拟并不完整某些版本直接缺少 Wi-Fi 模块的热点连接能力。getIpInfo()拿不到有效 IP自然就回退成 0。根因模拟器的网络形态跟真机不同不能默认它一定存在可用的 Wi-Fi 链路。修复我实现了一个回退策略——优先取 Wi-Fi IP拿不到就尝试网络连接管理模块最后再遍历网卡列表找第一个非回环 IPv4 地址。在实际实现里就是一个候选列表按优先级依次尝试private getIpAddress(): string { const candidates: string[] [ this.getWifiIp(), // 优先 Wi-Fi this.getDefaultNetIp() // 回退到默认网络 ]; for (const ip of candidates) { if (ip ip ! 0.0.0.0) { return ip; } } return 0.0.0.0; }真机几乎不会走到回退分支但模拟器测试和某些外接网络设备场景下这个回退逻辑能让人少焦虑一晚上。6. 适配之后集成方式与同类插件的抄作业路径代码改完、验证通过剩下的问题就是怎么把它用到自己的工程里以及能不能把这套经验复制给其他公共包。6.1 本地集成path 或 git 依赖如果只是自己项目内部用最简单的集成方式是在 pubspec.yaml 里用 path 指向改了之后的本地插件目录dependencies: get_ip_address: path: ./plugins/get_ip_address但本地路径没法分享给团队其他成员所以更好的是把适配完的代码推到自己的 Git 仓库然后用 git 依赖dependencies: get_ip_address: git: url: https://github.com/your-repo/get_ip_address.git ref: openharmony-support这样团队内 clone 下来执行flutter pub get就能拿到你的适配版本。注意 tag 打清楚别让意外提交影响别人的构建。6.2 向上游仓库提 PR 前要准备的几件事适配完成不等于项目结束。如果这个库本身还在积极维护把 ohos 支持回馈给上游对 OpenHarmony 的 Flutter 生态来说就是实打实的积累。提 PR 之前我习惯做四件事确认改动不破坏原有 Android 和 iOS 平台的构建确保插件声明里只是新增了一个 ohos 块而不是改动旧字段。把 ohos/ 目录下的所有工程配置文件完整放进提交包括 build-profile.json5、oh-package.json5少一个别人都没法构建。在 README 里写明适配的 OpenHarmony SDK 版本范围因为 OpenHarmony 的 API 变化频率不低。附上测试用例真机 Wi-Fi 环境下的 IP 返回结果、模拟器上的回退结果。6.3 其他插件的抄作业路径在 OpenHarmony 上适配插件方法大同小异核心始终是那三件事确认 Dart 侧的 MethodChannel 契约不变所有平台共用同一通道名称和方法名。在 pubspec.yaml 的 platforms 里声明 ohos 实现类让框架能找到并注册。在 ohos/ 目录里补齐系统能力调用和权限声明用 OpenHarmony 原生的 API 实现和 Android/iOS 等价的功能。我个人建议先从这种只有一两个方法的“迷你插件”开始练手。像 get_ip_address 这种接口简单、依赖少、回调链路直观跑通一遍之后你再去看 connectivity_plus、device_info 这类复杂插件就会发现它们的适配思路完全一样只是原生 API 更繁琐、权限清单更长而已。另一个小原则是能用纯 Dart 解决的就尽量别碰原生。如果你的业务只是需要当前设备某个系统参数先看看是不是有大佬已经写了 ohos 兼容的包实在没有才考虑自己补实现。盲目前往一个没有官方渠道、没有明确 API 文档的插件里加平台代码会消耗不少时间。这次适配 get_ip_address 最大的收获倒不是那十几行 ArkTS 代码本身而是让我对整个 Flutter 插件的跨平台分发机制有了更直观的判断力。下次再遇到 MethodChannel 调不通的问题我不会再盯着 Dart 代码发呆而是会按“通道名称 → 平台声明 → 原生类注册 → 权限配置”的顺序一路排查下去。这个排查顺序比任何一个人的现成代码都有价值。
返回列表