Flutter 定位插件 flutter_z_location 适配 OpenHarmony 全流程实战

欢迎大家加入跨平台开发者社区:https://openharmonycrossplatform.csdn.net/

本文记录了将 Flutter 定位插件 flutter_z_location 从仅支持 Android/iOS 适配到 HarmonyOS NEXT (OpenHarmony) 平台的完整过程,涵盖原生插件开发、权限适配、网络兼容性修复、示例工程调试等环节。

一、背景介绍

1.1 关于 flutter_z_location

flutter_z_location 是一个 Flutter 定位插件,具备以下核心能力:

  • GPS 定位:通过原生平台 API 获取设备经纬度
  • 经纬度反向地理编码:纯离线解析,根据经纬度获取省、市、区地址信息
  • IP 反向地理编码:获取公网 IP 并解析所在地理位置
  • 零费用:地理编码数据来源于本地资源文件,无并发限制、无次数限制、无收费

1.2 适配目标

将插件从 Android + iOS 双平台扩展为 Android + iOS + OpenHarmony 三平台支持,使其能在 HarmonyOS NEXT 设备上正常运行。

1.3 项目架构概览

flutter_z_location/
├── lib/                          # Dart 层(平台无关)
│   ├── flutter_z_location.dart   # 主入口
│   ├── flutter_z_location_method_channel.dart  # MethodChannel 实现
│   └── flutter_z_location_platform_interface.dart  # 平台接口
├── android/                      # Android 原生实现 (Java)
├── ios/                          # iOS 原生实现 (Swift)
├── ohos/                         # OpenHarmony 原生实现 (ArkTS) ← 新增
└── example/                      # 示例应用

二、适配步骤总览

整个适配过程分为以下 6 个步骤:

步骤 内容 涉及文件
Step 1 注册 ohos 平台 pubspec.yaml
Step 2 实现原生插件 ohos/src/main/ets/.../FlutterZLocationPlugin.ets
Step 3 配置 example 权限 example/ohos/entry/src/main/module.json5string.json
Step 4 修复网络兼容问题 lib/flutter_z_location.dart
Step 5 修复权限请求问题 example/lib/main.dart
Step 6 编写说明文档 README.OpenHarmony.mdREADME.OpenHarmony_CN.md

三、Step 1:注册 ohos 平台

3.1 问题现象

在 ohos 设备上运行 flutter run 时,终端提示:

You need to update ./pubspec.yaml to support ohos.

3.2 解决方案

pubspec.yamlflutter.plugin.platforms 下添加 ohos 平台声明:

flutter:
  plugin:
    platforms:
      android:
        package: com.example.flutter_z_location
        pluginClass: FlutterZLocationPlugin
      ios:
        pluginClass: FlutterZLocationPlugin
      ohos:                              # ← 新增
        pluginClass: FlutterZLocationPlugin

pluginClass 名称需要与 ohos/src/main/ets/.../FlutterZLocationPlugin.ets 中导出的类名一致。

四、Step 2:实现原生插件

这是适配的核心步骤。需要参照 Android 端的 Java 实现,使用 ArkTS 语言编写等价的 OpenHarmony 原生代码。

4.1 Android 端实现分析

Android 端由两个类组成:

FlutterZLocationPlugin.java — 主插件类,实现了 3 个 MethodChannel 方法:

方法 功能 关键 API
getPlatformVersion 获取平台版本 Build.VERSION.RELEASE
requestPermission 请求定位权限 ActivityCompat.requestPermissions()
getCoordinate 获取 GPS 经纬度 LocationManager + GpsListener

GpsListener.java — GPS 监听器,实现 LocationListener,收到位置回调后通过 result.success(map) 返回结果。

核心接口关系:

  • FlutterPlugin — 插件生命周期管理
  • MethodCallHandler — 处理 Dart 层的方法调用
  • ActivityAware — 获取 Activity 上下文(权限请求需要)

4.2 Android → OpenHarmony API 映射

在动手编码前,先梳理 API 映射关系:

