Skip to content

定位服务

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%')
  }
}

最佳实践

  1. 权限申请:定位权限必须动态申请,且先申请大概位置再申请精确位置
  2. 检查开关:定位前检查定位服务是否开启,未开启时引导用户开启
  3. 及时停止:持续定位在不需要时及时停止,避免耗电
  4. 超时处理:单次定位设置合理的超时时间,避免长时间等待
  5. 精度选择:根据场景选择合适的定位优先级,导航场景用高精度,社交场景用快速定位
  6. 错误处理:定位可能失败(如室内、信号差),做好降级处理
  7. 隐私合规:定位数据属于敏感信息,遵守隐私政策,明确告知用户用途

参考链接