HarmonyOS 地图、定位与传感器能力实战:从位置获取到运动感知
在 HarmonyOS 应用开发中,地图、定位与传感器能力是构建位置服务、运动健康、AR 导航等场景的基础。本文将从定位权限申请、位置获取、地图集成、传感器订阅四个维度展开,涵盖单次定位、持续跟踪、地图标注、加速度计与陀螺仪的实战用法。
一、定位权限与隐私合规
HarmonyOS 的定位能力需要申请 ohos.permission.APPROXIMATELY_LOCATION(粗略位置)或 ohos.permission.LOCATION(精确位置)权限,前者精度约 5 公里,后者可达米级。
1. 配置权限
在 module.json5 中声明:
json
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:location_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION"
}
]
}
}
2. 动态申请
typescript
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';
async function requestLocationPermission(): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
const bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleFlags);
const tokenID = bundleInfo.appInfo.accessTokenId;
const permissions = ['ohos.permission.APPROXIMATELY_LOCATION', 'ohos.permission.LOCATION'];
const grantStatus = await atManager.requestPermissionsFromUser(getContext(), permissions);
return grantStatus.authResults.every(result => result === 0);
}
二、位置获取:单次定位与连续跟踪
1. 单次定位
适用于签到、地址选择等场景:
typescript
import geoLocationManager from '@ohos.geoLocationManager';
async function getCurrentLocation() {
const request: geoLocationManager.CurrentLocationRequest = {
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: geoLocationManager.LocationRequestScenario.UNSET,
maxAccuracy: 0
};
try {
const location = await geoLocationManager.getCurrentLocation(request);
console.info(`当前位置: ${location.latitude}, ${location.longitude}`);
console.info(`精度: ${location.accuracy}m, 时间: ${location.time}`);
return location;
} catch (err) {
console.error(`定位失败: ${err.message}`);
return null;
}
}
2. 连续定位
适用于导航、跑步轨迹记录:
typescript
let locationRequest: geoLocationManager.LocationRequest = {
priority: geoLocationManager.LocationRequestPriority.ACCURACY,
scenario: geoLocationManager.LocationRequestScenario.NAVIGATION,
timeInterval: 1, // 1 秒上报一次
distanceInterval: 10, // 移动 10 米上报
maxAccuracy: 0
};
function startTracking() {
geoLocationManager.on('locationChange', locationRequest, (location) => {
console.info(`实时位置: ${location.latitude}, ${location.longitude}`);
// 更新 UI 或保存轨迹点
});
}
function stopTracking() {
geoLocationManager.off('locationChange');
}
3. 后台定位
需要申请 ohos.permission.LOCATION_IN_BACKGROUND 并配置后台任务:
typescript
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
async function startBackgroundLocation() {
await backgroundTaskManager.startBackgroundRunning(getContext(),
backgroundTaskManager.BackgroundMode.LOCATION,
{ notificationId: 1, notificationContent: '正在记录运动轨迹' }
);
startTracking();
}
function stopBackgroundLocation() {
stopTracking();
backgroundTaskManager.stopBackgroundRunning(getContext());
}
三、地图集成:华为地图服务(Map Kit)
HarmonyOS 推荐使用华为地图服务(Map Kit)显示地图、标注点位、绘制路线。
1. 配置依赖
在 oh-package.json5 中添加:
json
{
"dependencies": {
"@hmscore/map-kit": "^1.0.0"
}
}
2. 显示地图
typescript
import mapCommon from '@hmscore/map-kit';
@Entry
@Component
struct MapPage {
private mapController: mapCommon.MapComponentController = new mapCommon.MapComponentController();
build() {
Column() {
MapComponent({
mapOptions: {
position: { latitude: 39.9042, longitude: 116.4074 },
zoom: 12
},
mapCallback: (err, mapController) => {
if (!err) {
this.mapController = mapController;
}
}
})
.width('100%')
.height('100%')
}
}
}
3. 添加标注点
typescript
addMarker() {
const markerOptions: mapCommon.MarkerOptions = {
position: { latitude: 39.9042, longitude: 116.4074 },
title: '天安门',
snippet: '北京市中心'
};
this.mapController.addMarker(markerOptions);
}
4. 绘制路线
typescript
drawPolyline(points: Array<{ latitude: number, longitude: number }>) {
const polylineOptions: mapCommon.PolylineOptions = {
points: points,
color: 0xFF0000FF,
width: 5
};
this.mapController.addPolyline(polylineOptions);
}
四、传感器能力:加速度计、陀螺仪与方向传感器
1. 订阅加速度计
适用于计步、摇一摇、碰撞检测:
typescript
import sensor from '@ohos.sensor';
function startAccelerometer() {
sensor.on(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
console.info(`加速度: x=${data.x}, y=${data.y}, z=${data.z}`);
// 检测摇一摇:若 |x| > 15 或 |y| > 15 或 |z| > 15 触发
if (Math.abs(data.x) > 15 || Math.abs(data.y) > 15 || Math.abs(data.z) > 15) {
console.info('检测到摇一摇');
}
}, { interval: 100000000 }); // 100ms
}
function stopAccelerometer() {
sensor.off(sensor.SensorId.ACCELEROMETER);
}
2. 订阅陀螺仪
适用于 VR、AR、手势识别:
typescript
function startGyroscope() {
sensor.on(sensor.SensorId.GYROSCOPE, (data: sensor.GyroscopeResponse) => {
console.info(`角速度: x=${data.x}, y=${data.y}, z=${data.z}`);
}, { interval: 100000000 });
}
function stopGyroscope() {
sensor.off(sensor.SensorId.GYROSCOPE);
}
3. 订阅方向传感器
适用于指南针、地图旋转:
typescript
function startOrientation() {
sensor.on(sensor.SensorId.ORIENTATION, (data: sensor.OrientationResponse) => {
console.info(`方向角: alpha=${data.alpha}, beta=${data.beta}, gamma=${data.gamma}`);
// alpha: 0-360° (方位角), beta: -180-180° (俯仰角), gamma: -90-90° (翻滚角)
}, { interval: 200000000 });
}
function stopOrientation() {
sensor.off(sensor.SensorId.ORIENTATION);
}
五、实战案例:跑步轨迹记录与地图展示
1. 架构设计
- LocationService :封装定位逻辑,提供
start/stop/getTrack接口 - MapViewModel:管理地图状态、轨迹点、实时位置
- RunPage:展示地图、控制按钮、统计信息(距离、配速、时长)
2. LocationService 封装
typescript
import geoLocationManager from '@ohos.geoLocationManager';
export class LocationService {
private trackPoints: Array<geoLocationManager.Location> = [];
private isTracking = false;
start() {
if (this.isTracking) return;
this.isTracking = true;
this.trackPoints = [];
const request: geoLocationManager.LocationRequest = {
priority: geoLocationManager.LocationRequestPriority.ACCURACY,
scenario: geoLocationManager.LocationRequestScenario.SPORT,
timeInterval: 2,
distanceInterval: 5
};
geoLocationManager.on('locationChange', request, (location) => {
this.trackPoints.push(location);
console.info(`记录点位: ${location.latitude}, ${location.longitude}`);
});
}
stop() {
if (!this.isTracking) return;
geoLocationManager.off('locationChange');
this.isTracking = false;
}
getTrack() {
return this.trackPoints.map(p => ({ latitude: p.latitude, longitude: p.longitude }));
}
getTotalDistance(): number {
let distance = 0;
for (let i = 1; i < this.trackPoints.length; i++) {
distance += this.calculateDistance(this.trackPoints[i - 1], this.trackPoints[i]);
}
return distance;
}
private calculateDistance(p1: geoLocationManager.Location, p2: geoLocationManager.Location): number {
const R = 6371e3; // 地球半径(米)
const φ1 = p1.latitude * Math.PI / 180;
const φ2 = p2.latitude * Math.PI / 180;
const Δφ = (p2.latitude - p1.latitude) * Math.PI / 180;
const Δλ = (p2.longitude - p1.longitude) * Math.PI / 180;
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
}
3. RunPage 界面
typescript
import mapCommon from '@hmscore/map-kit';
import { LocationService } from './LocationService';
@Entry
@Component
struct RunPage {
@State isRunning: boolean = false;
@State distance: number = 0;
@State duration: number = 0;
private locationService = new LocationService();
private mapController: mapCommon.MapComponentController | null = null;
private timer: number = -1;
build() {
Stack() {
MapComponent({
mapOptions: { zoom: 15 },
mapCallback: (err, controller) => {
if (!err) this.mapController = controller;
}
})
.width('100%')
.height('100%')
Column() {
Text(`距离: ${(this.distance / 1000).toFixed(2)} km`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text(`时长: ${Math.floor(this.duration / 60)}:${(this.duration % 60).toString().padStart(2, '0')}`)
.fontSize(18)
Button(this.isRunning ? '结束' : '开始')
.onClick(() => this.toggleRun())
.margin({ top: 20 })
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.End)
.width('100%')
.height('100%')
.padding(20)
}
}
toggleRun() {
if (this.isRunning) {
this.locationService.stop();
clearInterval(this.timer);
const track = this.locationService.getTrack();
this.mapController?.addPolyline({ points: track, color: 0xFF0000FF, width: 5 });
this.distance = this.locationService.getTotalDistance();
} else {
this.locationService.start();
this.duration = 0;
this.timer = setInterval(() => {
this.duration++;
}, 1000);
}
this.isRunning = !this.isRunning;
}
}
六、性能与功耗优化
1. 定位策略
- 单次定位 :使用
FIRST_FIX优先级,获取后立即停止 - 导航场景 :使用
ACCURACY优先级 +NAVIGATION场景 - 省电场景 :使用
LOW_POWER优先级 + 更大的timeInterval
2. 传感器采样率
- 加速度计:游戏场景 20ms,计步场景 200ms
- 陀螺仪:VR 场景 10ms,普通场景 100ms
- 及时调用
sensor.off()停止订阅
3. 地图渲染
- 限制同屏标注点数量(< 100 个)
- 使用聚合标注(Cluster)处理密集点位
- 避免频繁调用
addPolyline,批量更新路线
七、常见问题
1. 定位失败返回 201
原因 :未授予定位权限
解决 :检查 requestPermissionsFromUser 返回值,引导用户手动开启
2. 后台定位被系统杀死
原因 :未申请后台任务或通知未显示
解决 :确保调用 startBackgroundRunning 并显示前台通知
3. 地图标注点击无响应
原因 :未设置 MapComponent 的点击回调
解决 :在 mapCallback 中注册 onMarkerClick 监听器
4. 传感器数据抖动严重
原因 :硬件噪声或采样率过高
解决:应用低通滤波器或卡尔曼滤波平滑数据
总结
本文覆盖了 HarmonyOS 地图、定位与传感器能力的核心要点:
- 定位 :单次定位用
getCurrentLocation,连续跟踪用on('locationChange'),后台场景需配合后台任务 - 地图:使用 Map Kit 显示地图、添加标注、绘制路线
- 传感器:加速度计检测运动状态,陀螺仪获取角速度,方向传感器提供方位角
- 优化:选择合适的定位策略、传感器采样率,及时停止订阅以降低功耗
掌握这些能力后,你可以构建位置签到、运动轨迹、AR 导航、体感游戏等丰富的应用场景,为用户提供更智能的空间感知与交互体验。