鸿蒙技术干货8:地理位置获取全解析

67 阅读4分钟

 承接上一篇的通知服务,今天咱们进入系列第二篇 —— 地理位置服务(geoLocationManager)。地理位置是位置提醒 APP 的核心依赖,无论是获取用户当前位置,还是判断是否到达目标区域,都离不开它。这篇咱们从权限配置、定位初始化、经纬度获取到定位模式优化,一步步拆解实战代码,让你快速掌握鸿蒙地理位置服务的核心用法!

一、地理位置服务核心认知

1. 核心应用场景

  • 位置提醒(如到达目的地、离开指定区域)
  • 周边服务推荐(如附近的餐厅、加油站)
  • 轨迹记录(如运动轨迹、出行路线)
  • 定位导航(如步行、驾车导航)

2. 核心模块与权限要求

  • 核心模块:@ohos.geolocation(geoLocationManager)

  • 必备权限:

    • 模糊定位:ohos.permission.APPROXIMATELY_LOCATION(基础必备)
    • 精确定位:ohos.permission.LOCATION(可选,需搭配模糊定位)
    • 后台定位:ohos.permission.LOCATION_IN_BACKGROUND(后台持续定位需声明)
  • 定位模式:高精度(GNSS + 网络)、低功耗(仅网络)、设备仅(仅 GNSS)

二、地理位置开发四步走:权限 - 初始化 - 定位 - 解析

1. 权限配置与申请(合规核心)

先在module.json5中声明权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "用于获取大致位置,提供位置提醒服务",
        "usedScene": { "abilities": ["MainAbility"], "when": "inuse" }
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "用于获取精准位置,提升提醒准确性",
        "usedScene": { "abilities": ["MainAbility"], "when": "inuse" }
      }
    ]
  }
}

动态申请权限代码(复用系列第一篇的权限申请逻辑扩展):

import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';

// 定位权限组
const LOCATION_PERMISSIONS = [
  'ohos.permission.APPROXIMATELY_LOCATION',
  'ohos.permission.LOCATION'
];

/**
 * 检查并申请定位权限
 */
export async function requestLocationPermission(context: common.UIAbilityContext): Promise<boolean> {
  const atManager = abilityAccessCtrl.createAtManager();
  try {
    // 检查权限状态
    const authResults = await atManager.checkAccessToken(
      abilityAccessCtrl.createTokenID(),
      LOCATION_PERMISSIONS
    );
    
    // 筛选未授权权限
    const needReqPerms = LOCATION_PERMISSIONS.filter(
      (perm, index) => authResults[index] !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
    );
    
    if (needReqPerms.length === 0) {
      console.log('定位权限已全部授予');
      return true;
    }
    
    // 动态申请权限
    const reqResult = await atManager.requestPermissionsFromUser(context, needReqPerms);
    const allGranted = reqResult.authResults.every(status => status === 0);
    console.log(`定位权限申请结果:${allGranted ? '成功' : '失败'}`);
    return allGranted;
  } catch (err) {
    console.error(`定位权限处理异常:${JSON.stringify(err)}`);
    return false;
  }
}

2. 定位初始化与配置

import geoLocationManager from '@ohos.geolocation';

// 全局存储定位订阅ID(用于取消定位)
let locationSubscriptionId: number | null = null;

/**
 * 初始化定位配置
 * @returns 定位请求配置
 */
export function initLocationConfig(): geoLocationManager.LocationRequest {
  // 配置定位参数
  const locationRequest = {
    priority: geoLocationManager.LocationRequestPriority.HIGH_ACCURACY, // 高精度模式
    interval: 5000, // 定位间隔(5秒一次,可根据需求调整)
    distance: 10, // 位置变化超过10米才触发回调(减少功耗)
    scenario: geoLocationManager.LocationScenario.NAVIGATION // 导航场景(适合位置跟踪)
  };
  console.log('定位配置初始化完成');
  return locationRequest;
}

3. 获取实时经纬度(核心功能)

支持两种定位方式:单次定位(获取当前位置)和持续定位(实时跟踪位置):

typescript

运行

import geoLocationManager from '@ohos.geolocation';

/**
 * 单次定位:获取当前经纬度
 */
export async function getSingleLocation(): Promise<{ latitude: number; longitude: number } | null> {
  try {
    const location = await geoLocationManager.getCurrentLocation();
    const { latitude, longitude } = location;
    console.log(`单次定位成功:纬度=${latitude},经度=${longitude}`);
    return { latitude, longitude };
  } catch (err) {
    console.error(`单次定位失败:${JSON.stringify(err)}`);
    return null;
  }
}

/**
 * 持续定位:实时监听位置变化
 * @param callback 位置变化回调函数
 */
export function startContinuousLocation(
  callback: (location: { latitude: number; longitude: number }) => void
) {
  const locationRequest = initLocationConfig();
  
  // 订阅位置变化
  locationSubscriptionId = geoLocationManager.on('locationChange', locationRequest, (location) => {
    const { latitude, longitude } = location;
    console.log(`持续定位更新:纬度=${latitude},经度=${longitude}`);
    callback({ latitude, longitude });
  });
  
  console.log('持续定位已启动,订阅ID:', locationSubscriptionId);
}

/**
 * 停止持续定位(释放资源)
 */
