ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

HarmonyOS社交通讯应用开发21 :定位与逆地理编码

2026/8/25 19:06:44 拓冰建站 浏览量
HarmonyOS社交通讯应用开发21 :定位与逆地理编码 定位与逆地理编码引言上一篇看到BottomToolbar有个位置列表——这些位置不是写死的假数据而是真定位出来的。用户进入发布页BottomToolbar.aboutToAppear申请定位权限后开启监听拿到经纬度后调用逆地理编码把32.05°N, 118.78°E这样的坐标翻译成南京市玄武区XX路XX号这样人类可读的地址再填入位置候选列表。实现这套能力的工具类是LocationUtilentry/src/main/ets/utils/LocationUtil.ets它把geoLocationManager的注册/注销、单次/连续定位封装成三个简洁的方法消费方是BottomToolbar的location回调负责逆地理编码、按系统语言切换 locale、并把结果刷进位置列表。本篇围绕一条数据链展开权限 → 注册定位监听 → 经纬度回调 → 逆地理编码 → 地址列表 → 选中展示。知识点讲解geoLocationManager定位服务入口geoLocationManagerkit.LocationKit是系统定位能力的统一入口常用三类接口on(locationChange, request, callback)注册连续定位监听按请求参数持续回调位置getCurrentLocation(callback)单次定位立即返回当前位置off(locationChange)注销监听停止定位省电关键。LocationRequest定位参数连续定位需要一份LocationRequest配置常用字段priority定位优先级。0x203 对应PRIORITY_ACCURACY高精度优先 GPS/网络综合数字写法来自枚举的数值scenario定位场景。0x300 对应SCENE_NAVIGATION导航场景系统据此调整定位策略timeInterval两次回调的最小时间间隔毫秒distanceInterval移动多少米才再次上报maxAccuracy期望精度0 表示不限制。实际开发中更推荐直接引用geoLocationManager.LocationRequestPriority/LocationRequestScenario等枚举常量本项目用数字字面量是示例代码的写法含义相同。逆地理编码坐标 → 地址逆地理编码接口是geoLocationManager.getAddressesFromLocation(request)请求结构ReverseGeoCodeRequest的关键字段latitude/longitude要解析的坐标locale返回地址的语言如 zh / en决定地址是中文还是英文。返回的GeoAddress[]是一串地址描述字段按行政层级拆开placeName地点名如街道名/地标、administrativeArea省/州、subAdministrativeArea市、subLocality区/县等。本项目把这几项依次 push 进位置列表形成街道、省、市、区一组候选。locale 与系统语言联动地址文案的语言应跟随系统语言。i18n.System.getAppPreferredLanguage()返回应用当前偏好语言如 zh-CN、en-US把包含 zh 的语言标识映射成 zh否则用 en即可让逆地理编码返回对应语言的地址。当系统语言切换时本项目用Watch监听 AppStorage 的 language 键变化重新触发定位解析见下文源码。结合本项目源码分析工具层LocationUtil 的三个方法文件路径entry/src/main/ets/utils/LocationUtil.ets。classLocationUtil{// 注册连续定位监听geolocationOn(locationChange:(location: geoLocationManager.Location) void):void{letrequestInfo: geoLocationManager.LocationRequest {priority:0x203,// 高精度优先scenario:0x300,// 导航场景timeInterval:100,// 最小上报间隔 100msdistanceInterval:0,// 不做距离过滤maxAccuracy:0// 不限制精度};try{ geoLocationManager.on(locationChange, requestInfo, locationChange); }catch(err) { hilog.error(DOMAIN,TAG,FORMAT,On locationChange failed. Cause code:${err.code}, message:${err.message}); } }// 单次定位getCurrentLocation(locationChange: (err: BusinessError, location: geoLocationManager.Location) void) { geoLocationManager.getCurrentLocation(locationChange); }// 注销定位监听geolocationOff():void{try{ geoLocationManager.off(locationChange); }catch(err) { hilog.error(DOMAIN,TAG,FORMAT,Off locationChange failed. Cause code:${err.code}, message:${err.message}); } } }// 单例导出全应用共用一个实例letlocationUtil newLocationUtil();exportdefaultlocationUtilasLocationUtil;设计要点类 单例导出export default locationUtil任何组件import LocationUtil from ../../utils/LocationUtil拿到的都是同一个实例方法内部 try/catch 包裹系统调用失败只记日志不向上抛——把定位可能失败的复杂度挡在工具层调用方只需关心成功回调。消费层BottomToolbar 的定位回调文件路径entry/src/main/ets/view/contentEditor/BottomToolbar.ets。核心是location方法——它同时是连续定位回调和单次定位回调的公共处理函数privatelocation(location: geoLocationManager.Location) {try{this.latitude location.latitude;this.longitude location.longitude;// 构造逆地理编码请求locale 跟随系统语言含 zh → zh否则 enlet reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest {locale:this.locale.toString().includes(zh) ?zh:en,latitude:this.latitude,longitude:this.longitude };// 逆地理编码坐标 → 地址列表geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest).then(data { hilog.info(DOMAIN, TAG, FORMAT,GetAddressesFromLocation data :, JSON.stringify(data));if(data.length !0) {this.currentLocalInfo [];// 清空默认示例位置// 按行政层级展开成候选列表地点名、省、市、区data.forEach((item) {this.currentLocalInfo.push(item.placeName!);this.currentLocalInfo.push(item.administrativeArea!);this.currentLocalInfo.push(item.subAdministrativeArea!);this.currentLocalInfo.push(item.subLocality!); }) }// 取第一个地址的地点名作为当前定位展示if(data[0].placeName) {this.currentLocation data[0].placeName; }this.isDefaultLocation false;// 已有真实定位不再显示默认示例}).catch((err: BusinessError) { hilog.error(DOMAIN, TAG, FORMAT,GetAddressesFromLocation err: JSON.stringify(err)); }); }catch(err) { hilog.error(DOMAIN, TAG, FORMAT, GetAddressesFromLocation failed. err: ${(errasBusinessError).code} ${(errasBusinessError).message}); } }细节逐条看先存坐标再解析经纬度存进this.latitude/longitude后面语言切换时systemLanguage会复用它触发重新解析。locale 跟随系统语言this.locale在组件声明处初始化i18n.System.getAppPreferredLanguage()includes(zh)判断中文环境返回中文地址还是英文地址由系统语言决定——这是国际化在数据层的最小实现。地址展开成候选currentLocalInfo原本是CommonConstants.CURRENT_LOCAL_INFO10 条 Building %d, XX Road, Wuhan 示例数据拿到真实结果后清空重建每个GeoAddress的placeName地点、administrativeArea省、subAdministrativeArea市、subLocality区依次 push——这也是上一篇位置列表能显示多层地址的原因。默认位置标记isDefaultLocation false后位置行主文本从示例地址切换到截断显示的真实地址characterElision这个开关贯穿了两篇的 UI 逻辑。location被两个箭头函数包装分别适配连续定位与单次定位两种回调签名// 连续定位回调无 err 参数privategetGeolocationOn:(location:geoLocationManager.Location) void (location:geoLocationManager.Location) { this.location(location); };// 单次定位回调先 err 后 locationprivategetGeolocation:(err: BusinessError,location:geoLocationManager.Location) void (err: BusinessError,location:geoLocationManager.Location) {if(err) { return;// 单次定位失败直接忽略} this.location(location); };生命周期与语言联动aboutToAppear(): void { this.requestPermissions();// 申请定位权限下一篇详讲} aboutToDisappear(): void {LocationUtil.geolocationOff();// 页面消失停止定位监听}// Watch 回调系统语言变化时重新定位解析systemLanguage(): void {if(this.latitude ! undefinedthis.longitude ! undefined) { this.locale i18n.System.getAppPreferredLanguage();// 更新语言LocationUtil.getCurrentLocation(this.getGeolocation);// 单次定位重新解析} }systemLanguage由Watch(systemLanguage) StorageLink(CommonConstants.LANGUAGE) systemLanguages触发应用语言切换 → AppStorage language 键变化 → 组件重新执行systemLanguage→ 若已有坐标说明曾定位成功就更新 locale 并单次定位重跑一遍逆地理编码地址列表随即刷新成新语言。这套语言 → 定位 → 地址的联动链条是本项目国际化设计里最精彩的一环。权限从哪来requestPermissions在aboutToAppear里调用成功后LocationUtil.geolocationOn(this.getGeolocationOn)开启连续定位——权限成功是定位的起点requestPermissions(): void {letatManager abilityAccessCtrl.createAtManager();try{ atManager.requestPermissionsFromUser(this.context, CommonConstants.REQUEST_PERMISSIONS).then(() {LocationUtil.geolocationOn(this.getGeolocationOn);// 授权成功才开启定位}).catch((err: BusinessError) {...}); } catch (err) {...} }完整的权限申请机制是下一篇的主角这里先记住结论REQUEST_PERMISSIONS声明了APPROXIMATELY_LOCATION模糊定位与LOCATION精确定位两个权限都授予后才启动定位监听。参数取舍与常见疑问LocationRequest里几个看似随意的数字其实各有讲究timeInterval 100两次位置回调的最小间隔 100ms意味着系统上报频率最高可达每秒 10 次。对展示当前位置这种低频需求而言明显偏密——示例工程刻意用了高频率方便在真机上直观看到位置变化实际产品应根据业务调整到 1000~5000ms兼顾流畅与省电。distanceInterval 0不做移动距离过滤只要满足时间间隔就上报。若设为 2000则只有移动超过 2 公里才回调适合大范围位移才更新的场景。maxAccuracy 0不限制期望精度交给系统按priority自行权衡。两个容易混淆的接口选择连续定位 vs 单次定位。on(locationChange)持续上报适合需要跟随用户移动的场景本项目发布页展示当前位置即属此类getCurrentLocation只取一次值适合打开页面定位一次的场景——本项目在语言切换时正是用单次定位快速刷新地址。而geolocationOff()不传参数即注销全部监听防止重复注册导致回调堆积aboutToDisappear里调用它保证页面退栈后定位服务及时关闭不留后台空转的隐患。最后提醒权限与定位的联动location回调若在未授权时被触发getAddressesFromLocation会抛错进入 catch代码里所有定位相关操作都留有日志出口调试时查看 hilog 即可定位问题——这也是整个模块三反复出现的每个异步环节都要有错误出口工程习惯在定位场景的体现。数据流全景把工具层与消费层串起来发布页定位的完整数据链是aboutToAppear→ requestPermissions弹授权框 → 用户授权 → LocationUtil.geolocationOn注册连续定位 → geoLocationManager.on(locationChange) → Location 回调经纬度 → location() → getAddressesFromLocation逆地理编码 → GeoAddress[] → 展开 placeName/省/市/区 → currentLocalInfo 刷新列表 → 用户点选列表项 →selectLocalInfo / isAddLocalInfo → 位置行展示 → aboutToDisappear → geolocationOff停止监听小结定位与逆地理编码是发布页里能力最重的功能之一但工程把它封装得举重若轻工具层单例LocationUtil三个方法on/off/单次把geoLocationManager的复杂度隔离在utils目录消费方只写业务回调统一location方法同时服务连续定位与单次定位两种回调签名一份处理逻辑两处复用语言联动locale跟随系统语言、Watch监听语言切换自动重定位让切换系统语言 → 地址变语言顺滑发生生命周期闭环aboutToAppear开启、aboutToDisappear关闭定位监听不泄漏省电且规范。下一篇我们专门拆解这最后一环——权限申请机制看看requestPermissionsFromUser与module.json5声明如何配合。本文引用源码entry/src/main/ets/utils/LocationUtil.ets、entry/src/main/ets/view/contentEditor/BottomToolbar.ets、entry/src/main/ets/constants/CommonConstants.ets