Flutter 三方库 petalmaps_ohos 的 OpenHarmony 适配实战(0 → 1 全记录)
本文记录了从零创建 Flutter 三方库
petalmaps_ohos、将其适配到 OpenHarmony / HarmonyOS 平台,
并升级到 HarmonyOS SDK 26(API 26)的完整过程,包含适配思路、代码对照、关键决策和踩坑复盘。
写给基础较弱的开发者:每一步都给出"为什么这么做",照着做就能复现。
一、背景
1.1 三方库简介
petalmaps_ohos 是一个 Flutter 插件,用于在 HarmonyOS / OpenHarmony 应用中调起系统自带的花瓣地图(Petal Maps),提供以下能力:
- 打开地图首页(openMapHomePage)
- 地点搜索(openMapTextSearch)
- 查看地点详情(openMapPoiDetail)
- 路线规划(openMapRoutePlan,支持驾车/步行/骑行/公交)
- 发起导航(openMapNavi)
- 打车页面(openMapTaxi)
- 离线数据管理(openMapOfflineDataManagement,API 24+ 新增)
它属于**"调起系"插件**:不在应用内嵌入地图 SDK,而是通过系统 Kit API 拉起花瓣地图 App 并带上参数。这类插件实现简单、体积为零、无密钥配置,非常适合"选地点 → 去导航"类场景。
1.2 适配目标
| 维度 | 要求 |
|---|---|
| 功能一致性 | 与华为官方 @kit.MapKit 中 petalMaps 模块的 API 一一对应,不遗漏参数 |
| Dart 层易用性 | 提供 Dart 枚举(出行方式、坐标系),屏蔽原生数字枚举细节 |
| 兼容性 | example 工程升级到 HarmonyOS SDK 26,同时保持 API 15+ 设备可用(新参数均为可选) |
| 工程规范 | 三层架构(API / 平台接口 / 方法通道)、CHANGELOG、单元测试、example 演示齐全 |
1.3 为什么选"调起"而不是"内嵌地图"
| 方案 | 优点 | 缺点 |
|---|---|---|
| 调起花瓣地图 ✅ | 零 SDK 依赖、无密钥、包体积零增加、导航体验由系统地图保证 | 依赖设备安装了花瓣地图;无法自定义地图 UI |
| 内嵌 MapKit 地图组件 | 可自定义 UI、地图随应用走 | 需要 AGC 配置、包体积增加、工程复杂度高 |
对于"点外卖看商家位置 → 一键导航"这类需求,调起方案 30 行代码就能交付,是性价比最高的选择。
二、适配路线图
整个从 0 到 1 分为 6 个阶段:
第 1 阶段:项目初始化 ── flutter create 生成 OHOS 插件模板
第 2 阶段:原生实现 ── ArkTS 实现 MethodCallHandler,桥接 @kit.MapKit
第 3 阶段:三方库注册 ── pubspec.yaml 注册 ohos 平台
第 4 阶段:示例验证 ── example 工程 + 全功能演示页
第 5 阶段:SDK 26 升级 ── compatibleSdkVersion 升级 + 新增 API 能力
第 6 阶段:构建验证 ── flutter build hap 产出签名 HAP
三、逐步适配过程
第 1 阶段:项目初始化
使用 Flutter 鸿蒙版命令行生成 OHOS 插件模板:
flutter create --template=plugin --platforms=ohos petalmaps_ohos
注意:这里的
flutter必须是 Flutter OH 版(如3.47.9-ohos-1.0.0,仓库地址
https://atomgit.com/oh-flutter/flutter_flutter.git)。官方 Flutter 没有ohos平台选项,
用flutter --version可以确认版本号里是否带-ohos后缀。
该命令会自动生成 ohos/ 目录的标准模板结构:
ohos/
├── index.ets # 模块入口,导出插件类
├── oh-package.json5 # 包配置
├── build-profile.json5 # 构建配置
├── src/main/
│ ├── module.json5 # HAR 模块配置
│ └── ets/components/plugin/
│ └── PetalmapsOhosPlugin.ets # 原生插件实现(核心)
关键配置文件:
index.ets(入口导出文件)
import PetalmapsOhosPlugin from './src/main/ets/components/plugin/PetalmapsOhosPlugin';
export default PetalmapsOhosPlugin;
oh-package.json5(包配置)
{
"name": "petalmaps_ohos",
"version": "1.0.0",
"main": "index.ets",
"license": "Apache-2.0",
"dependencies": {}
}
注:
@ohos/flutter_ohos由 Flutter 引擎在构建时自动链接,无需在dependencies中显式声明。
module.json5(HAR 模块配置)
{
"module": {
"name": "petalmaps_ohos",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
第 2 阶段:原生实现(核心)
2.1 插件类型判断
本插件属于方法调用型(Dart 主动调起地图,不需要原生反向推送事件),因此只需实现
MethodChannel + MethodCallHandler,不涉及 EventChannel / StreamHandler。
但由于调起地图必须持有 UIAbilityContext,还需要额外实现 AbilityAware 接口——
这是本插件区别于普通插件的关键点:
FlutterPlugin ── 引擎生命周期(创建 MethodChannel)
MethodCallHandler ── 接收 Dart 方法调用
AbilityAware ── 拿到 UIAbilityContext(petalMaps API 的必传参数)
2.2 整体架构与通道注册
import {
FlutterPlugin, FlutterPluginBinding, MethodCall, MethodCallHandler,
MethodChannel, MethodResult, AbilityAware, AbilityPluginBinding,
} from '@ohos/flutter_ohos';
import { petalMaps } from '@kit.MapKit';
import { common } from '@kit.AbilityKit';
export default class PetalmapsOhosPlugin
implements FlutterPlugin, MethodCallHandler, AbilityAware {
private channel: MethodChannel | null = null;
private context: common.UIAbilityContext | null = null;
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), "petalmaps_ohos");
this.channel.setMethodCallHandler(this);
}
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.context = binding.getAbility().context as common.UIAbilityContext;
}
// ...
}
通道注册对照:
| 平台 | 代码 |
|---|---|
| Android(参考) | MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "petalmaps_ohos") |
| OHOS | new MethodChannel(binding.getBinaryMessenger(), "petalmaps_ohos") |
差异:OHOS 使用
getBinaryMessenger()替代 Android 的getFlutterEngine().getDartExecutor(),接口更简洁。
2.3 为什么必须实现 AbilityAware
petalMaps 的每个 API 第一个参数都是 common.Context。如果用 getContext() 全局上下文,
在没有前台 UIAbility 的场景下会调起失败(报 1002600014 Failed to start the map app)。
正确做法是实现 AbilityAware,在 onAttachedToAbility 中取 UIAbilityContext:
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.context = binding.getAbility().context as common.UIAbilityContext;
}
onDetachedFromAbility(): void {
this.context = null; // 解绑时置空,避免持有失效引用
}
Flutter OH 的引擎会在宿主 Ability 生命周期中自动回调这两个方法,插件注册后无需手工绑定。
2.4 方法分发与参数解析
onMethodCall 里用 switch 分发方法名,每个方法单独抽成 handleXxx 函数,统一
try/catch 并把原生错误码透传给 Dart:
onMethodCall(call: MethodCall, result: MethodResult): void {
switch (call.method) {
case "openMapHomePage":
this.handleOpenMapHomePage(result);
break;
case "openMapRoutePlan":
this.handleOpenMapRoutePlan(call, result);
break;
case "openMapOfflineDataManagement": // API 24+ 新增
this.handleOpenMapOfflineDataManagement(call, result);
break;
default:
result.notImplemented();
break;
}
}
参数解析时,可选参数必须判断 undefined && null 再赋值,避免把空值塞进 ArkTS 的可选字段。
我们抽了三个工具函数统一处理:
private optString(args: Map<string, Object>, key: string): string | undefined
private optNumber(args: Map<string, Object>, key: string): number | undefined
private optCoordinateType(args, key): mapCommon.CoordinateType | undefined // number → 枚举
private optVehicleType(args, key): petalMaps.VehicleType | undefined // number → 枚举
关键差异点:Dart 侧无法直接传 ArkTS 枚举,所以 Dart 枚举通过
.index序列化为 int,
ArkTS 侧用as断言映射回mapCommon.CoordinateType/petalMaps.VehicleType。
这要求两端的枚举顺序严格一致(见第五章决策 3)。
2.5 原生方法一览
| Dart 方法名 | petalMaps API | 引入版本 | 关键参数 |
|---|---|---|---|
| openMapHomePage | openMapHomePage | 5.0.3(15) | 无 |
| openMapTextSearch | openMapTextSearch | 5.0.3(15) | destinationName |
| openMapPoiDetail | openMapPoiDetail | 5.0.3(15) | 经纬度、POI ID、名称、地址(24+)、zoom(21+)、坐标系(21+) |
| openMapRoutePlan | openMapRoutePlan | 5.0.3(15) | 起/终点、出行方式、坐标系(21+) |
| openMapNavi | openMapNavi | 5.0.3(15) | 起/终点、出行方式、坐标系(21+) |
| openMapTaxi | openMapTaxi | 6.0.1(21) | 起/终点、坐标系(21+) |
| openMapOfflineDataManagement | openMapOfflineDataManagement | 6.1.1(24) | scenarios(WATCH/PHONE/TABLET/PC/VOICE)、recommendedRegionIds |
以路线规划为例的完整实现:
private async handleOpenMapRoutePlan(call: MethodCall, result: MethodResult): Promise<void> {
try {
let args = call.args as Map<string, Object>;
let params: petalMaps.RoutePlanParams = {
destinationPosition: {
latitude: args.get('latitude') as number,
longitude: args.get('longitude') as number
}
};
let originLat = this.optNumber(args, 'originLatitude');
let originLng = this.optNumber(args, 'originLongitude');
if (originLat !== undefined && originLng !== undefined) {
params.originPosition = { latitude: originLat, longitude: originLng };
}
let vehicleType = this.optVehicleType(args, 'vehicleType');
if (vehicleType !== undefined) {
params.vehicleType = vehicleType;
}
let coordinateType = this.optCoordinateType(args, 'coordinateType');
if (coordinateType !== undefined) {
params.coordinateType = coordinateType;
}
await petalMaps.openMapRoutePlan(this.context!, params);
result.success(null);
} catch (e) {
let error = e as Error;
result.error("openMapRoutePlan_error", error.message, null);
}
}
第 3 阶段:三方库注册
在 pubspec.yaml 中注册 OHOS 平台:
flutter:
plugin:
platforms:
ohos:
pluginClass: PetalmapsOhosPlugin # 对应 index.ets 默认导出
Flutter 的 OHOS 引擎在构建时会读取这段配置,自动加载 ohos/index.ets 导出的插件类,
并写入 example 工程的 GeneratedPluginRegistrant.ets——无需手写注册代码。
第 4 阶段:示例应用创建
在 example/ 目录(不是插件根目录)生成 OHOS 宿主工程:
cd example
flutter create . --platforms=ohos
example 的 lib/main.dart 提供全功能演示页:每个能力一张卡片按钮,
点击后调起花瓣地图并用 SnackBar 展示成功/失败状态,是新用户最快的上手入口。
第 5 阶段:SDK 26 升级
这是本次升级的核心(详见第八章踩坑复盘中的版本格式问题)。改动点:
example/ohos/build-profile.json5:compatibleSdkVersion从"5.1.0(18)"改为"26.0.0";- 对照 SDK 26 的
@hms.core.map.petalMaps.d.ts,把插件升级到全量能力:- 新增方法
openMapOfflineDataManagement(离线地图/语音包管理,API 24+); openMapRoutePlan/openMapNavi补齐originName、originPoiId、
destinationPoiId、vehicleType、coordinateType;openMapTaxi补齐起点信息(originPosition/originName/originPoiId)与coordinateType;openMapPoiDetail补齐destinationName、destinationAddress(API 24+)、
zoom(3~20,API 21+)、coordinateType;- Dart 侧新增
PetalmapsVehicleType(driving/walking/cycling/transit)与
PetalmapsCoordinateType(wgs84/gcj02)两个枚举。
- 新增方法
升级原则:所有新参数都是可选的,老用户升级插件后无需改一行代码;
真机系统版本低于对应 API 时,petalMaps 内部会抛 801 Capability not supported,
插件透传给 Dart 侧做降级提示。
第 6 阶段:构建验证
cd example
flutter build hap --debug
# ✓ Built build/ohos/hap/entry-default-signed.hap
同时 flutter analyze lib example/lib 零告警。
四、完整代码对照
4.1 三层 Dart 架构
插件 Dart 侧遵循 Flutter 官方推荐的联邦插件三层结构,方便他人替换平台实现或写 Mock 测试:
lib/
├── petalmaps_ohos.dart # 对外 API:类 + 枚举定义
├── petalmaps_ohos_platform_interface.dart # 平台接口:abstract class,抛 UnimplementedError
└── petalmaps_ohos_method_channel.dart # 方法通道实现:组装参数并 invokeMethod
以打车为例看三层如何传递参数:
// 第 1 层:对外 API(petalmaps_ohos.dart)
Future<void> openMapTaxi({
required double latitude,
required double longitude,
PetalmapsVehicleType? vehicleType, // 枚举对用户友好
}) => PetalmapsOhosPlatform.instance.openMapTaxi(...);
// 第 2 层:平台接口(petalmaps_ohos_platform_interface.dart)
Future<void> openMapTaxi({...}) {
throw UnimplementedError('openMapTaxi() has not been implemented.');
}
// 第 3 层:方法通道(petalmaps_ohos_method_channel.dart)
await methodChannel.invokeMethod<void>('openMapTaxi', {
'latitude': latitude,
'longitude': longitude,
if (vehicleType != null) 'vehicleType': vehicleType.index, // 枚举 → int
});
4.2 Dart ↔ ArkTS 类型映射
| Dart 类型 | 通道载荷 | ArkTS 类型 |
|---|---|---|
double(经纬度) | number | number |
String | string | string |
PetalmapsVehicleType.transit | 3(index) | petalMaps.VehicleType.TRANSIT |
PetalmapsCoordinateType.wgs84 | 0(index) | mapCommon.CoordinateType.WGS84 |
List<String> | List | string[] |
| 成功 | null | result.success(null) |
| 失败 | PlatformException | result.error(code, message, null) |
4.3 关键 ArkTS 语法差异
| Java/Kotlin 习惯 | ArkTS 语法 | 备注 |
|---|---|---|
io.flutter.embedding... | import { ... } from '@ohos/flutter_ohos' | 模块化导入 |
call.argument("x") | call.args as Map<string, Object> 后 .get() | OHOS 侧整包取 args 再自行解析 |
@Nullable String | string | undefined + 手工判空 | 可选参数必须判 undefined && null |
switch/case 分发 | 相同 | 未识别方法必须 result.notImplemented() |
五、关键决策说明
决策 1:保持通道名 petalmaps_ohos 不变
通道名是 Dart 与原生之间的通信契约,升级时只增方法、不改名,保证 0.0.1 老用户平滑升级到 0.1.0。
决策 2:AbilityAware 获取 UIAbilityContext
调起系 API 对 Context 敏感,全局 Context 会报 1002600014。通过 AbilityAware 在
onAttachedToAbility 拿到前台 UIAbilityContext,是调起成功的前提(见 2.3)。
决策 3:Dart 枚举按 index 序列化
PetalmapsVehicleType / PetalmapsCoordinateType 的声明顺序与原生
petalMaps.VehicleType(0=DRIVING,1=WALKING,2=CYCLING,3=TRANSIT)、
mapCommon.CoordinateType(0=WGS84,1=GCJ02)严格一致。这个"顺序契约"写进了
Dart 枚举的文档注释,未来新增枚举值只能追加不能重排。
决策 4:新参数全部可选,错误透传不做静默
API 21+/24+ 的参数一律 ? 可选;低版本设备调用新 API 时,原生的 801 Capability not supported 错误码原样抛给 Dart,让业务侧自行决定降级策略,
而不是在插件里静默吞掉——静默降级会让"功能没生效"变成查不到原因的悬案。
决策 5:免签构建作为 CI 基线
签名材料(p12/p7b/密码)是机器绑定的,不能进仓库。flutter build hap 触发的
自动签名只在本机有效,仓库中 signingConfigs 仅作占位;代码正确性验证不依赖签名。
六、测试与验证
测试环境
| 项目 | 版本 |
|---|---|
| Flutter | 3.47.9-ohos-1.0.0(channel oh-3.47-dev) |
| Dart | 3.13.4 |
| HarmonyOS SDK | 26.0.0(API 26,compatibleSdkVersion “26.0.0”) |
| IDE | DevEco Studio 26.0.0(内置 hvigor 6.26.4) |
| Dart SDK 约束 | sdk: ^3.9.2,flutter: '>=3.3.0' |
版本获取方式:
| 版本项 | 获取方式 |
|---|---|
| Flutter / Dart | flutter --version |
| HarmonyOS SDK | 读取 example/ohos/build-profile.json5 的 compatibleSdkVersion;DevEco 内置 SDK 看 /Applications/DevEco-Studio.app/Contents/sdk/default/sdk-pkg.json |
| IDE | /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" /Applications/DevEco-Studio.app/Contents/Info.plist |
| 设备 ROM | hdc shell param get const.product.software.version |
验证要点
- 静态检查 —
flutter analyze lib example/lib零告警; - 全功能演示 — example 页面逐个点击:首页/搜索/详情/路线/导航/打车/公交路线/离线数据管理;
- 新参数生效 — 公交路线规划按钮验证
vehicleType: transit; - 低版本兼容 — 新参数不传时行为与 0.0.1 完全一致(可选参数设计保证);
- 构建验证 —
flutter build hap --debug产出签名 HAP; - 插件注册 —
GeneratedPluginRegistrant.ets自动导入并注册PetalmapsOhosPlugin。
七、运行效果
7.1 真机验证
适配完成后,连接 OpenHarmony 真机完成全功能验证:
hdc list targets # 确认设备连接
cd example && flutter build hap --debug # 构建
hdc install -r build/ohos/hap/entry-default-signed.hap # 安装
hdc shell aa start -a EntryAbility -b com.nutpi.petalmaps_ohos_example # 启动
UI 自动化验证用 uitest:hdc shell uitest dumpLayout 获取按钮坐标,
hdc shell uitest uiInput click <x> <y> 逐个点击,hdc shell snapshot_display
截图核对花瓣地图是否被正确调起。
7.2 真机验证结果
| 功能 | 结果 | 说明 |
|---|---|---|
| 打开地图首页 | ✅ | 花瓣地图首页正常调起 |
| 地点搜索 | ✅ | 搜索页正常打开 |
| 公交路线规划(vehicleType: transit) | ✅ | "公交地铁"标签默认选中,目的地正确传入 |
| 打车 | ✅ | 打车 tab 选中,目的地落点正确 |
| 开始导航 | ✅ | 进入导航模式并请求路线 |
| 离线数据管理(API 24+ 新增) | ✅ | 打开"地图资源管理"页 |
7.3 运行截图
示例应用主界面(每个能力一张卡片,点击即调起花瓣地图):

