一、引言

在 Harmony OS NEXT / 5.0 / API 12+ 版本的开发中,实现精准的定位功能对于许多应用来说至关重要。本文将详细介绍如何开发一个具备定位功能的应用,从效果展示、实现逻辑、源码解析到错误处理,带你深入了解其背后的技术细节。

二、适用版本说明

本文所涉及的定位功能开发适用于 Harmony OS NEXT / 5.0 / API 12+ 版本。这些版本提供了丰富且稳定的 API,如 @kit.LocationKit@kit.AbilityKit 等,为定位功能的实现提供了有力支持。

三、效果展示

                                                

四、实现逻辑剖析

  1. 获取权限管理:借助 abilityAccessCtrl 模块实现动态权限申请,仅请求 APPROXIMATELY_LOCATIONLOCATION 权限,严格遵循最小化授权原则。通过在异步回调中校验 authResults 来统一管理授权状态,确保应用功能符合系统安全规范,避免因权限缺失导致定位服务中断。这就好比你要进入一个特定区域,必须先拿到对应的通行证,而且只拿必要的通行证,防止过度授权带来的风险。
  2. 定位服务封装:将定位的核心逻辑,如请求参数配置、事件监听等,抽象为 LocationService 静态类。这样做使得定位功能的代码结构更加清晰,易于维护和复用,就像把复杂的机器零件组装成一个整体,方便调用和管理。
  3. 数据流向清晰化:应用遵循「事件→数据→视图」的单向数据流模式。当 locationChange 事件触发时,回调函数捕获位置对象,进而更新 text_locationResult 状态变量,最终触发 UI 重渲染。这种模式确保了数据流动的可预测性,使得代码的逻辑更加清晰,就像一条顺畅的生产线,每个环节都有条不紊地进行。
  4. 异常监控:使用 try - catch 块包裹关键操作,如权限请求、事件注册等,并结合 hilog 日志工具输出错误详情。这就像给应用安装了一个 “智能医生”,一旦出现问题,能够快速定位错误位置,帮助开发者及时解决问题。

五、源码详解

静态源码

// 导入所需的模块
import { geoLocationManager } from '@kit.LocationKit'; // 用于管理地理位置功能
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit'; // 用于权限管理
import { hilog } from '@kit.PerformanceAnalysisKit'; // 用于日志记录

// LocationService类封装了与定位相关的操作
class LocationService {
    // 请求定位权限
    static async requestPermissions(): Promise<boolean> {
        // 定义需要的权限列表
        const permissions: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'];
        // 创建权限管理对象
        const atManager = abilityAccessCtrl.createAtManager();
        try {
            // 向用户请求权限
            const result = await atManager.requestPermissionsFromUser(getContext(), permissions);
            // 检查所有权限是否都被授予
            return result.authResults.every(status => status === 0);
        } catch (err) {
            // 如果请求失败,抛出错误
            throw new Error(`requestPermissionsFromUser failed: ${JSON.stringify(err)}`);
        }
    }

    // 开始定位跟踪
    static startLocationTracking(callback: (location: geoLocationManager.Location) => void) {
        // 定义定位请求参数
        const requestInfo: geoLocationManager.LocationRequest = {
           'scenario': geoLocationManager.LocationRequestScenario.DAILY_LIFE_SERVICE, // 定位场景
            'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX, // 定位优先级
            'timeInterval': 1, // 时间间隔
            'distanceInterval': 0, // 距离间隔
           'maxAccuracy': 0 // 最大精度
        };
        try {
            // 注册位置变化监听器
            geoLocationManager.on('locationChange', requestInfo, callback);
        } catch (err) {
            // 如果启动定位失败,抛出错误
            throw new Error(`startLocationTracking failed: ${JSON.stringify(err)}`);
        }
    }

    // 停止定位跟踪
    static stopLocationTracking(callback: (location: geoLocationManager.Location) => void) {
        // 取消位置变化监听器
        geoLocationManager.off('locationChange', callback);
    }

    // ... 其他定位相关方法 ...
}

// 定义定位页面组件
@Entry
@Component
struct PositioningPage {
    @State text_locationResult: string = ''; // 用于显示定位结果的文本
    @State isTracking: boolean = false; // 标记是否正在定位

