尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

what3words鸿蒙化适配:Flutter插件MissingPluginException的桥接方案

what3words鸿蒙化适配:Flutter插件MissingPluginException的桥接方案 第一次接到 what3words 鸿蒙化适配需求时我第一反应是这玩意儿不是有官方 Flutter 插件吗直到打开鸿蒙构建日志看到一屏 “Unhandled Exception: MissingPluginException” 才明白问题没那么简单。what3words 的方案很有意思把整个世界切成无数个 3 米见方的格子每个格子用三个英语单词当地址。比如 “///filled.count.soap” 就是伦敦某处的一个点比经纬度好记比门牌号精确。但鸿蒙生态起步晚官方 Flutter 插件根本没适配鸿蒙你只能自己动手给这套核心能力做一次“鸿蒙化手术”。这篇文章不打算写那种“一步步点击”的入门教程而是把我在真实项目中踩过的坑、验证过的路径、最终落地的方案全盘托出。内容适合两类人一类是在鸿蒙上做 Flutter 应用、想集成 what3words 的开发者另一类是以后要适配其他三方库到鸿蒙想搞懂“鸿蒙化到底在化什么”的人。我会从编码原理讲到平台通道实现再讲到坐标资产在业务里的玩法最后把那些报错和崩溃的排查过程也一起交代清楚。1. 认识 what3words 与坐标转换原理1.1 三词网格3 米格子背后不是魔法是算法what3words 的核心是一套全局网格划分体系。地球表面被划成约 57 万亿个 3 米 × 3 米的方块每个方块拥有一个由三个单词组成的唯一标识。这三个单词不是随机拼凑的它遵循一套固定的编码逻辑先把经纬度通过等距投影转换成平面坐标然后在网格坐标系里取整再通过一套混淆算法将整数映射到三个词列表索引上。反过来拿到三个词也能通过逆映射还原出网格中心的经纬度。这里有个很关键的细节单词表不是英文全量词典而是经过筛选的、发音清晰、拼写简单、语义无歧义的约 4 万词表。每个词对应一个数值三个词组成一个类似“base-40000”的三位数系统。为什么用三个词而不是两个因为 40000 的三次方是 640 亿还覆盖不了 57 万亿格子所以实际词表长度和编码范围经过了严格匹配。也正因如此你几乎不会遇到发音相近导致翻车的情况。从开发者视角看你不需要实现这套算法。what3words 提供了 HTTP API 和若干语言 SDK把“词转坐标”和“坐标转词”变成了两个简单的网络请求。但这恰恰是鸿蒙适配的第一个痛点官方 Flutter 插件内部依赖了 what3words 原生 Android/iOS SDK而鸿蒙上既没有这两个 SDK 的运行时也不能直接调用它们。所以我们要做的不是重写算法而是搭一座从 Flutter 到鸿蒙的桥桥那头接 what3words 官方 REST API桥这头维持 Flutter 原有的调用方式。1.2 鸿蒙适配的真实难点不是加密壳是桥梁在鸿蒙设备上跑 Flutter 应用本身已经需要特殊的 Flutter 鸿蒙分支OpenHarmony 适配版。这个分支支持了 Dart 层和鸿蒙平台层之间的 MethodChannel / EventChannel但很多插件仍然没有鸿蒙原生实现。what3words 官方 Flutter 插件就是典型它在 pub 里最新版依然只有 Android/iOS 的 Platform 实现在鸿蒙上初始化就直接抛 MissingPluginException 或者干脆找不到符号。你可能会想那我在 Dart 层直接换成 HTTP 请求不就行了可以但 Flutter 工程里已经写好了调用 what3words 插件的业务代码比如What3words.instance.wordsToCoordinates(...)、CoordinatesToWords等到处改调用逻辑工作量很大而且后续如果要升级官方插件你的私有改动会非常痛苦。所以最优解是在鸿蒙端自己实现一个同名插件把官方插件的 Dart 接口原样“接住”底层用鸿蒙的网络请求去请求 what3words API。这样业务代码不用动只需在鸿蒙侧补上 Platform 实现。这个思路同样适合地图、支付、推送等任何“官方不支持鸿蒙”的 Flutter 插件。1.3 方案选型自研通道、HTTP 直连、社区桥接我当时梳理出三条路列个表格方便对比方案接入成本维护成本风险点修改官方 Flutter 插件往里添鸿蒙实现中需改插件源码高每次升级都要合并官方代码结构复杂容易冲突在 Flutter 工程里用 HTTP 替代插件全部调用低只改 Dart 层中业务代码侵入大无法复用官方 SDK 的离线包、缓存等特性自研鸿蒙 Platform 层维持原有 Dart 接口高需搭通道、写原生低业务层零改动需要自己实现网络层和错误处理我最终选了第三条。原因很简单这个项目的业务层有一两百处调用 what3words 的地方改 Dart 层等于重写业务而且我打算让鸿蒙端和 Android/iOS 端共用同一套 Dart 代码未来随时可以把官方鸿蒙支持接回来。自研通道虽然前期累但一劳永逸。2. 鸿蒙化适配实操从零封装一个 Flutter 插件2.1 环境准备你需要的不是鸿蒙 IDE而是一条畅通的构建链开始干活前先把环境踩实。我用的组合是DevEco Studio 5.0鸿蒙应用开发 Flutter 3.19.0 的 OpenHarmony 分支flutter_flutter再加上 OpenHarmony SDK API 11。如果你拿到的是“鸿蒙版 Flutter”发行包记得先跑flutter doctor确认openharmony出现在设备列表里。有一个坑必须提醒鸿蒙 Flutter 工程里插件目录结构和 Android/iOS 不一样。Android 插件是android/src/main/kotlin鸿蒙插件则是ohos/src/main/etsDeveco 工程下的 ArkTS 代码。你需要在pubspec.yaml里声明pluginClass和packageName就像这样flutter: plugin: implements: some_what3words_plugin platforms: ohos: pluginClass: What3wordsPlugin package: com.example.w3w_ohos这段配置决定了 Flutter 引擎怎么在鸿蒙侧找到你的插件入口。别小看这几十行我见过好几个项目就是忘了加implements导致插件根本无法加载。想要保险你可以直接用 Flutter 官方提供的模板命令flutter create --templateplugin --platformsohos my_plugin目前社区已经提供了鸿蒙模板支持省得手写桥接目录。2.2 ArkTS 侧写出 MethodChannel 服务最核心的 200 行接下来是重头戏在鸿蒙端写一个 ArkTS 类实现MethodChannel的回调。在ohos/src/main/ets/What3wordsPlugin.ets里核心逻辑有三段第一段是注册通道。需要拿到 Flutter 引擎给的PluginUtils调用getMethodChannel(what3words/coordinates, handler)。注意通道名称必须和 Dart 侧MethodChannel名字完全一致否则 Dart 调用时找不到实现。第二段是处理调用。MethodChannel 的onMethodCall会返回方法名和参数。我们需要实现两个方法wordsToCoordinates和coordinatesToWords。前者入参是字符串三词地址后者入参是一个包含lat和lng的 Map。为了保持接口兼容返回值结构也要模仿官方插件一个包含经纬度、坐标精度、三词地址的 Map。第三段是网络请求。官方 what3words API 需要传入key以后统称密钥完整请求是https://api.what3words.com/v3/convert-to-coordinates?words...key...。在鸿蒙里用ohos.net.http模块发 GET 请求就行。这里有个坑鸿蒙的 HTTP 模块回调是 Promise 风格但 MethodChannel 的响应期望是同步返回值。所以你必须把异步封装成Promise再await最后把结果传给result.success(...)。如果你直接同步 returnFlutter 端会收到null然后大概率抛空指针。代码骨架大概是这样的我省略了错误分支但你可以看到映射关系import http from ohos.net.http; import MethodCall from ohos.application.methodCall; export default class What3wordsPlugin { onMethodCall(call: MethodCall, result: any) { if (call.method wordsToCoordinates) { this.wordsToCoordinates(call.arguments, result); } else if (call.method coordinatesToWords) { this.coordinatesToWords(call.arguments, result); } } async wordsToCoordinates(args: any, result: any) { const request http.createHttp(); const url https://api.what3words.com/v3/convert-to-coordinates?words${args.words}key${args.key}; request.request(url, (err, data) { if (!err data.result.responseCode 200) { const body JSON.parse(data.result.result as string); result.success({ latitude: body.coordinates.lat, longitude: body.coordinates.lng, words: args.words, nearestPlace: }); } else { result.error(W3W_ERROR, Request failed, err?.message || data.result.responseCode); } }); } }这里我故意把“坐标转词”省略了因为代码结构几乎一样只是请求/v3/convert-to-coordinates变成了/v3/convert-to-3wa参数从words变成coordinateslat,lng。建议你直接看官方 API 文档把两个函数都补上然后重点测试边界参数。2.3 Dart 侧保持原接口让业务层毫无感知自研鸿蒙插件时Dart 侧最好直接复用官方插件的接口定义。做法是修改 Flutter 工程的 pubspec可能这样写dependencies: what3words: path: ./plugins/what3words_harmony然后在lib/what3words.dart里暴露和官方一模一样的类名和方法名。我推荐直接抄官方插件的 dart 代码只把底层的MethodChannel(what3words/coordinates)改成你自己的名字。这样业务层用await What3words.instance.wordsToCoordinates(filled.count.soap)时根本不知道底层已经换成了鸿蒙原生请求。这里有个细节得注意MethodChannel 通信本身是异步的。如果你在 ArkTS 侧用Promise发送 HTTP 请求Dart 侧拿到结果会有一个微任务延迟。实测在鸿蒙设备上一次“词转坐标”从 Dart 发出到拿到结果大约耗时 280ms比 Android 上的 150ms 慢了一些但尚可接受。如果你需要频繁转换提前在 Dart 层做缓存或节流。2.4 完整链路演示把一次三词查询从起点跑到终点适配之后链路是这样的Flutter 业务层调用What3words.instance.wordsToCoordinates(///filled.count.soap)。Dart 层生成MethodCall(wordsToCoordinates, {words: filled.count.soap, key: YOUR_API_KEY})。通过 StandardMethodCodec 编码后经 Flutter 引擎发送到鸿蒙侧 MethodChannel。鸿蒙侧onMethodCall收到参数用http.createHttp()请求 what3words 服务器。服务器返回经纬度 JSON鸿蒙侧解析成 Map再通过result.success()回传 Dart。Dart 侧把 Map 转成What3wordsCoordinates对象业务层直接使用。这过程中最常出问题的是第 5 步ArkTS 的http.request回调类型和 Dart 侧的Map结构如果不匹配回传的时候就会类型转换失败。我建议在 ArkTS 侧统一返回一个扁平 Map不要用嵌套coordinates.lat这种深层结构。官方插件的标准返回确实是嵌套的但鸿蒙侧我们完全可以在 Dart 层做二次映射这样原生侧代码更简单也更不容易踩类型不一致的坑。3. 坐标资产的落地实战在鸿蒙应用中使用 what3words3.1 资产化概念三词地址不只是字符串是业务数据“坐标资产”这个词听起来玄其实说白了经纬度加三词地址在很多场景下就是核心业务数据。比如物流公司每单的收货点、救援队要定位的遇险点、户外活动分享的营地位置。在鸿蒙应用里我们要做的不是调一个接口这么简单而是把三词地址纳入数据生命周期的管理生成、存储、校验、展示、分享。我的做法是封装一个CoordinateAsset模型包含words、lat、lng、timestamp、source五个字段。这样既能把“坐标资产”当成对象在 Dart 层传参也能在鸿蒙侧通过序列化存储到本地数据库。因为三词地址是自然语言的它天然适合做日志展示、文本共享、甚至语音播报而经纬度则适合做可视化。两者并存才叫“坐标资产”。3.2 场景一物流收货码让三词地址代替“第五个路灯右转”很多海外物流项目已经在用 what3words 做“最后一公里”。用户在下单时填三词地址司机端用三词转坐标再跳到鸿蒙地图里导航。鸿蒙上我们实现了同一套接口业务逻辑直接复用。唯一需要注意的是物流场景里用户可能输入大小写不一致what3words 官方要求使用小写并去掉///前缀所以我们在 Dart 层统一做toLowerCase()和trim()保证请求格式正确。我在适配过程中还顺手加了一个功能把三词地址中的空格自动转成英文句点。因为官方 API 接受的格式是filled.count.soap但用户可能习惯在输入框里用空格分隔。这个转换必须在调用鸿蒙通道之前完成不要指望原生层去处理。3.3 场景二户外救援与位置分享离线兜底是生死线救援场景里手机可能没信号API 调用必然失败。what3words 官方提供了离线 SDK但是鸿蒙没有。所以我们做了一个本地缓存把最近 1000 次查询的“三词 ↔ 坐标”对存进鸿蒙数据库同时在应用启动时预置一份常用区域的离线映射表比如每个城市选几万个热点三词地址。这样当网络不可用时鸿蒙插件会先查本地缓存命中就直接返回没命中则返回一个W3W_OFFLINE_ERROR。Dart 层收到这个错误后可以提示用户“当前位置暂无法识别三词地址请尝试靠近已知地标”。虽然不能覆盖全部场景但至少不至于在野外直接白屏。别小看这个兜底设计我在真机测试时把系统网络关掉连续调用了 50 次坐标转换有 12 次命中缓存平均响应小于 10ms。剩下的 38 次虽然失败了但错误码清晰业务层可以走降级弹窗。这在户外救援工具里非常加分。3.4 场景三把三词结果映射到鸿蒙地图拿到经纬度之后下一步当然是画在地图上。鸿蒙官方地图组件MapKit已经支持MapView显示标记点但如果你的 Flutter 工程里用的是高德或华为地图插件要注意鸿蒙适配的差异。最顺滑的方式是在 Dart 层把经纬度传给鸿蒙端的地图组件利用 UIAbility 的 XComponent 机制嵌入。我这里是直接用了一个开源的 Flutter 鸿蒙地图插件然后在它的onMapReady回调里加入了 marker 展示逻辑。注意地图坐标系是 WGS84 还是 GCJ02what3words 返回的坐标是 WGS84 经纬度国内地图通常要转成 GCJ02。如果你海外用可以直接忽略。如果国内用这个火星坐标偏移一定要处理否则你会看到标记点“漂移”到几个路口之外。这里分享一个实操技巧在三词转坐标成功后立即把lat、lng传给地图 SDK让地图先moveCamera到目标点再添加 marker。如果反过来先添加 marker 再 moveCamera部分国产地图组件会有 0.5 秒的白屏闪烁体验不太好。4. 常见坑位与排查实录4.1 “Unhandled Exception: MissingPluginException” 要怎么根治我在开头提到的e/flutter (31173): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] unhand...这串日志实际上就是 MissingPluginException 的底层输出。它出现的路径是Dart 调用了 MethodChannel但鸿蒙侧没找到对应的 plugin class。八成原因是pubspec.yaml里ohos平台的pluginClass写错或者鸿蒙插件没有被flutter pub get正确编译进工程。排查步骤我记成了口诀一看插件是不是声明了 ohos 平台二看鸿蒙侧的入口类有没有被注册到PluginManager三看ohos目录下有没有ets/源码被打进 HAR 包。前两种问题相对好治第三种比较隐蔽鸿蒙插件编译出的 HAR 包如果没有把.ets文件带进去运行时自然会找不到符号。这种情况建议直接flutter clean然后重新构建。4.2 API 密钥别硬编码鸿蒙端要防窥屏what3words 的密钥分前端限制型和服务端型。如果你在 App 里调用 REST API必须在 what3words 控制台申请“前端限制型”密钥并设置允许调用的域名和包名。鸿蒙端没有包名校验但你可以把密钥放到应用沙箱里通过环境变量读取。更稳的做法是让 Flutter 业务层从你自己的后端获取一个短期 token再传给鸿蒙通道避免把长期密钥躺在 App 里。我在项目里做了一个安全策略密钥一天轮换一次Dart 层启动时先从配置中心拉取然后缓存在内存里不落盘。鸿蒙通道每次只接收当天有效的 key过期就报 401业务层去刷新。这样即使截了包密钥有效期也很短。不过你得在自己后端控制 by 调用频率防止被刷。另外what3words 的 API 是按调用次数计费的。如果用户在 App 里疯狂拖拽地图导致每秒请求几十次你月底的账单会很好看。我加了一个 Dart 层节流器同一三词地址 5 秒内重复请求直接返回缓存不同地址 100ms 内最多并发 3 个。4.3 离线转换的 4 个实战细节离线转换是容易被忽略的坑。如果你有离线包注意三件事第一词表大小。what3words 官方离线包分区域一个国家的包大约 100~300MB下载到本地要控制缓存目录别塞到内存路径。鸿蒙的沙箱路径可以通过context.filesDir获取建议把离线包放在这里。第二离线包更新。what3words 偶尔会调整编码网格或词表大概每年一次你需要设计一个版本号机制。我用了一个简单方案每次应用启动时检查离线包版本与服务端比对发现旧版就静默下载下载完原子替换。千万不要边用边删旧文件否则数据会损坏。第三离线精度。我实测过离线转出来的坐标和在线转的结果可以相差 3~5 米这通常是网格边界取整误差。如果不影响业务比如物流收件人自取可以忽略如果用于高精度定位比如设备测绘则必须在线校验。4.4 性能优化MethodChannel 别传大象另一个体验问题来自 MethodChannel 本身。不要试图用通道传输大字符串或频繁调用。我一个同事试过把一张 Base64 图片从 Dart 传到鸿蒙侧去识别结果耗时 3 秒多直接卡掉 UI 线程。what3words 虽然传的是小字符串但如果你批量查询 100 个三词地址建议把任务切分每批 10 个或者全部在 Dart 层并发await让鸿蒙侧自己管理并发。实测并发 10 个请求比串行快 4 倍比单次传数组安全得多。鸿蒙侧还要注意 HTTP 连接复用。我每次请求都http.createHttp()再销毁导致 TCP 连接频繁建立大概多耗 30% 时间。后来我把HttpRequest对象存成单例复用同一个连接性能提升明显。你可以参考ohos.net.http的HttpRequest文档设置connectTimeout和readTimeout避免弱网下请求悬挂。5. 安全与合规三词地址的隐私红线你可能觉得三词地址就是几个单词和隐私无关。但别忘了它本质上是一个高精度坐标点的编码。如果用户的位置是通过三词地址分享出去的那么这三词地址就等于把经纬度暴露给了接收方。尤其在鸿蒙这种强调数据安全的平台上我们必须谨慎处理。我的经验是不要在日志里打印完整的三词地址和完整坐标因为一旦日志上传到第三方崩溃分析平台用户位置就泄露了。可以只打印“前两个词加星号”这种脱敏版本。数据库本地存储时也要采用加密方案鸿蒙提供了ohos.security.huks加密接口我建议用 HMAC 对用户的三词地址字段做摘要同时搭配随机盐这样即便数据库被拖库也无法直接还原出精确位置。另外what3words 的 ToS 要求不得将其用于跟踪他人位置除非获得明确授权。在 App 里接入这个能力时你需要主动设计一个“位置分享二次确认”弹窗避免用户无意间把自己的三词地址发出去了。鸿蒙系统本身有应用权限管理但那是属于系统级的定位权限和三词地址 API 是两回事。合规的底线是你没有在后台默默转换某个人的位置。前面绕了一大圈其实核心就一句话鸿蒙化适配的本质不是“重写”而是“桥接”。你不需要精通 what3words 的网格算法也不需要深挖华为 API 的每一个细节只要把 Flutter 和鸿蒙之间的通道打通并且在边缘场景上做足防护就能让原本只有 Android/iOS 一半的功能在鸿蒙上完整跑起来。我在真实项目里被 MissingPluginException 折磨过一整晚也在离线包版本升级时把线上地图搞白过一次。但等我把这套插件封装稳定之后业务层再也没有改动过一行 what3words 调用代码。如果你想在自己的鸿蒙应用里集成类似能力我的建议是先画一张调用链图明确哪些逻辑留在 Dart哪些放到 ArkTS哪些该走网络哪些该落缓存。想清楚这些再照着本文的骨架去写速度会快很多坑也会少很多。
返回列表