HarmonyOS 获取设备位置信息开发指导
HarmonyOS 获取设备位置信息开发指导
场景概述
开发者可以调用HarmonyOS位置相关接口,获取设备实时位置,或者最近的历史位置,以及监听设备的位置变化。
对于位置敏感的应用业务,建议获取设备实时位置信息。如果不需要设备实时位置信息,并且希望尽可能的节省耗电,开发者可以考虑获取最近的历史位置。
接口说明
获取设备的位置信息所使用的接口如下,详细说明参见:Location Kit。
注意:本模块能力仅支持WGS-84坐标系。
接口名 | 功能描述 |
---|---|
on(type: ‘locationChange’, request: LocationRequest | ContinuousLocationRequest, callback: Callback): void | 开启位置变化订阅,并发起定位请求 |
off(type: ‘locationChange’, callback?: Callback): void | 关闭位置变化订阅,并删除对应的定位请求 |
getCurrentLocation(request: CurrentLocationRequest | SingleLocationRequest, callback: AsyncCallback): void | 获取当前位置,使用callback回调异步返回结果 |
getCurrentLocation(request?: CurrentLocationRequest | SingleLocationRequest): Promise | 获取当前位置,使用Promise方式异步返回结果 |
getLastLocation(): Location | 获取最近一次定位结果 |
isLocationEnabled(): boolean | 判断位置服务是否已经开启 |
开发步骤
1. 权限申请
获取设备的位置信息,需要有位置权限,位置权限申请的方法和步骤见申请位置权限开发指导。
2. 导入模块
导入geoLocationManager模块,所有与基础定位能力相关的功能API,都是通过该模块提供的。
import { geoLocationManager } from '@kit.LocationKit';
3. 检查位置开关状态
调用获取位置接口之前需要先判断位置开关是否打开。
查询当前位置开关状态,返回结果为布尔值,true代表位置开关开启,false代表位置开关关闭。
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';try {let locationEnabled = geoLocationManager.isLocationEnabled();if (!locationEnabled) {console.warn('Location service is disabled');// 可以拉起全局开关设置弹框,引导用户打开位置开关}
} catch (err) {console.error("errCode:" + err.code + ", message:" + err.message);
}
如果位置开关未开启,可以拉起全局开关设置弹框,引导用户打开位置开关。
单次获取位置
单次获取当前设备位置。多用于查看当前位置、签到打卡、服务推荐等场景。
方式一:获取系统缓存的最新位置
如果系统当前没有缓存位置会返回错误码。推荐优先使用该接口获取位置,可以减少系统功耗。
如果对位置的新鲜度比较敏感,可以先获取缓存位置,将位置中的时间戳与当前时间对比,若新鲜度不满足预期可以使用方式二获取位置。
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';try {let location = geoLocationManager.getLastLocation();console.info('Last location:', JSON.stringify(location));// 检查位置新鲜度const currentTime = Date.now();const locationTime = location.timeStamp;const timeDiff = currentTime - locationTime;if (timeDiff > 5 * 60 * 1000) { // 5分钟console.warn('Location is too old, consider getting fresh location');}
} catch (err) {console.error("errCode:" + JSON.stringify(err));
}
方式二:获取当前位置
首先要实例化SingleLocationRequest对象,用于告知系统该向应用提供何种类型的位置服务,以及单次定位超时时间。
设置LocatingPriority
- PRIORITY_ACCURACY:如果对位置的返回精度要求较高,建议优先选择此参数,会将一段时间内精度较好的结果返回给应用
- PRIORITY_LOCATING_SPEED:如果对定位速度要求较高,建议选择此参数,会将最先拿到的定位结果返回给应用
两种定位策略均会同时使用GNSS定位和网络定位技术,以便在室内和户外场景下均可以获取到位置结果,对设备的硬件资源消耗较大,功耗也较大。
设置locatingTimeoutMs
因为设备环境、设备所处状态、系统功耗管控策略等的影响,定位返回的时延会有较大波动,建议把单次定位超时时间设置为10秒。
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';// 快速定位策略示例
let request: geoLocationManager.SingleLocationRequest = {'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,'locatingTimeoutMs': 10000
};try {geoLocationManager.getCurrentLocation(request).then((result) => {console.info('Current location: ' + JSON.stringify(result));this.handleLocationResult(result);}).catch((error: BusinessError) => {console.error('Promise, getCurrentLocation: error=' + JSON.stringify(error));this.handleLocationError(error);});
} catch (err) {console.error("errCode:" + JSON.stringify(err));
}// 高精度定位策略示例
let accuracyRequest: geoLocationManager.SingleLocationRequest = {'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_ACCURACY,'locatingTimeoutMs': 10000
};try {geoLocationManager.getCurrentLocation(accuracyRequest).then((result) => {console.info('Accurate location: ' + JSON.stringify(result));this.handleAccurateLocationResult(result);}).catch((error: BusinessError) => {console.error('Promise, getCurrentLocation: error=' + JSON.stringify(error));});
} catch (err) {console.error("errCode:" + JSON.stringify(err));
}
坐标系说明
通过本模块获取到的坐标均为WGS-84坐标系坐标点,如需使用其它坐标系类型的坐标点,请进行坐标系转换后再使用。
可参考Map Kit提供的地图计算工具进行坐标转换。
持续定位
持续定位。多用于导航、运动轨迹、出行等场景。
设置定位参数
首先要实例化ContinuousLocationRequest对象,用于告知系统该向应用提供何种类型的位置服务,以及位置结果上报的频率。
设置locationScenario
建议locationScenario参数优先根据应用的使用场景进行设置,该参数枚举值定义参见UserActivityScenario,例如地图在导航时使用NAVIGATION参数,可以持续在室内和室外场景获取位置用于导航。
设置interval
表示上报位置信息的时间间隔,单位是秒,默认值为1秒。如果对位置上报时间间隔无特殊要求,可以不填写该字段。
实现示例
地图导航场景
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';export class NavigationLocationService {private locationCallback: ((location: geoLocationManager.Location) => void) | null = null;startNavigationLocation(): void {let request: geoLocationManager.ContinuousLocationRequest = {'interval': 1,'locationScenario': geoLocationManager.UserActivityScenario.NAVIGATION};this.locationCallback = (location: geoLocationManager.Location): void => {console.info('Navigation location update: ' + JSON.stringify(location));this.handleNavigationLocation(location);};try {geoLocationManager.on('locationChange', request, this.locationCallback);console.info('Navigation location service started');} catch (err) {console.error("errCode:" + JSON.stringify(err));}}stopNavigationLocation(): void {if (this.locationCallback) {try {geoLocationManager.off('locationChange', this.locationCallback);this.locationCallback = null;console.info('Navigation location service stopped');} catch (err) {console.error("errCode:" + JSON.stringify(err));}}}private handleNavigationLocation(location: geoLocationManager.Location): void {// 处理导航位置更新console.info(`Navigation - Latitude: ${location.latitude}, Longitude: ${location.longitude}`);}
}
运动轨迹记录场景
import { geoLocationManager } from '@kit.LocationKit';export class FitnessTrackingService {private locationCallback: ((location: geoLocationManager.Location) => void) | null = null;private locationHistory: geoLocationManager.Location[] = [];startFitnessTracking(): void {let request: geoLocationManager.ContinuousLocationRequest = {'interval': 3, // 3秒更新一次,适合运动记录'locationScenario': geoLocationManager.UserActivityScenario.FITNESS};this.locationCallback = (location: geoLocationManager.Location): void => {console.info('Fitness location update: ' + JSON.stringify(location));this.recordLocation(location);};try {geoLocationManager.on('locationChange', request, this.locationCallback);console.info('Fitness tracking started');} catch (err) {console.error("errCode:" + JSON.stringify(err));}}stopFitnessTracking(): void {if (this.locationCallback) {try {geoLocationManager.off('locationChange', this.locationCallback);this.locationCallback = null;console.info('Fitness tracking stopped');this.saveLocationHistory();} catch (err) {console.error("errCode:" + JSON.stringify(err));}}}private recordLocation(location: geoLocationManager.Location): void {this.locationHistory.push(location);console.info(`Recorded location ${this.locationHistory.length}: ${location.latitude}, ${location.longitude}`);}private saveLocationHistory(): void {console.info(`Saved ${this.locationHistory.length} location points`);// 保存位置历史到本地存储或云端}
}
出行服务场景
import { geoLocationManager } from '@kit.LocationKit';export class TravelLocationService {private locationCallback: ((location: geoLocationManager.Location) => void) | null = null;startTravelLocation(): void {let request: geoLocationManager.ContinuousLocationRequest = {'interval': 5, // 5秒更新一次,适合出行服务'locationScenario': geoLocationManager.UserActivityScenario.TRAVEL};this.locationCallback = (location: geoLocationManager.Location): void => {console.info('Travel location update: ' + JSON.stringify(location));this.handleTravelLocation(location);};try {geoLocationManager.on('locationChange', request, this.locationCallback);console.info('Travel location service started');} catch (err) {console.error("errCode:" + JSON.stringify(err));}}stopTravelLocation(): void {if (this.locationCallback) {try {geoLocationManager.off('locationChange', this.locationCallback);this.locationCallback = null;console.info('Travel location service stopped');} catch (err) {console.error("errCode:" + JSON.stringify(err));}}}private handleTravelLocation(location: geoLocationManager.Location): void {// 处理出行位置更新console.info(`Travel - Latitude: ${location.latitude}, Longitude: ${location.longitude}`);// 可以用于打车服务、路线规划等}
}
位置信息处理
位置数据结构
interface Location {latitude: number; // 纬度longitude: number; // 经度altitude: number; // 海拔高度accuracy: number; // 精度timeStamp: number; // 时间戳direction: number; // 方向speed: number; // 速度timeSinceBoot: number; // 系统启动后的时间
}
位置信息处理示例
export class LocationDataProcessor {processLocation(location: geoLocationManager.Location): void {// 检查位置有效性if (!this.isValidLocation(location)) {console.warn('Invalid location data');return;}// 格式化位置信息const locationInfo = {coordinates: {latitude: location.latitude,longitude: location.longitude},accuracy: location.accuracy,timestamp: new Date(location.timeStamp),speed: location.speed,direction: location.direction};console.info('Processed location:', JSON.stringify(locationInfo));}private isValidLocation(location: geoLocationManager.Location): boolean {return location.latitude !== 0 && location.longitude !== 0 && location.accuracy > 0;}calculateDistance(location1: geoLocationManager.Location, location2: geoLocationManager.Location): number {// 使用Haversine公式计算两点间距离const R = 6371; // 地球半径(公里)const dLat = this.toRadians(location2.latitude - location1.latitude);const dLon = this.toRadians(location2.longitude - location1.longitude);const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +Math.cos(this.toRadians(location1.latitude)) * Math.cos(this.toRadians(location2.latitude)) *Math.sin(dLon / 2) * Math.sin(dLon / 2);const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));return R * c;}private toRadians(degrees: number): number {return degrees * (Math.PI / 180);}
}
错误处理
常见错误码
export class LocationErrorHandler {static handleLocationError(error: BusinessError): void {switch (error.code) {case 201:console.error('Location permission denied');this.handlePermissionError();break;case 202:console.error('Location service unavailable');this.handleServiceUnavailable();break;case 203:console.error('Location timeout');this.handleTimeoutError();break;case 204:console.error('Location service busy');this.handleServiceBusy();break;default:console.error('Unknown location error:', error);this.handleUnknownError(error);}}private static handlePermissionError(): void {// 处理权限错误console.warn('Please grant location permission');}private static handleServiceUnavailable(): void {// 处理服务不可用错误console.warn('Location service is not available');}private static handleTimeoutError(): void {// 处理超时错误console.warn('Location request timeout');}private static handleServiceBusy(): void {// 处理服务繁忙错误console.warn('Location service is busy');}private static handleUnknownError(error: BusinessError): void {// 处理未知错误console.error('Unknown error occurred:', error);}
}
最佳实践
1. 电池优化
export class LocationBatteryOptimizer {private isInBackground: boolean = false;setBackgroundMode(inBackground: boolean): void {this.isInBackground = inBackground;this.optimizeLocationRequest();}private optimizeLocationRequest(): void {if (this.isInBackground) {// 后台时降低定位频率this.updateLocationInterval(10); // 10秒间隔} else {// 前台时正常频率this.updateLocationInterval(1); // 1秒间隔}}private updateLocationInterval(interval: number): void {// 更新定位间隔console.info(`Updated location interval to ${interval} seconds`);}
}
2. 位置缓存管理
export class LocationCacheManager {private static readonly CACHE_DURATION = 5 * 60 * 1000; // 5分钟static isLocationFresh(location: geoLocationManager.Location): boolean {const currentTime = Date.now();const locationTime = location.timeStamp;return (currentTime - locationTime) < this.CACHE_DURATION;}static getOptimalLocation(): Promise<geoLocationManager.Location> {return new Promise((resolve, reject) => {try {// 先尝试获取缓存位置const cachedLocation = geoLocationManager.getLastLocation();if (this.isLocationFresh(cachedLocation)) {resolve(cachedLocation);return;}} catch (error) {console.warn('No cached location available');}// 缓存位置不可用或过期,获取新位置const request: geoLocationManager.SingleLocationRequest = {'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,'locatingTimeoutMs': 5000};geoLocationManager.getCurrentLocation(request).then(resolve).catch(reject);});}
}
注意事项
- 及时停止定位:如果不主动结束定位可能导致设备功耗高,耗电快;建议在不需要获取定位信息时及时结束定位
- 权限检查:在获取位置前确保已获得相应权限
- 位置开关检查:确保系统位置开关已开启
- 坐标系转换:注意WGS-84坐标系与其他坐标系的转换
- 错误处理:妥善处理定位过程中的各种异常情况
- 电池优化:根据应用场景选择合适的定位策略和频率
- 隐私保护:合理使用位置信息,保护用户隐私