Flutter 定位插件 flutter_z_location 适配 OpenHarmony 全流程实战
GPS 定位:通过原生平台 API 获取设备经纬度经纬度反向地理编码:纯离线解析,根据经纬度获取省、市、区地址信息IP 反向地理编码:获取公网 IP 并解析所在地理位置零费用:地理编码数据来源于本地资源文件,无并发限制、无次数限制、无收费= null) {// AbilityAware 接口 — 获取 UIAbilityContextAndroid说明完全一致获取上下文方式不同坑点生成的 ohos
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.json5、string.json |
| Step 4 | 修复网络兼容问题 | lib/flutter_z_location.dart |
| Step 5 | 修复权限请求问题 | example/lib/main.dart |
| Step 6 | 编写说明文档 | README.OpenHarmony.md、README.OpenHarmony_CN.md |
三、Step 1:注册 ohos 平台
3.1 问题现象
在 ohos 设备上运行 flutter run 时,终端提示:
You need to update ./pubspec.yaml to support ohos.
3.2 解决方案
在 pubspec.yaml 的 flutter.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 |
| 上下文获取 | ActivityAware → Activity |
AbilityAware → UIAbilityContext |
| 权限请求 | 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,核心工作量集中在:
- 原生插件实现(约 60%)— 理解 Android 实现逻辑,找到对应的 OpenHarmony API,用 ArkTS 重写
- 权限与配置适配(约 20%)— 理解 OpenHarmony 的权限模型和配置方式
- 兼容性修复(约 20%)— 处理三方包不兼容、网络环境差异等实际问题
OpenHarmony 的 Flutter 插件开发模式与 Android 高度相似(FlutterPlugin + MethodCallHandler + AbilityAware),API 命名和概念也有明确的映射关系。如果熟悉 Android 端的 Flutter 插件开发,适配 OpenHarmony 的学习成本并不高。最大的挑战在于三方包生态不完善(如 permission_handler 不支持 ohos),需要在 Dart 层或原生层自行实现替代方案。
更多推荐



所有评论(0)