    // 构建页面布局
    build() {
        Column({ space: 20 }) {
            // 权限获取按钮
            Button('获取定位权限')
               .type(ButtonType.Capsule) // 设置按钮样式为胶囊形状
               .onClick(async () => {
                    try {
                        // 请求定位权限
                        const granted = await LocationService.requestPermissions();
                        // 根据权限请求结果更新显示文本
                        this.text_locationResult = granted? '权限获取成功' : '权限获取失败';
                    } catch (err) {
                        // 如果请求失败,处理错误
                        this.handleError(err);
                    }
                })

            // 定位控制按钮
            Row({ space: 20 }) {
                Button(this.isTracking? '停止定位' : '开始定位')
                   .type(ButtonType.Capsule) // 设置按钮样式为胶囊形状
                   .backgroundColor(this.isTracking? '#ff4d4f' : '#52c41a') // 根据定位状态设置按钮背景色
                   .onClick(() => {
                        if (this.isTracking) {
                            // 如果正在定位,停止定位
                            LocationService.stopLocationTracking(this.locationChange);
                            this.text_locationResult = '定位已停止';
                        } else {
                            try {
                                // 如果未在定位,开始定位
                                LocationService.startLocationTracking(this.locationChange);
                                this.text_locationResult = '定位已开始';
                            } catch (err) {
                                // 如果启动定位失败,处理错误
                                this.handleError(err);
                            }
                        }
                        // 切换定位状态
                        this.isTracking =!this.isTracking;
                    })
            }

            // 结果显示区域
            Scroll() {
                Text(this.text_locationResult)
                   .fontSize(16) // 设置文本字体大小
                   .textAlign(TextAlign.Start) // 设置文本对齐方式
                   .padding(10) // 设置文本内边距
            }
           .height('40%') // 设置滚动区域高度
           .width('100%') // 设置滚动区域宽度
           .border({ width: 1, color: '#d9d9d9' }) // 设置边框样式
           .margin({ top: 20 }) // 设置上边距
        }
       .padding(20) // 设置列的内边距
       .width('100%') // 设置列的宽度
       .height('100%') // 设置列的高度
    }

    // 处理位置变化的回调函数
    private locationChange = (location: geoLocationManager.Location) => {
        // 更新定位结果文本
        this.updateLocationResult(`当前位置: ${location.latitude},${location.longitude}`);
        // 记录调试日志
        hilog.debug(0x0000, 'testTag', 'locationChange: %{public}s', JSON.stringify(location));
    }

    // 更新定位结果文本
    private updateLocationResult(result: string) {
        // 在结果文本前添加时间戳
        this.text_locationResult = `${new Date().toLocaleTimeString()}: ${result}\n${this.text_locationResult}`;
    }

    // 处理错误信息
    private handleError(err: Error) {
        // 记录错误日志
        hilog.error(0x0000, 'testTag', `Error: ${err.message}`);
        // 更新显示文本为错误信息
        this.text_locationResult = `错误: ${err.message}`;
    }
}
  1. LocationService 类
    • requestPermissions 方法:定义了所需权限列表,创建权限管理对象 atManager,通过 requestPermissionsFromUser 方法向用户请求权限,并在校验所有权限都被授予后返回 true,否则抛出错误。这就像是向系统 “索要” 进入定位功能区域的钥匙,只有拿到所有钥匙才能顺利进入。
    • startLocationTracking 方法:配置定位请求参数,包括定位场景、优先级、时间间隔、距离间隔和最大精度等。然后通过 geoLocationManager.on 方法注册位置变化监听器,一旦位置发生变化,就会调用传入的回调函数。这一步就像是给定位功能设置好参数,准备开始 “追踪” 位置信息。
    • stopLocationTracking 方法:通过 geoLocationManager.off 方法取消位置变化监听器,停止定位跟踪。就像给定位功能按下了 “暂停键”。
  2. PositioningPage 组件
    • build 方法:构建了应用的页面布局,包含权限获取按钮、定位控制按钮和结果显示区域。权限获取按钮点击后调用 LocationService.requestPermissions 请求权限,并根据结果更新显示文本。定位控制按钮根据当前定位状态切换按钮文本和背景色,点击后相应地启动或停止定位,并更新定位结果文本和定位状态。
    • locationChange 回调函数:当位置发生变化时,更新定位结果文本,并记录调试日志,就像一个 “记录员”,随时记录位置变化信息。
    • updateLocationResult 方法:在定位结果文本前添加时间戳,方便用户了解位置信息的更新时间。
    • handleError 方法:记录错误日志,并更新显示文本为错误信息,帮助用户和开发者快速了解错误情况。