export function stopContinuousLocation() {
  if (locationSubscriptionId !== null) {
    geoLocationManager.off('locationChange', locationSubscriptionId);
    locationSubscriptionId = null;
    console.log('持续定位已停止');
  }
}

4. 位置数据解析与距离计算

位置提醒需要判断 “是否到达目标区域”,核心是计算当前位置与目标位置的直线距离:

typescript

运行

/**
 * 计算两点之间的直线距离(单位:米)
 * @param lat1 起点纬度
 * @param lng1 起点经度
 * @param lat2 终点纬度
 * @param lng2 终点经度
 * @returns 直线距离
 */
export function calculateDistance(
  lat1: number,
  lng1: number,
  lat2: number,
  lng2: number
): number {
  const R = 6371000; // 地球半径(米)
  const radLat1 = Math.PI * lat1 / 180;
  const radLat2 = Math.PI * lat2 / 180;
  const deltaLat = radLat2 - radLat1;
  const deltaLng = Math.PI * (lng2 - lng1) / 180;
  
  const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
    Math.cos(radLat1) * Math.cos(radLat2) *
    Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
  
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c; // 距离(米)
}

/**
 * 判断是否到达目标区域
 * @param currentLoc 当前位置
 * @param targetLoc 目标位置
 * @param radius 提醒半径(米)
 * @returns 是否到达
 */
export function isReachTargetArea(
  currentLoc: { latitude: number; longitude: number },
  targetLoc: { latitude: number; longitude: number },
  radius: number = 100
): boolean {
  const distance = calculateDistance(
    currentLoc.latitude,
    currentLoc.longitude,
    targetLoc.latitude,
    targetLoc.longitude
  );
  return distance <= radius;
}

三、实战:定位功能整合演示

typescript

运行

@Entry
@Component
struct LocationDemoPage {
  private context = getContext(this) as common.UIAbilityContext;
  @State currentLocation: string = '未获取位置';
  @State targetLocation: { latitude: number; longitude: number } = {
    latitude: 39.9042, // 北京天安门纬度
    longitude: 116.4074 // 北京天安门经度
  };

  build() {
    Column({ space: 30 })
      .width('100%')
      .height('100%')
      .padding(30)
      .backgroundColor('#f5f5f5') {
      
      Text('地理位置服务演示')
        .fontSize(32)
        .fontWeight(FontWeight.Bold)
      
      Text(`当前位置:${this.currentLocation}`)
        .fontSize(20)
        .width('100%')
        .textAlign(TextAlign.Center)
      
      Button('申请定位权限')
        .type(ButtonType.Capsule)
        .width(250)
        .height(60)
        .backgroundColor('#2f54eb')
        .onClick(async () => {
          const granted = await requestLocationPermission(this.context);
          Toast.show({ message: granted ? '定位权限申请成功' : '定位权限申请失败' });
        })
      
      Button('获取当前位置')
        .type(ButtonType.Capsule)
        .width(250)
        .height(60)
        .backgroundColor('#2f54eb')
        .onClick(async () => {
          const location = await getSingleLocation();
          if (location) {
            this.currentLocation = `纬度:${location.latitude.toFixed(6)},经度:${location.longitude.toFixed(6)}`;
            // 判断是否到达目标区域
            const reach = isReachTargetArea(location, this.targetLocation);
            if (reach) {
              Toast.show({ message: '已到达目标区域!' });
            }
          } else {
            this.currentLocation = '位置获取失败';
          }
        })
      
      Button('启动持续定位')
        .type(ButtonType.Capsule)
        .width(250)
        .height(60)
        .backgroundColor('#2f54eb')
        .onClick(() => {
          startContinuousLocation((location) => {
            this.currentLocation = `纬度:${location.latitude.toFixed(6)},经度:${location.longitude.toFixed(6)}`;
            // 到达目标区域触发提醒(后续整合通知)
            if (isReachTargetArea(location, this.targetLocation)) {
              Toast.show({ message: '已到达目标区域,即将发送通知!' });
              stopContinuousLocation(); // 到达后停止定位,减少功耗
            }
          });
        })
      
      Button('停止持续定位')
        .type(ButtonType.Capsule)
        .width(250)
        .height(60)
        .backgroundColor('#ff4d4f')
        .onClick(() => {
          stopContinuousLocation();
        })
    }
  }
}

四、实战踩坑指南

1. 定位失败的常见原因

  • ❶ 权限缺失:必须同时声明模糊定位权限,精确定位不能单独申请;
  • ❷ 定位模式不匹配:室内场景使用 “高精度模式”(GNSS + 网络),纯 GNSS 在室内可能无信号;
  • ❸ 设备定位开关关闭:需引导用户开启系统定位功能(代码可检测定位开关状态);
  • ❹ 后台定位权限未开启:持续定位需申请LOCATION_IN_BACKGROUND并引导用户手动开启。

2. 功耗优化技巧

  • ❶ 合理设置定位间隔:非实时导航场景建议间隔≥3 秒,距离阈值≥10 米;
  • ❷ 到达目标后停止定位:避免不必要的持续定位消耗电量;
  • ❸ 适配电池状态:低电量时切换为 “低功耗模式”(仅网络定位)。

加入班级,学习鸿蒙开发