Flutter/OpenHarmony 三方库适配:geolocator 定位能力
Flutter/OpenHarmony 三方库适配:geolocator 定位能力
定位能力是 Flutter 应用迁移 OpenHarmony 时最容易遇到平台差异的模块之一。Dart 层的 Geolocator API 看起来是统一的,但权限模型、定位服务开关、位置回调和设置页都由系统提供。本文记录 geolocator 的 OHOS 适配过程,重点解释为什么必须同时处理 Dart 注册、MethodChannel、EventChannel、UIAbilityContext 和系统权限,而不是只让一个方法返回成功。
一、项目信息与适配目标
| 项目 | 内容 |
|---|---|
| 仓库名称 | geolocator |
| 上游版本 | 14.1.1 |
| OHOS 适配 TAG | 14.1.1-ohos-1.0.0-beta.1 |
| 适配分支 | main |
| 原生模块 | OHOS HAR,入口类 GeolocatorOhosPlugin |
| Dart 注册类 | GeolocatorOhos |
首个版本的目标是完整覆盖前台定位闭环:
检查定位服务
|
检查/请求定位权限
|
获取当前位置或最后位置
|
订阅位置流和服务开关流
|
在设置页中调整定位配置
后台常驻定位、Android 前台服务和精确位置的特殊审批不在首版范围内。适配遇到系统没有等价能力时会返回明确的错误码,不会把未完成的能力伪装成成功。
二、先保住上游 Dart API
上游 geolocator 已经有稳定的 Geolocator 静态 API,业务代码通常这样调用:
import 'package:geolocator/geolocator.dart';
Future<Position> loadPosition() async {
final enabled = await Geolocator.isLocationServiceEnabled();
if (!enabled) {
throw StateError('定位服务已关闭');
}
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission == LocationPermission.whileInUse ||
permission == LocationPermission.always) {
return Geolocator.getCurrentPosition();
}
throw StateError('没有定位权限');
}
OHOS 适配不重新设计 Dart API,只在主包中注册一个薄的实现类:
import 'package:geolocator_platform_interface/geolocator_platform_interface.dart';
// ignore: implementation_imports
import 'package:geolocator_platform_interface/src/implementations/method_channel_geolocator.dart';
class GeolocatorOhos extends MethodChannelGeolocator {
static void registerWith() {
GeolocatorPlatform.instance = GeolocatorOhos();
}
}
这样 Android、iOS 业务页面继续使用原来的 Geolocator 调用方式,OHOS 只替换平台实现。pubspec.yaml 必须同时声明原生入口和 Dart 注册类,否则 Flutter 工具链可能只生成了 OHOS 工程,却没有在 Dart 侧选择 OHOS 实现:
flutter:
plugin:
platforms:
ohos:
pluginClass: GeolocatorOhosPlugin
dartPluginClass: GeolocatorOhos
三、三个通道的职责
OHOS 插件注册了三个通道:
| 通道 | 用途 |
|---|---|
flutter.baseflow.com/geolocator | 权限、服务开关、单次定位、设置页 |
flutter.baseflow.com/geolocator_updates | 连续位置更新 |
flutter.baseflow.com/geolocator_service_updates | 定位服务开关变化 |
MethodChannel 适合一次请求和一次响应,位置流必须使用 EventChannel。原生入口在引擎绑定时创建通道,在引擎分离时注销 handler、停止定位回调并清空 sink,避免页面销毁后仍然持有系统回调。
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.methodChannel = new MethodChannel(
binding.getBinaryMessenger(),
METHOD_CHANNEL,
);
this.updateChannel = new EventChannel(
binding.getBinaryMessenger(),
UPDATE_CHANNEL,
);
this.serviceUpdateChannel = new EventChannel(
binding.getBinaryMessenger(),
SERVICE_UPDATE_CHANNEL,
);
this.methodChannel.setMethodCallHandler(this);
this.updateChannel.setStreamHandler(this);
this.serviceUpdateChannel.setStreamHandler(this.serviceHandler);
}
四、权限不能只看 Dart 的枚举
示例工程在 entry/src/main/module.json5 中声明近似定位权限:
"requestPermissions": [
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:location_permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
原生层使用 bundleManager.getBundleInfoForSelfSync 获取当前应用的 accessToken,再用 abilityAccessCtrl 检查权限:
private checkPermission(permission: string): boolean {
const info = bundleManager.getBundleInfoForSelfSync(0);
return abilityAccessCtrl.createAtManager().checkAccessTokenSync(
info.appInfo.accessTokenId,
permission as ESObject,
) === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
请求权限时必须持有当前 Ability 的 Context,不能只使用 ApplicationContext:
private requestPermission(result: MethodResult): void {
const context = this.uiAbilityContext;
if (context === null) {
result.error(
'ACTIVITY_MISSING',
'UIAbilityContext is not available.',
null,
);
return;
}
abilityAccessCtrl.createAtManager()
.requestPermissionsFromUser(
context,
[APPROXIMATELY_LOCATION] as ESObject,
)
.then((requestResult: ESObject): void => {
const granted =
requestResult.authResults.length > 0 &&
requestResult.authResults[0] === 0;
result.success(granted ? 3 : 0);
});
}
这里把 OHOS 的授权结果映射回上游枚举:3 表示 whileInUse,0 表示 denied。用户永久拒绝时,适配层会记录 permissionDeniedForever,下一次 checkPermission() 返回上游的 deniedForever 语义。
五、当前位置和最后位置
当前位置使用 geoLocationManager.getCurrentLocation(),最后位置使用 getLastLocation():
private getCurrentPosition(result: MethodResult): void {
geoLocationManager.getCurrentLocation()
.then((location: geoLocationManager.Location): void => {
result.success(this.positionMap(location));
})
.catch((error: ESObject): void => {
result.error(
this.locationErrorCode(error),
this.errorMessage(error),
null,
);
});
}
private getLastKnownPosition(result: MethodResult): void {
try {
result.success(
this.positionMap(geoLocationManager.getLastLocation()),
);
} catch (error) {
result.success(null);
}
}
positionMap 把 OHOS 的 Location 字段转换成上游 Position 所需的 map。OHOS 当前没有公开等价的 headingAccuracy 和 speedAccuracy,适配返回 -1 表示未知:
latitude <- Location.latitude
longitude <- Location.longitude
timestamp <- Location.timeStamp
accuracy <- Location.accuracy
altitude <- Location.altitude
heading <- Location.direction
speed <- Location.speed
headingAccuracy<- -1
speedAccuracy <- -1
isMocked <- false
最后位置不存在时返回 null,这和上游 API 对“没有缓存位置”的表达一致。
六、位置流和服务开关流
位置流监听 locationChange,服务开关流监听 locationEnabledChange。每次 onListen 都要检查权限,并防止重复订阅:
onListen(args: Any, events: EventSink): void {
if (!this.hasApproximatePermission()) {
events.error(
'PERMISSION_DENIED',
'Location permission is not granted.',
null,
);
return;
}
if (this.locationCallback !== null) {
events.error(
'LOCATION_SUBSCRIPTION_ACTIVE',
'A location stream is already active.',
null,
);
return;
}
this.locationSink = events;
this.locationCallback = (
location: geoLocationManager.Location,
): void => {
this.locationSink?.success(this.positionMap(location));
};
geoLocationManager.on(
'locationChange',
{},
this.locationCallback,
);
}
onCancel 和引擎分离时都要调用 offLocationChange,并将 callback、sink 置空。服务状态流则把 OHOS 的布尔值转换为上游的 1/0,Dart 层再映射为 ServiceStatus.enabled 和 ServiceStatus.disabled。
七、设置页与生命周期
openAppSettings() 和 openLocationSettings() 通过系统设置 Ability 打开对应页面:
context.startAbility({
bundleName: 'com.huawei.hmos.settings',
abilityName: 'com.huawei.hmos.settings.MainAbility',
uri: 'application_location_settings',
}).then((): void => result.success(true))
.catch((): void => result.success(false));
系统发行版可能调整设置页 URI,所以打开失败不会抛出未处理异常,而是返回 false。实现类实现 AbilityAware:
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.uiAbilityContext = binding.getAbility().context;
}
onDetachedFromAbility(): void {
this.uiAbilityContext = null;
}
这一步是定位权限弹窗和设置页跳转真正可用的前提。
八、测试结果
新增 Dart 测试覆盖 OHOS 注册、权限映射、服务开关和位置字段:
flutter test \
test/geolocator_ohos_test.dart \
test/ohos_plugin_metadata_test.dart
结果:All tests passed。
flutter analyze
结果:无 error。
示例工程生成 OHOS HAP 后,安装并启动:
cd geolocator/example
flutter build hap --debug
hdc install -r ohos/entry/build/default/outputs/default/entry-default-unsigned.hap
hdc shell aa start -a EntryAbility -b com.example.geolocator_example

九、能力边界与排查建议
- 首版只覆盖前台定位,不实现后台常驻定位。
always不会伪造为已授权,前台应用统一返回whileInUse语义。- 定位服务关闭、权限弹窗未完成和系统定位接口异常会返回不同错误码。
- 如果方法通道没有返回,先检查
pubspec.yaml的ohos.pluginClass、ohos.dartPluginClass、HAR 的index.ets和 AbilityContext 是否绑定。 - 如果权限一直 denied,检查应用工程的
module.json5、权限 reason 字符串和系统设置中的授权状态。
社区
- OpenHarmony Flutter 社区:https://atomgit.com/oh-flutter
- CPF-Flutter 社区:https://atomgit.com/CPF-Flutter
欢迎提交设备型号、系统版本、复现步骤和 hilog 日志,一起完善 OHOS 定位适配。
更多推荐



所有评论(0)