功能 Android API OpenHarmony API
插件基类 FlutterPlugin FlutterPlugin(来自 @ohos/flutter_ohos
方法处理 MethodCallHandler MethodCallHandler
上下文获取 ActivityAwareActivity AbilityAwareUIAbilityContext
权限请求 ActivityCompat.requestPermissions() abilityAccessCtrl.requestPermissionsFromUser()
权限检查 ContextCompat.checkSelfPermission() atManager.checkAccessToken()
定位服务检查 LocationManager.isProviderEnabled() geoLocationManager.isLocationEnabled()
获取位置 LocationManager.requestSingleUpdate() geoLocationManager.getCurrentLocation()
权限名称 ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION ohos.permission.LOCATION / ohos.permission.APPROXIMATELY_LOCATION

4.3 OpenHarmony 原生实现

文件路径:ohos/src/main/ets/components/plugin/FlutterZLocationPlugin.ets

4.3.1 导入依赖
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
  AbilityAware,          // 类似 Android 的 ActivityAware
  AbilityPluginBinding,
} from '@ohos/flutter_ohos';
import { geoLocationManager } from '@kit.LocationKit';
import { abilityAccessCtrl, bundleManager, Permissions, common } from '@kit.AbilityKit';

关键点:

  • @ohos/flutter_ohos — Flutter 鸿蒙 SDK,提供插件基础接口
  • @kit.LocationKit — 系统定位能力
  • @kit.AbilityKit — 权限管理和应用上下文
4.3.2 类定义与生命周期
export default class FlutterZLocationPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
  private channel: MethodChannel | null = null;
  private abilityContext: common.UIAbilityContext | null = null;

  getUniqueClassName(): string {
    return "FlutterZLocationPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "flutter_z_location");
    this.channel.setMethodCallHandler(this);
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null);
    }
  }

  // AbilityAware 接口 — 获取 UIAbilityContext
  onAttachedToAbility(binding: AbilityPluginBinding): void {
    this.abilityContext = binding.getAbility().context as common.UIAbilityContext;
  }

  onDetachedFromAbility(): void {
    this.abilityContext = null;
  }
}

对比 Android:

Android OpenHarmony 说明
onAttachedToEngine() onAttachedToEngine() 完全一致
onAttachedToActivity() onAttachedToAbility() Activity → Ability
activity = binding.getActivity() abilityContext = binding.getAbility().context 获取上下文方式不同
4.3.3 方法路由
onMethodCall(call: MethodCall, result: MethodResult): void {
  if (call.method == "getPlatformVersion") {
    result.success("OpenHarmony");
  } else if (call.method == "getCoordinate") {
    this.requestCurrentGps(call, result);
  } else if (call.method == "requestPermission") {
    this.checkPermission(result);
  } else {
    result.notImplemented();
  }
}

与 Android 端的 onMethodCall 完全对应,三个方法名保持一致。

4.3.4 权限请求实现

Android 端通过 ActivityCompat.requestPermissions() 弹出系统权限弹窗。OpenHarmony 端等价实现:

private async checkPermission(result: MethodResult): Promise<void> {
  if (this.abilityContext == null) {
    let map: Map<string, string> = new Map();
    map.set("code", "A00001");
    map.set("message", "权限请求-context获取失败");
    result.success(map);
    return;
  }

  try {
    let atManager = abilityAccessCtrl.createAtManager();
    let data = await atManager.requestPermissionsFromUser(
      this.abilityContext!, LOCATION_PERMISSIONS
    );

    let allGranted = true;
    for (let i = 0; i < data.authResults.length; i++) {
      if (data.authResults[i] !== 0) {
        allGranted = false;
        break;
      }
    }

    let map: Map<string, string> = new Map();
    if (allGranted) {
      map.set("code", "00000");
      map.set("message", "权限已授予");
    } else {
      map.set("code", "A00002");
      map.set("message", "权限被拒绝");
    }
    result.success(map);
  } catch (err) {
    let map: Map<string, string> = new Map();
    map.set("code", "A00003");
    map.set("message", "权限请求异常: " + JSON.stringify(err));
    result.success(map);
  }
}

关键差异:

  • Android 的 requestPermissions() 是同步调用 + 回调模式;OpenHarmony 的 requestPermissionsFromUser() 返回 Promise,可以用 async/await
  • 返回结果中 authResults[i] === 0 表示授权通过
4.3.5 GPS 定位实现

这是差异最大的部分。Android 使用 LocationManager + LocationListener 回调模式,而 OpenHarmony 使用 geoLocationManager.getCurrentLocation() 返回 Promise,代码更加简洁:

private async requestCurrentGps(call: MethodCall, result: MethodResult): Promise<void> {
  let accuracy: number = 2;
  // ... 解析参数

  // 1. 检查定位服务是否开启
  let isEnabled = geoLocationManager.isLocationEnabled();
  if (!isEnabled) {
    // 返回错误码 A0003
    return;
  }

  // 2. 检查权限
  let atManager = abilityAccessCtrl.createAtManager();
  let bundleInfo = await bundleManager.getBundleInfoForSelf(
    bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
  );
  let tokenId = bundleInfo.appInfo.accessTokenId;
  let locationGranted = await atManager.checkAccessToken(tokenId, 'ohos.permission.LOCATION');
  // ... 检查权限结果

  // 3. 获取位置
  let requestInfo: geoLocationManager.CurrentLocationRequest = {
    priority: accuracy === 1
      ? geoLocationManager.LocationRequestPriority.ACCURACY
      : geoLocationManager.LocationRequestPriority.LOW_POWER,
    scenario: geoLocationManager.LocationRequestScenario.UNSET,
    maxAccuracy: 0,
    timeoutMs: 10000,
  };
  let location = await geoLocationManager.getCurrentLocation(requestInfo);

  let map: Map<string, string> = new Map();
  map.set("latitude", String(location.latitude));
  map.set("longitude", String(location.longitude));
  map.set("code", "00000");
  map.set("message", "获取成功");
  result.success(map);
}

精度参数映射:

accuracy 值 Android OpenHarmony
1(精确) Criteria.ACCURACY_FINE LocationRequestPriority.ACCURACY
2(粗略) Criteria.ACCURACY_COARSE LocationRequestPriority.LOW_POWER

Android vs OpenHarmony 定位模型对比:

Android:
  LocationManager.requestSingleUpdate(criteria, listener, looper)
      ↓ 异步回调
  LocationListener.onLocationChanged(location)
      ↓
  result.success(map)

OpenHarmony:
  let location = await geoLocationManager.getCurrentLocation(requestInfo)
      ↓ Promise 直接返回
  result.success(map)

OpenHarmony 的 getCurrentLocation() 直接返回 Promise<Location>,无需额外的 Listener 类,代码量减少约 50%。

五、Step 3:配置 example 应用权限

5.1 声明权限

example/ohos/entry/src/main/module.json5 中添加权限声明:

"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  },
  {
    "name": "ohos.permission.APPROXIMATELY_LOCATION",
    "reason": "$string:location_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  },
  {
    "name": "ohos.permission.LOCATION",
    "reason": "$string:location_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  }
]

OpenHarmony 权限分类:

类型 说明 示例
system_grant 安装时自动授权 ohos.permission.INTERNET
user_grant 需要运行时弹窗授权 ohos.permission.LOCATION

定位权限属于 user_grant,必须提供 reason 字段说明用途,否则编译报错。

5.2 添加权限说明字符串

resources/base/element/string.json 等多语言资源文件中添加:

{
  "name": "location_reason",
  "value": "用于获取您的当前位置以提供定位服务。"
}

六、Step 4:修复网络兼容问题

6.1 问题现象

应用在 ohos 设备上运行后,日志持续输出:

SocketException: Connection refused (OS Error: Connection refused, errno = 111),
address = api.ipify.org, port = 35850

getIp() 方法无法获取公网 IP,导致 IP 反向地理编码也返回空结果。

6.2 原因分析

原代码只使用了单一 IP 服务源 api.ipify.org。在国内网络环境下,该服务经常无法访问(Connection refused / Connection reset)。

6.3 解决方案

改造 getIp()多源容错机制,依次尝试多个 IP 服务,任一成功即返回:

static Future<String> getIp() async {
  final List<_IpSource> sources = [
    _IpSource('https://api.ipify.org?format=json', _IpParseType.jsonIp),
    _IpSource('https://httpbin.org/ip', _IpParseType.jsonOrigin),
    _IpSource('https://api.ip.sb/ip', _IpParseType.plainText),
    _IpSource('https://ip.3322.net', _IpParseType.plainText),  // 国内源
  ];

  for (final source in sources) {
    try {
      final result = await _fetchIp(source);
      if (result.isNotEmpty) return result;
    } catch (e) {
      debugPrint("FlutterGps-getIp(${source.url}): $e");
    }
  }
  return '';
}

每个请求设置 5 秒连接超时 + 8 秒响应超时,失败后自动切换下一个。ip.3322.net 是国内服务,可用性更高。

6.4 修复效果

修复前:

FlutterGps-getIp: SocketException: Connection refused...
GeocodeEntity{ province: , city: , ...}

修复后:

120.237.132.100
GeocodeEntity{ province: 广东省, city: 深圳市, ...}

七、Step 5:修复权限请求问题

7.1 问题现象

点击 getCoordinate 按钮后,经纬度始终为 0.0,无任何错误日志。

7.2 原因分析

example 应用使用 permission_handler 包请求定位权限:

// 原代码
Permission.location.request().then((value) async {
  final res = await FlutterZLocation.getCoordinate(accuracy: 1);
  // ...
});

permission_handler不支持 OpenHarmony 平台,导致 Permission.location.request() 静默失败,权限未实际获取,后续 GPS 调用因无权限返回默认值。

7.3 解决方案

通过 Platform 判断当前平台,在 ohos 上使用插件自带的权限请求方法:

// 修复后
onPressed: () async {
  if (Platform.isAndroid || Platform.isIOS) {
    final status = await Permission.location.request();
    if (!status.isGranted) return;
  } else {
    // ohos 平台使用插件自带的权限请求
    await FlutterZLocation.requestPermission();
  }
  final res = await FlutterZLocation.getCoordinate(accuracy: 1);
  // ...
},

这样在 ohos 设备上会调用我们实现的 abilityAccessCtrl.requestPermissionsFromUser(),正确弹出系统权限弹窗。

八、调试记录与踩坑总结

8.1 pubspec.yaml 平台声明

坑点flutter create --template=plugin 生成的 ohos 目录不会自动在 pubspec.yaml 中注册平台。必须手动添加 ohos 平台声明,否则 Flutter 不会识别 ohos 目录下的原生代码。

8.2 AbilityAware 接口

坑点:OpenHarmony 上的权限请求 requestPermissionsFromUser() 需要 UIAbilityContext,而不是普通的 ApplicationContext。必须实现 AbilityAware 接口,在 onAttachedToAbility 中获取。

对应关系:

Android:  ActivityAware → onAttachedToActivity → binding.getActivity()
ohos:     AbilityAware → onAttachedToAbility  → binding.getAbility().context

8.3 权限声明的 reason 字段

坑点user_grant 类型权限必须在 module.json5 中提供 reason 字段引用字符串资源。直接写字符串(如 "reason": "需要定位")会编译失败,必须用 $string:xxx 引用资源文件中的字符串。

8.4 permission_handler 不支持 ohos

坑点:Flutter 生态中广泛使用的 permission_handler 包目前不支持 OpenHarmony 平台。在 ohos 上调用其 API 不会报错但也不会有实际效果。解决方案是使用插件自带的原生权限请求,或基于 Platform 做平台判断。

8.5 网络服务可用性

坑点:在国内网络环境下,很多国外 API 服务(如 api.ipify.org)可能无法访问。涉及网络请求的功能应当设计多源容错机制,包含国内可用的备选服务。

九、最终文件变更清单

文件 操作 说明
pubspec.yaml 修改 添加 ohos 平台声明
ohos/ 新增 整个 ohos 原生工程目录
ohos/.../FlutterZLocationPlugin.ets 新增 核心原生插件实现
example/ohos/ 新增 example 应用的 ohos 工程配置
example/ohos/.../module.json5 修改 添加定位权限声明
example/ohos/.../string.json (x3) 修改 添加权限说明字符串
example/lib/main.dart 修改 适配 ohos 平台权限请求逻辑
lib/flutter_z_location.dart 修改 getIp() 多源容错
README.OpenHarmony.md 新增 英文说明文档
README.OpenHarmony_CN.md 新增 中文说明文档

十、测试环境与验证结果

10.1 测试环境

项目 版本
Flutter 3.35.8-ohos-0.0.2
Dart 3.9.2
HarmonyOS SDK 5.1.0(18)
IDE DevEco Studio 6.0.2 Release
设备 ROM 6.0.0.130 SP15

10.2 验证结果

功能 状态 说明
getPlatformVersion 通过 返回 “OpenHarmony”
getIp 通过 多源容错,成功获取 IP(120.237.132.100)
geocodeIp 通过 离线解析为 “广东省深圳市”
requestPermission 通过 正确弹出系统权限弹窗
getCoordinate 通过 授权后成功获取经纬度
geocodeCoordinate 通过 离线反向地理编码正确

十一、总结

将一个 Flutter 原生插件从 Android/iOS 适配到 OpenHarmony,核心工作量集中在:

  1. 原生插件实现(约 60%)— 理解 Android 实现逻辑,找到对应的 OpenHarmony API,用 ArkTS 重写
  2. 权限与配置适配(约 20%)— 理解 OpenHarmony 的权限模型和配置方式
  3. 兼容性修复(约 20%)— 处理三方包不兼容、网络环境差异等实际问题

OpenHarmony 的 Flutter 插件开发模式与 Android 高度相似(FlutterPlugin + MethodCallHandler + AbilityAware),API 命名和概念也有明确的映射关系。如果熟悉 Android 端的 Flutter 插件开发,适配 OpenHarmony 的学习成本并不高。最大的挑战在于三方包生态不完善(如 permission_handler 不支持 ohos),需要在 Dart 层或原生层自行实现替代方案。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