本文记录了从零创建 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 并带上参数。这类插件实现简单、体积为零、无密钥配置,非常适合"选地点 → 去导航"类场景。

项目地址:https://atomgit.com/oh-flutter/petalMaps_ohos

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")
OHOSnew 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引入版本关键参数
openMapHomePageopenMapHomePage5.0.3(15)无
openMapTextSearchopenMapTextSearch5.0.3(15)destinationName
openMapPoiDetailopenMapPoiDetail5.0.3(15)经纬度、POI ID、名称、地址(24+)、zoom(21+)、坐标系(21+)
openMapRoutePlanopenMapRoutePlan5.0.3(15)起/终点、出行方式、坐标系(21+)
openMapNaviopenMapNavi5.0.3(15)起/终点、出行方式、坐标系(21+)
openMapTaxiopenMapTaxi6.0.1(21)起/终点、坐标系(21+)
openMapOfflineDataManagementopenMapOfflineDataManagement6.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 升级

这是本次升级的核心(详见第八章踩坑复盘中的版本格式问题)。改动点:

  1. example/ohos/build-profile.json5:compatibleSdkVersion 从 "5.1.0(18)" 改为 "26.0.0";
  2. 对照 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(经纬度)numbernumber
Stringstringstring
PetalmapsVehicleType.transit3(index)petalMaps.VehicleType.TRANSIT
PetalmapsCoordinateType.wgs840(index)mapCommon.CoordinateType.WGS84
List<String>Liststring[]
成功nullresult.success(null)
失败PlatformExceptionresult.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 Stringstring | 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 仅作占位;代码正确性验证不依赖签名。


六、测试与验证

测试环境

项目版本
Flutter3.47.9-ohos-1.0.0(channel oh-3.47-dev)
Dart3.13.4
HarmonyOS SDK26.0.0(API 26,compatibleSdkVersion “26.0.0”)
IDEDevEco Studio 26.0.0(内置 hvigor 6.26.4)
Dart SDK 约束sdk: ^3.9.2,flutter: '>=3.3.0'

版本获取方式:

版本项获取方式
Flutter / Dartflutter --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
设备 ROMhdc shell param get const.product.software.version

验证要点

  1. 静态检查 — flutter analyze lib example/lib 零告警;
  2. 全功能演示 — example 页面逐个点击:首页/搜索/详情/路线/导航/打车/公交路线/离线数据管理;
  3. 新参数生效 — 公交路线规划按钮验证 vehicleType: transit;
  4. 低版本兼容 — 新参数不传时行为与 0.0.1 完全一致(可选参数设计保证);
  5. 构建验证 — flutter build hap --debug 产出签名 HAP;
  6. 插件注册 — 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 databuild-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 设备上),属预期行为,业务侧做版本判断降级。

已知问题

  1. flutter test 在 Flutter OH 环境不稳定 — 单元测试已编写,但受 “Invalid SDK hash”
    环境问题影响,当前以 analyze + build 验证为主;
  2. 签名配置不可移植 — 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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