定位服务
HarmonyOS 提供完善的定位服务,支持获取当前位置、持续定位、地理编码与反编码、地理围栏等功能。
前置配置
module.json5 权限声明
json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:reason_location",
"usedScene": {}
},
{
"name": "ohos.permission.LOCATION",
"reason": "$string:reason_precise_location",
"usedScene": {}
}
]
}
}| 权限 | 说明 | 是否动态申请 |
|---|---|---|
ohos.permission.APPROXIMATELY_LOCATION | 获取大概位置 | 是 |
ohos.permission.LOCATION | 获取精确位置 | 是 |
提示
APPROXIMATELY_LOCATION为必选权限,申请精确位置前必须先申请大概位置权限- 两个权限都需要在运行时动态申请
API 列表
| API | 说明 | 起始版本 |
|---|---|---|
geoLocationManager.getCurrentLocation() | 获取当前位置(单次) | API 9 |
geoLocationManager.on('locationChange') | 监听位置变化 | API 9 |
geoLocationManager.off('locationChange') | 取消监听位置变化 | API 9 |
geoLocationManager.getLastLocation() | 获取上次定位结果 | API 9 |
geoLocationManager.isLocationEnabled() | 判断定位是否开启 | API 9 |
geoLocationManager.requestEnableLocation() | 请求开启定位 | API 9 |
geoLocationManager.getAddressesFromLocation() | 地理反编码 | API 9 |
geoLocationManager.getAddressesFromLocationName() | 地理编码 | API 9 |
geoLocationManager.on('gnssStatusChange') | 监听卫星状态变化 | API 9 |
geoLocationManager.getCachedGnssLocationsSize() | 获取缓存 GNSS 位置数量 | API 9 |
定位权限申请
在使用定位服务前,需要先申请定位权限:
typescript
import { abilityAccessCtrl, PermissionRequestResult } from '@kit.AbilityKit'
async function requestLocationPermission(): Promise<boolean> {
const permissions: Array<string> = [
'ohos.permission.APPROXIMATELY_LOCATION',
'ohos.permission.LOCATION'
]
const atManager = abilityAccessCtrl.createAtManager()
try {
const result: PermissionRequestResult = await atManager.requestPermissionsFromUser(
getContext(),
permissions
)
// 检查所有权限是否都已授权
const allGranted = result.authResults.every(result => result === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
return allGranted
} catch (error) {
console.error('权限申请失败:', JSON.stringify(error))
return false
}
}单次定位
获取当前位置信息:
typescript
import { geoLocationManager } from '@kit.LocationKit'
async function getCurrentPosition(): Promise<void> {
try {
// 检查定位是否开启
const isEnabled = geoLocationManager.isLocationEnabled()
if (!isEnabled) {
console.info('定位服务未开启,请求开启')
await geoLocationManager.requestEnableLocation()
return
}
// 配置定位请求参数
const requestInfo: geoLocationManager.SingleLocationRequest = {
locatingPriority: geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
locatingTimeoutMs: 10000
}
// 获取当前位置
const location = await geoLocationManager.getCurrentLocation(requestInfo)
console.info('定位成功:')
console.info(' 经度:', location.longitude)
console.info(' 纬度:', location.latitude)
console.info(' 海拔:', location.altitude)
console.info(' 精度:', location.accuracy)
console.info(' 方向:', location.direction)
console.info(' 速度:', location.speed)
console.info(' 时间:', new Date(location.timeStamp).toLocaleString())
} catch (error) {
console.error('定位失败:', JSON.stringify(error))
}
}LocatingPriority 枚举
| 枚举值 | 说明 |
|---|---|
PRIORITY_LOCATING_SPEED | 优先速度,尽快返回定位结果 |
PRIORITY_LOCATING_ACCURACY | 优先精度,返回更精确的定位结果 |
持续定位
持续监听位置变化:
typescript
import { geoLocationManager } from '@kit.LocationKit'
class LocationTracker {
private locationCallback: ((location: geoLocationManager.Location) => void) | null = null
// 开始持续定位
startTracking(callback: (location: geoLocationManager.Location) => void): void {
this.locationCallback = callback
const requestInfo: geoLocationManager.ContinuousLocationRequest = {
interval: 1000, // 定位间隔,单位毫秒
locationScenario: geoLocationManager.UserActivityScenario.NAVIGATION
}
try {
geoLocationManager.on('locationChange', requestInfo, callback)
console.info('开始持续定位')
} catch (error) {
console.error('开启定位失败:', JSON.stringify(error))
}
}
// 停止持续定位
stopTracking(): void {
if (this.locationCallback) {
geoLocationManager.off('locationChange', this.locationCallback)
this.locationCallback = null
console.info('停止持续定位')
}
}
}
// 使用示例
const tracker = new LocationTracker()
// 开始追踪
tracker.startTracking((location) => {
console.info(`位置更新: 经度 ${location.longitude}, 纬度 ${location.latitude}`)
})
// 停止追踪(例如在页面销毁时)
// tracker.stopTracking()UserActivityScenario 枚举
| 枚举值 | 说明 | 适用场景 |
|---|---|---|
DAILY_LIFE_SERVICE | 日常生活服务 | 社交、天气 |
TRANSPORT | 交通出行 | 公交、地铁 |
NAVIGATION | 导航 | 驾车导航 |
SPORT | 运动健身 | 跑步、骑行 |
HIKING | 徒步旅行 | 户外徒步 |
地理编码与反编码
地理反编码(坐标转地址)
typescript
import { geoLocationManager } from '@kit.LocationKit'
async function reverseGeocode(latitude: number, longitude: number): Promise<void> {
try {
const reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest = {
latitude,
longitude,
maxItems: 5 // 返回结果的最大数量
}
const addresses = await geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest)
if (addresses.length > 0) {
const address = addresses[0]
console.info('反编码结果:')
console.info(' 国家:', address.countryName)
console.info(' 省份:', address.administrativeArea)
console.info(' 城市:', address.locality)
console.info(' 区县:', address.subLocality)
console.info(' 街道:', address.roadName)
console.info(' 门牌号:', address.subRoadName)
console.info(' 详细地址:', address.placeName)
console.info(' 邮编:', address.postalCode)
console.info(' 电话:', address.phoneNumber)
} else {
console.info('未找到地址信息')
}
} catch (error) {
console.error('反编码失败:', JSON.stringify(error))
}
}
// 使用示例
reverseGeocode(39.9042, 116.4074) // 北京天安门地理编码(地址转坐标)
typescript
import { geoLocationManager } from '@kit.LocationKit'
async function geocodeAddress(address: string): Promise<void> {
try {
const geocodeRequest: geoLocationManager.GeoCodeRequest = {
description: address,
maxItems: 5
}
const locations = await geoLocationManager.getAddressesFromLocationName(geocodeRequest)
if (locations.length > 0) {
locations.forEach((loc, index) => {
console.info(`结果 ${index + 1}:`)
console.info(' 经度:', loc.longitude)
console.info(' 纬度:', loc.latitude)
console.info(' 地址:', loc.placeName)
})
} else {
console.info('未找到坐标信息')
}
} catch (error) {
console.error('地理编码失败:', JSON.stringify(error))
}
}
// 使用示例
geocodeAddress('北京市海淀区中关村')获取上次定位结果
typescript
import { geoLocationManager } from '@kit.LocationKit'
async function getLastKnownLocation(): Promise<geoLocationManager.Location | null> {
try {
const location = geoLocationManager.getLastLocation()
if (location) {
console.info('上次定位结果:')
console.info(' 经度:', location.longitude)
console.info(' 纬度:', location.latitude)
console.info(' 时间:', new Date(location.timeStamp).toLocaleString())
return location
}
console.info('暂无定位缓存')
return null
} catch (error) {
console.error('获取上次定位失败:', JSON.stringify(error))
return null
}
}定位状态监听
监听卫星状态变化
typescript
import { geoLocationManager } from '@kit.LocationKit'
function listenGnssStatus(): void {
geoLocationManager.on('gnssStatusChange', (status) => {
console.info('卫星状态变化:')
console.info(' 卫星总数:', status.satelliteNumber)
console.info(' 各卫星信息:')
status.satelliteIds.forEach((id, index) => {
console.info(` 卫星 ${id}:`)
console.info(` 载噪比: ${status.carrierToNoiseDensitys[index]}`)
console.info(` 仰角: ${status.elevations[index]}`)
console.info(` 方位角: ${status.azimuths[index]}`)
console.info(` 用于定位: ${status.usedInFix[index]}`)
})
})
}
// 取消监听
function stopListenGnssStatus(): void {
geoLocationManager.off('gnssStatusChange')
}完整封装示例
typescript
import { geoLocationManager } from '@kit.LocationKit'
import { abilityAccessCtrl } from '@kit.AbilityKit'
interface LocationResult {
longitude: number
latitude: number
altitude: number
accuracy: number
address?: string
city?: string
district?: string
street?: string
}
class LocationManager {
private static instance: LocationManager
private locationCallback: ((location: geoLocationManager.Location) => void) | null = null
static getInstance(): LocationManager {
if (!LocationManager.instance) {
LocationManager.instance = new LocationManager()
}
return LocationManager.instance
}
// 检查定位权限
async checkPermission(): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager()
const permissions = ['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION']
try {
const result = await atManager.requestPermissionsFromUser(getContext(), permissions)
return result.authResults.every(r => r === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
} catch {
return false
}
}
// 检查定位服务是否开启
isLocationEnabled(): boolean {
return geoLocationManager.isLocationEnabled()
}
// 请求开启定位服务
async requestEnableLocation(): Promise<void> {
await geoLocationManager.requestEnableLocation()
}
// 获取当前位置
async getCurrentLocation(): Promise<LocationResult | null> {
try {
const requestInfo: geoLocationManager.SingleLocationRequest = {
locatingPriority: geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
locatingTimeoutMs: 10000
}
const location = await geoLocationManager.getCurrentLocation(requestInfo)
// 获取地址信息
let addressInfo = {}
try {
const addresses = await geoLocationManager.getAddressesFromLocation({
latitude: location.latitude,
longitude: location.longitude,
maxItems: 1
})
if (addresses.length > 0) {
const addr = addresses[0]
addressInfo = {
address: addr.placeName,
city: addr.locality,
district: addr.subLocality,
street: addr.roadName
}
}
} catch {
// 反编码失败不影响定位结果
}
return {
longitude: location.longitude,
latitude: location.latitude,
altitude: location.altitude,
accuracy: location.accuracy,
...addressInfo
}
} catch (error) {
console.error('获取位置失败:', JSON.stringify(error))
return null
}
}
// 开始持续定位
startContinuousTracking(
interval: number = 1000,
callback: (result: LocationResult) => void
): void {
const requestInfo: geoLocationManager.ContinuousLocationRequest = {
interval,
locationScenario: geoLocationManager.UserActivityScenario.NAVIGATION
}
this.locationCallback = (location) => {
callback({
longitude: location.longitude,
latitude: location.latitude,
altitude: location.altitude,
accuracy: location.accuracy
})
}
geoLocationManager.on('locationChange', requestInfo, this.locationCallback)
}
// 停止持续定位
stopContinuousTracking(): void {
if (this.locationCallback) {
geoLocationManager.off('locationChange', this.locationCallback)
this.locationCallback = null
}
}
// 地理反编码
async reverseGeocode(latitude: number, longitude: number): Promise<string> {
try {
const addresses = await geoLocationManager.getAddressesFromLocation({
latitude,
longitude,
maxItems: 1
})
return addresses[0]?.placeName || ''
} catch {
return ''
}
}
// 地理编码
async geocode(address: string): Promise<{ longitude: number; latitude: number } | null> {
try {
const locations = await geoLocationManager.getAddressesFromLocationName({
description: address,
maxItems: 1
})
if (locations.length > 0) {
return {
longitude: locations[0].longitude,
latitude: locations[0].latitude
}
}
return null
} catch {
return null
}
}
}
export const locationManager = LocationManager.getInstance()完整页面示例
typescript
import { geoLocationManager } from '@kit.LocationKit'
@Entry
@Component
struct LocationPage {
@State longitude: string = '--'
@State latitude: string = '--'
@State accuracy: string = '--'
@State address: string = '--'
@State isTracking: boolean = false
private tracker: LocationTracker = new LocationTracker()
aboutToDisappear() {
this.tracker.stopTracking()
}
async getLocation() {
const result = await locationManager.getCurrentLocation()
if (result) {
this.longitude = result.longitude.toFixed(6)
this.latitude = result.latitude.toFixed(6)
this.accuracy = `${result.accuracy.toFixed(2)} 米`
this.address = result.address || '未知地址'
}
}
toggleTracking() {
if (this.isTracking) {
this.tracker.stopTracking()
this.isTracking = false
} else {
this.tracker.startTracking((location) => {
this.longitude = location.longitude.toFixed(6)
this.latitude = location.latitude.toFixed(6)
this.accuracy = `${location.accuracy.toFixed(2)} 米`
})
this.isTracking = true
}
}
build() {
Column({ space: 16 }) {
Text('定位服务')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Column({ space: 8 }) {
InfoRow('经度', this.longitude)
InfoRow('纬度', this.latitude)
InfoRow('精度', this.accuracy)
InfoRow('地址', this.address)
}
.width('100%')
.padding(16)
.backgroundColor('#f5f5f5')
.borderRadius(8)
Row({ space: 16 }) {
Button('获取位置')
.onClick(() => this.getLocation())
Button(this.isTracking ? '停止追踪' : '持续追踪')
.onClick(() => this.toggleTracking())
}
Button('地理编码')
.onClick(async () => {
const result = await locationManager.geocode('北京市海淀区中关村')
if (result) {
this.longitude = result.longitude.toFixed(6)
this.latitude = result.latitude.toFixed(6)
}
})
}
.width('100%')
.height('100%')
.padding(16)
}
}
@Component
struct InfoRow {
@Prop label: string
@Prop value: string
build() {
Row() {
Text(this.label)
.fontSize(14)
.fontColor('#666')
.width(60)
Text(this.value)
.fontSize(14)
.layoutWeight(1)
}
.width('100%')
}
}最佳实践
- 权限申请:定位权限必须动态申请,且先申请大概位置再申请精确位置
- 检查开关:定位前检查定位服务是否开启,未开启时引导用户开启
- 及时停止:持续定位在不需要时及时停止,避免耗电
- 超时处理:单次定位设置合理的超时时间,避免长时间等待
- 精度选择:根据场景选择合适的定位优先级,导航场景用高精度,社交场景用快速定位
- 错误处理:定位可能失败(如室内、信号差),做好降级处理
- 隐私合规:定位数据属于敏感信息,遵守隐私政策,明确告知用户用途