打开地图首页(openMapHomePage):

公交路线规划(openMapRoutePlan + vehicleType: transit,"公交地铁"标签已选中):

打车(openMapTaxi,目的地落点正确并展示网约车面板):

开始导航(openMapNavi,进入导航模式请求路线):

离线数据管理(openMapOfflineDataManagement,API 24+ 新增能力,打开"地图资源管理"页):

八、踩坑复盘
踩坑记录(本次升级实际遇到的)
| 踩坑点 | 现象 / 报错 | 根因与解法 |
|---|---|---|
| compatibleSdkVersion 格式 | 00308018 api version parameter is illegal! Expected format: <major>[.<minor>][.<patch>] | 新版 hvigor(6.x)不再接受 "5.1.0(18)" 这种"HarmonyOS 版本(API 版本)"双段格式,改成纯 API 格式 "26.0.0" 即可 |
| 签名失败(路径) | 00303107 Invalid storeFile value | 仓库里的 signingConfigs 写死了别人机器的 /Users/jianguo/... 证书路径;解法是把证书路径改成本机 ~/.ohos/config/ 下的对应材料,或在 DevEco 里重新配置签名 |
| 签名失败(口令) | 00303242 Signature material verification failed, Unsupported state or unable to authenticate data | build-profile 里的 keyPassword/storePassword 是 DevEco 按证书材料加密的,换一份 p12 口令就对不上;解法是删除 product 的 signingConfig 引用走自动签名,或用 DevEco 的 File → Project Structure → Signing Configs 重新生成 |
| flutter test 卡死 | Can't load Kernel binary: Invalid SDK hash 后挂起 | Flutter OH 版 test 环境与本地 SDK hash 不匹配的已知环境问题;编译验证改以 flutter analyze + flutter build hap 为准 |
鸿蒙插件适配高频坑速查
module.json5权限reason字段必须是$string:xxx资源引用,不能写纯文本;- 权限名报
00303221→ 查文档确认是否真的需要权限,很多调起类 API 无需任何权限; - ArkTS 编译报
Property 'xxx' does not exist→ 对照 SDK.d.ts(本插件是
@hms.core.map.petalMaps.d.ts)确认字段名与引入版本; - 调起地图报
1002600014→ 九成是 Context 用错,必须UIAbilityContext; - 报
801 Capability not supported→ 设备系统版本低于该 API 的引入版本(如 API 24+
的离线管理跑在 API 21 设备上),属预期行为,业务侧做版本判断降级。
已知问题
- flutter test 在 Flutter OH 环境不稳定 — 单元测试已编写,但受 “Invalid SDK hash”
环境问题影响,当前以 analyze + build 验证为主; - 签名配置不可移植 — signingConfigs 是机器绑定的,新机器需在 DevEco 中重新生成。
未来优化
- openMapTextSearch 增加坐标限定搜索 — 等上游 TextSearchParams 扩展后跟进;
- 支持返回调起结果 — 目前 promise 只表示"调起成功",后续跟进 petalMaps 的结果回调能力;
- CI 门禁 — 引入无头构建 + analyze 流水线。
九、总结
把一个 Flutter 三方库从 0 做到 1 并适配 OHOS,核心路径可以概括为 三步走:
1. 找对应 ── 找到 OHOS 对目标能力的等价实现(本插件即 @kit.MapKit 的 petalMaps 模块)
2. 保契约 ── 方法通道名、方法名、参数结构两端完全一致;枚举按 index 对齐
3. 补缺口 ── Context 用 AbilityAware 拿 UIAbilityContext;低版本能力用可选参数 + 错误透传降级
petalmaps_ohos 从 0.0.1 的 6 个方法升级到 SDK 26 下的 7 个方法 + 全量参数,
Dart 层对老用户完全向后兼容——这正是 Flutter 跨平台三方库生态的魅力所在。
参考文档
开源协议:Apache-2.0
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)