权限源码

"requestPermissions": [
    {
        "name": "ohos.permission.INTERNET"
    },
    {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:EntryAbility_label",
        "usedScene": {

        }
    },
    {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:EntryAbility_label",
        "usedScene": {

        }
    }
]

module.json5 中配置所需权限,包括网络权限 ohos.permission.INTERNET,以及定位相关权限 ohos.permission.LOCATIONohos.permission.APPROXIMATELY_LOCATION,并说明权限使用的原因和场景。这就像是在应用的 “说明书” 里,明确告知系统和用户应用需要哪些权限以及为什么需要这些权限。

六、源码详细分析

1、权限请求

const permissions: Permissions[] = [
    'ohos.permission.APPROXIMATELY_LOCATION', // 必须声明于config.json
    'ohos.permission.LOCATION' // 精确定位需同时申请
];

// 动态请求实现
static async requestPermissions() {
    const atManager = abilityAccessCtrl.createAtManager();
    const result = await atManager.requestPermissionsFromUser(
        getContext(), // 关键:依赖Ability上下文
        permissions
    );
    return result.authResults.every(status => status === 0); // 0为授权成功
}
  • 这里定义了所需权限数组,并通过 abilityAccessCtrl.createAtManager() 创建权限管理对象,使用 requestPermissionsFromUser 方法动态请求权限。getContext() 提供了必要的 Ability 上下文,确保权限请求的正确性。最后通过校验 authResults 中所有权限状态是否都为 0(授权成功)来判断权限是否全部获取。

2、定位请求参数

const requestInfo: geoLocationManager.LocationRequest = {
    scenario: LocationRequestScenario.DAILY_LIFE_SERVICE, // 场景策略
    priority: LocationRequestPriority.FIRST_FIX,          // 定位策略
    timeInterval: 1,      // 单位:秒(实际受系统策略限制)
    distanceInterval: 0,   // 单位:米(0表示仅时间间隔生效)
    maxAccuracy: 0         // 精度要求(0为最高)
};
  • scenario:指定定位场景为 DAILY_LIFE_SERVICE,不同的场景会影响系统资源分配策略,如导航场景可选择 NAVIGATION
  • priority:设置定位优先级为 FIRST_FIX,适用于快速定位需求。若追求高精度定位,可选择 ACCURACY;若对功耗敏感,可选择 LOW_POWER
  • timeInterval:设置最小更新间隔为 1 秒,但实际间隔受系统策略限制,开发者应结合业务需求进行调整。
  • distanceInterval:设置位移触发阈值为 0 米,表示仅根据时间间隔触发位置更新。
  • maxAccuracy:设置精度要求为 0,代表最高精度。

七、错误的处理

        1、错误一(展示)

                                                        

                产生原因:系统获取了定位权限,但手机并没有开启定位功能

                解决方法:打开手机定位权限

                 参考文档:3301100 位置功能的开关未开启导致功能失败

        2、错误二(展示)

                                                          

                产生原因:未配置系统的权限管理(module.json5)

                解决方法:配置系统定位权限

                参考文档:通用错误码-API参考概述 - 华为HarmonyOS开发者

八、总结

本文介绍的 Harmony OS 定位功能,如同给应用装上了一个精准的 “小雷达”。其核心围绕权限获取、定位控制以及错误处理展开。先向系统请求必要权限,用户通过按钮控制定位的开启与关闭,位置信息变化时实时刷新界面显示,一旦出现问题,及时告知用户错误原因。

通过对实现逻辑和源码的详细分析,开发者可以深入理解 Harmony OS 定位功能开发的要点。在实际应用中,此功能可广泛应用于地图导航、位置打卡、周边服务推荐等场景。关于 Harmony OS 定位功能开发的更多技巧和优化方法,我会在后续博客中持续分享,感兴趣的话欢迎关注。对于本文介绍的定位功能开发,你有什么疑问或者想法吗?欢迎随时交流。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