上一篇文章介绍了危险权限申请的内容,鸿蒙后台任务分为 "短时任务"(≤3 分钟,如临时数据同步)和 "长时任务"(持续运行,如定位、音频),本文聚焦定位上传场景的长时任务 ,核心依赖backgroundTaskManager模块。
一、后台任务调度:backgroundTaskManager 实战
1. 长时后台任务核心特性
- 必备权限:
ohos.permission.KEEP_BACKGROUND_RUNNING(必须声明) - 支持类型:location(定位)、audio(音频)、download(下载)等(需在
backgroundModes配置) - 合规要求:运行时系统自动显示通知,告知用户 "应用正在后台运行",不可隐藏
- 核心 API:
startBackgroundRunning(启动)、stopBackgroundRunning(停止)
2. 长时任务启动与停止代码
javascript
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
import wantAgent from '@ohos.wantAgent';
import common from '@ohos.app.ability.common';
/**
* 启动定位类型长时后台任务
*/
export async function startLocBackgroundTask(context: common.UIAbilityContext) {
try {
// 1. 创建通知代理(点击通知跳转应用)
const wantAgentInfo = {
wants: [{
bundleName: context.applicationInfo.bundleName,
abilityName: 'LocationAbility'
}],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 1001
};
const notifyAgent = await wantAgent.getWantAgent(wantAgentInfo);
// 2. 启动长时后台任务(指定定位类型)
await backgroundTaskManager.startBackgroundRunning(
context,
['location'], // 任务类型:定位(需与backgroundModes一致)
notifyAgent
);
console.log('长时后台任务启动成功');
} catch (err) {
console.error(`后台任务启动失败:${JSON.stringify(err)}`);
Toast.show({ message: '后台服务启动失败,请重试' });
}
}
/**
* 停止长时后台任务(资源释放)
*/
export async function stopLocBackgroundTask(context: common.UIAbilityContext) {
try {
await backgroundTaskManager.stopBackgroundRunning(context);
console.log('长时后台任务停止成功');
} catch (err) {
console.error(`后台任务停止失败:${JSON.stringify(err)}`);
}
}
二、实战整合:位置权限 + 后台定位数据上传
结合前面的权限申请和后台任务,实现 "前台授权→后台持续定位→数据上传" 的完整流程,代码可直接嵌入项目使用:
1. 核心依赖导入
javascript
import geoLocationManager from '@ohos.geolocation';
import http from '@ohos.net.http';
import common from '@ohos.app.ability.common';
import { requestForegroundLocPerm, startLocBackgroundTask, stopLocBackgroundTask } from './PermAndTaskUtil';
2. 全局变量与初始化
typescript
// 全局状态管理
let locSubscriptionId: number | null = null; // 定位订阅ID
let httpClient: http.HttpClient | null = null; // HTTP上传客户端
/**
* 初始化定位与后台上传服务
*/
export async function initLocAndBackgroundTask(context: common.UIAbilityContext) {
// 1. 启动长时后台任务
await startLocBackgroundTask(context);
// 2. 初始化HTTP客户端(HTTPS加密传输,合规必备)
httpClient = http.createHttpClient();
httpClient.setTimeout(10000); // 超时10秒
// 3. 配置定位参数(平衡精度与功耗)
const locRequest = {
priority: geoLocationManager.LocationRequestPriority.HIGH_ACCURACY, // 高精度模式
interval: 30000, // 定位间隔30秒(避免频繁定位耗电)
scenario: geoLocationManager.LocationScenario.NAVIGATION // 导航场景(适合轨迹记录)
};
// 4. 订阅位置变化(持续定位)
locSubscriptionId = geoLocationManager.on('locationChange', locRequest, (location) => {
console.log(`获取位置:纬度=${location.latitude},经度=${location.longitude}`);
// 上传位置数据到服务器
uploadLocData({
userId: 'user_123456', // 实际开发替换为当前登录用户ID
lat: location.latitude,
lng: location.longitude,
accuracy: location.accuracy,
timestamp: Date.now()
});
});
}
/**
* 上传位置数据到服务器(HTTPS加密)
*/
async function uploadLocData(data: { userId: string; lat: number; lng: number; accuracy: number; timestamp: number }) {
if (!httpClient) return;
try {
const response = await httpClient.request('https://your-server.com/api/location/upload', {
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
body: http.createHttpBody(JSON.stringify(data))
});
if (response.responseCode === 200) {
console.log('位置数据上传成功');
} else {
console.error(`上传失败:状态码=${response.responseCode}`);
// 失败重试机制(最多3次)
retryUploadLocData(data, 3);
}
} catch (err) {
console.error(`上传异常:${JSON.stringify(err)}`);
}
}
/**
* 上传失败重试机制
*/
async function retryUploadLocData(data: any, retryCount: number) {
if (retryCount <= 0) return;
setTimeout(async () => {
try {
await uploadLocData(data);
} catch (err) {
retryUploadLocData(data, retryCount - 1);
}
}, 5000 * (4 - retryCount)); // 重试间隔:5秒、10秒、15秒
}
3. 资源释放(避免内存泄漏)
scss
/**
* 释放定位与后台任务资源
*/
export function releaseLocAndBackgroundResource(context: common.UIAbilityContext) {
// 1. 取消定位订阅
if (locSubscriptionId !== null) {
geoLocationManager.off('locationChange', locSubscriptionId);
locSubscriptionId = null;
}
// 2. 停止后台任务
stopLocBackgroundTask(context);
// 3. 关闭HTTP客户端
if (httpClient) {
httpClient.close();
httpClient = null;
}
console.log('资源释放完成');
}
4. UI 组件触发流程
scss
@Entry
@Component
struct LocUploadPage {
private context = getContext(this) as common.UIAbilityContext;
build() {
Column({ space: 30 })
.width('100%')
.height('100%')
.padding(30)
.backgroundColor('#f5f5f5') {
Text('位置数据同步服务')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#333')
Button('开启同步服务')
.type(ButtonType.Capsule)
.width(280)
.height(60)
.backgroundColor('#2f54eb')
.fontSize(20)
.onClick(async () => {
// 触发权限申请,成功后初始化服务
const isPermGranted = await requestForegroundLocPerm(this.context);
handlePermResult(isPermGranted, this.context);
})
Button('停止同步服务')
.type(ButtonType.Capsule)
.width(280)
.height(60)
.backgroundColor('#ff4d4f')
.fontSize(20)
.onClick(() => {
// 释放资源,停止服务
releaseLocAndBackgroundResource(this.context);
Toast.show({ message: '同步服务已停止' });
})
}
}
}
三、合规性避坑指南(实战经验总结)
后台任务合规红线
- ❶ 未声明
backgroundModes:长时任务必须在 Ability 中配置对应模式(如定位配置["location"]),否则启动失败; - ❷ 隐藏后台运行通知:系统自动生成的后台运行通知不可隐藏,这是鸿蒙合规硬性要求;
- ❸ 定位间隔过短:频繁定位(如 1 秒 / 次)会被系统判定为恶意耗电,建议≥30 秒,电量低时可调整为 60 秒。