/** * 定位能力(契约 §8.6/§9/§10.8,T-M3-09)。@kit.LocationKit geoLocationManager。 * - startlocation:data="1" 连续 / 其他 单次。用时申请定位权限;定位成功逆地理编码后出站 getlocationinfo。 * - getlocationinfo:同步返回最近一次 MaplocationInfo(未就绪 errorCode=-1 / 权限拒绝 -2)。 * 需 module.json5 声明 LOCATION + APPROXIMATELY_LOCATION,运行时申请。 */ import { geoLocationManager } from '@kit.LocationKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { common, Permissions } from '@kit.AbilityKit'; import { PermissionGuard } from 'platform'; import { BridgeController } from 'feature_bridge'; import { InboundHandlers, OutboundHandlers, MaplocationInfo } from 'contracts'; import { Logger } from 'common'; import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider'; const LOC_PERMS: Permissions[] = ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION']; export class LocationProvider implements CapabilityProvider { readonly name: string = 'location'; private readonly log: Logger = Logger.tag('LocationProvider'); private bridge: BridgeController | undefined = undefined; private context: common.UIAbilityContext | undefined = undefined; private lastInfo: MaplocationInfo | undefined = undefined; private continuousOn: boolean = false; private wantContinuous: boolean = false; private disposed: boolean = false; private readonly onLocation: (loc: geoLocationManager.Location) => void; constructor() { this.onLocation = (loc: geoLocationManager.Location) => this.handleLocation(loc); } register(bridge: BridgeController, ctx: CapabilityContext): void { this.bridge = bridge; this.context = ctx.uiAbilityContext; bridge.registerHandler(InboundHandlers.StartLocation, (data: string, _cb: (resp: string) => void) => { this.start(data === '1'); }); bridge.registerHandler(InboundHandlers.GetLocationInfo, (_d: string, cb: (resp: string) => void) => { cb(JSON.stringify(this.lastInfo ?? this.errorInfo(-1))); }); } /** 槽激活:恢复连续定位(若 H5 仍要)。 */ onForeground(): void { if (this.wantContinuous) { this.startContinuous(); } } /** 槽去激活:停连续定位监听(保留 H5 意图)。 */ onBackground(): void { this.stopContinuous(); } onDestroy(): void { this.disposed = true; this.wantContinuous = false; this.stopContinuous(); } private async start(continuous: boolean): Promise { const ctx = this.context; if (ctx === undefined) { return; } const granted: boolean = await PermissionGuard.request(ctx, LOC_PERMS); // 权限对话框期间槽可能已销毁:勿在 onDestroy 后再注册监听(否则泄漏 + 打已弃桥) if (this.disposed) { return; } if (!granted) { this.lastInfo = this.errorInfo(-2); this.push(this.lastInfo); return; } this.wantContinuous = continuous; if (continuous) { this.startContinuous(); } else { // 单次请求前,若已有连续监听在跑则先停,避免单次后仍持续推送(H5 预期一次) this.stopContinuous(); this.requestSingle(); } } private requestSingle(): void { const request: geoLocationManager.CurrentLocationRequest = { priority: geoLocationManager.LocationRequestPriority.FIRST_FIX, scenario: geoLocationManager.LocationRequestScenario.UNSET, }; geoLocationManager.getCurrentLocation(request) .then((loc: geoLocationManager.Location) => this.handleLocation(loc)) .catch((e: BusinessError) => { this.log.w(`getCurrentLocation failed: ${e.code} ${e.message}`); this.lastInfo = this.errorInfo(e.code); this.push(this.lastInfo); }); } private startContinuous(): void { if (this.continuousOn) { return; } const request: geoLocationManager.LocationRequest = { priority: geoLocationManager.LocationRequestPriority.FIRST_FIX, scenario: geoLocationManager.LocationRequestScenario.UNSET, timeInterval: 5, distanceInterval: 0, maxAccuracy: 0, }; try { geoLocationManager.on('locationChange', request, this.onLocation); this.continuousOn = true; } catch (e) { this.log.w(`on locationChange failed: ${(e as BusinessError).message}`); } } private stopContinuous(): void { if (!this.continuousOn) { return; } try { geoLocationManager.off('locationChange', this.onLocation); } catch (e) { this.log.w(`off locationChange failed: ${(e as BusinessError).message}`); } this.continuousOn = false; } /** 定位成功 → 逆地理编码(best-effort)→ 构建 MaplocationInfo → 出站。 */ private handleLocation(loc: geoLocationManager.Location): void { const base: MaplocationInfo = this.baseInfo(loc); const req: geoLocationManager.ReverseGeoCodeRequest = { latitude: loc.latitude, longitude: loc.longitude, maxItems: 1, }; geoLocationManager.getAddressesFromLocation(req) .then((addrs: geoLocationManager.GeoAddress[]) => { if (addrs.length > 0) { this.fillAddress(base, addrs[0]); } this.lastInfo = base; this.push(base); }) .catch((e: BusinessError) => { this.log.i(`reverse geocode failed (${e.code}); push coords only`); this.lastInfo = base; this.push(base); }); } private baseInfo(loc: geoLocationManager.Location): MaplocationInfo { return { latitude: loc.latitude, longitude: loc.longitude, address: '', country: '', province: '', city: '', district: '', street: '', cityCode: '', streetNum: '', adCode: '', aoiName: '', accuracy: loc.accuracy, locationType: 0, errorCode: 0, errorMsg: '', }; } private fillAddress(info: MaplocationInfo, a: geoLocationManager.GeoAddress): void { info.address = a.placeName ?? ''; info.country = a.countryName ?? ''; info.province = a.administrativeArea ?? ''; info.city = a.locality ?? ''; info.district = a.subLocality ?? ''; info.street = a.roadName ?? ''; } private errorInfo(code: number): MaplocationInfo { return { latitude: 0, longitude: 0, address: '', country: '', province: '', city: '', district: '', street: '', cityCode: '', streetNum: '', adCode: '', aoiName: '', accuracy: 0, locationType: 0, errorCode: code, errorMsg: code === -2 ? 'permission denied' : (code === -1 ? 'not ready' : 'location error'), }; } private push(info: MaplocationInfo): void { // 单次定位用 getCurrentLocation(一次性,无监听可停);连续定位由 stopshake 等价的 stopContinuous 管理。 this.bridge?.callHandler(OutboundHandlers.GetLocationInfo, JSON.stringify(info)); } }