轻轨迹鸿蒙开发实战2:接管 backgroundTaskManager,长时后台定位与锁屏不间断追踪
背景介绍
在开发外勤考勤或运动轨迹类应用时,开发者面临的最大挑战往往不是“如何获取经纬度”,而是“怎么让定位在后台持续工作”。
对于我们的防作弊产品“轻轨迹”来说,员工在开始作业后,必然会把手机揣进兜里并锁屏。如果应用退到后台的几分钟内,定位服务就被系统无情掐断,那么画出来的轨迹就会出现大面积的“高空折线”甚至是空白,这就完全丧失了考核的真实性。
在 HarmonyOS NEXT 中,系统为了极致省电和用户隐私,建立了一套严丝合缝的后台冻结与生命周期托管机制。任何处于后台的普通应用进程,都会在几分钟内被系统强行转入挂起(Suspended)状态,其各种底层监听和异步回调(包括定位、网络、计时器)都会被全面冷冻。
作为程序员,想要冲破这层防线,唯一的合规途径就是——接管系统的长时任务(Continuous Task)管理器。本文将揭示鸿蒙的后台生存法则,带你通过实战代码打通锁屏常驻的后台高精度定位生命线。
1. 鸿蒙后台生存法则:短时任务 vs 长时任务
在鸿蒙系统资源调度框架中,应用进入后台后的待遇可划分为三种生存模式:
- 挂起与冻结(Suspended):默认模式。当应用退后台后,系统不会立刻杀死它,但会快速暂缓或停止其所有 CPU 调度。
- 短时任务(Transient Task):如果应用退后台时有未完成的工作(如正在写本地数据库),可向系统申请最长几分钟的延迟挂起。这对我们持续几小时的外勤考核来说无济于事。
- 长时任务(Continuous Task):如果应用在后台需要持续提供用户可感知的服务(如导航、音频播放、位置追踪),可以向系统申请后台长时任务。申请成功后,系统会为应用建立一条持续保活的通道,并强制在状态栏常驻一条小图标或胶囊,让用户明确知晓。
后台任务选择流程:
对于“轻轨迹”而言,我们必须申请 BackgroundMode.LOCATION 长时任务。
避坑指南
长时任务并不是开发者在代码里“悄悄”调个 API 就能实现的。它必须同时满足两个硬性前置条件:
- 必须在
module.json5配置文件中声明后台运行模式(backgroundModes)。 - 在后台运行期间,必须向用户展示一个持续更新的前台通知(通过
WantAgent托管),使用户随时能够点击通知回到应用。任何试图隐藏通知或滥用其他模式(如伪装成音频播放来蹭保活)的行为,在应用上架审核时都会被直接驳回。
2. module.json5 配置与权限声明
在开启代码编写前,我们先要在 entry/src/main/module.json5 中宣告我们要接管后台位置能力:
{
"module": {
"name": "entry",
"type": "entry",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"exported": true,
// 【核心机制】:配置后台运行模式,声明为位置持续定位模式
"backgroundModes": [
"location"
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
// 系统前台高精度定位权限
{
"name": "ohos.permission.LOCATION"
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION"
},
// 【避坑指南】:在后台持续接收定位,必须额外声明此权限,否则退后台即报权限缺失
{
"name": "ohos.permission.LOCATION_IN_BACKGROUND"
},
// 申请后台运行长时任务的必备权限
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
]
}
}3. 封装后台保活工具类:WantAgent 托管常驻通知栏
长时任务的启动需要使用到 backgroundTaskManager,并且需要传入一个 WantAgent 对象。
WantAgent 是鸿蒙提供的一种“代理意图”。因为我们的长时任务通知栏是由系统系统级进程渲染的,当用户点击这个通知时,系统进程需要知道应该拉起哪一个应用、进入哪一个页面。这就需要我们将拉起 EntryAbility 的意图封装进 WantAgent 中,托管给系统。
我们来封装 BackgroundTaskHelper.ets 工具类:
import backgroundTaskManager from '@ohos.backgroundTaskManager';
import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent';
export class BackgroundTaskHelper {
private static context: Context | null = null;
// 在 EntryAbility 初始化时,将 ApplicationContext 注入进来
public static setContext(ctx: Context) {
BackgroundTaskHelper.context = ctx;
}
// 开启长时后台任务保活
public static async startContinuousTask(): Promise<void> {
let ctx = BackgroundTaskHelper.context;
if (!ctx) {
console.error("BackgroundTaskHelper", "Context is null, cannot start continuous task");
return;
}
try {
// 1. 构建 WantAgentInfo,设定用户点击通知栏后的跳转意图
const wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: "com.qingkouwei.trajectory", // 我们的应用包名
abilityName: "EntryAbility" // 跳转的目标 Ability
}
],
actionType: wantAgent.OperationType.START_ABILITY, // 触发动作:拉起页面
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] // 标志位:更新已存在的通知
};
// 2. 向系统申请构建代理 WantAgent 对象
const wa: WantAgent = await wantAgent.getWantAgent(wantAgentInfo);
// 3. 核心调用:申请后台位置任务,并绑定通知栏托管
await backgroundTaskManager.startBackgroundRunning(ctx,
backgroundTaskManager.BackgroundMode.LOCATION,
wa);
console.info("BackgroundTaskHelper", `Continuous task (LOCATION) started successfully.`);
} catch (e) {
// 避坑指南:如果配置没有声明 backgroundModes,此 API 将直接抛出错误
console.error("BackgroundTaskHelper", `Failed to start continuous task. ErrorCode: ${e.code}, Message: ${e.message}`);
}
}
// 结束作业时,主动释放长时后台占位
public static async stopContinuousTask(): Promise<void> {
let ctx = BackgroundTaskHelper.context;
if (!ctx) return;
try {
await backgroundTaskManager.stopBackgroundRunning(ctx);
console.info("BackgroundTaskHelper", "Continuous task stopped, process returned to normal state");
} catch (e) {
console.error("BackgroundTaskHelper", `Failed to stop continuous task: ${e.code} ${e.message}`);
}
}
}4. 业务总控对接:联动开始/结束作业
有了这个辅助工具后,我们的业务总控单例 TrajectoryManager.ets 便可以在开启定位作业的同时,无缝拉起后台常驻盾牌:
import { LocationHelper } from './LocationHelper';
import { BackgroundTaskHelper } from './BackgroundTaskHelper';
import { IMUFloorEstimator } from './IMUFloorEstimator';
export class TrajectoryManager {
private static instance: TrajectoryManager | null = null;
private isTracking = false;
public static getInstance(): TrajectoryManager {
if (!TrajectoryManager.instance) {
TrajectoryManager.instance = new TrajectoryManager();
}
return TrajectoryManager.instance;
}
// 开始带看作业
public async startTracking() {
if (this.isTracking) return;
this.isTracking = true;
// 1. 一键激活后台长时任务,拉起盾牌通知栏,防止进程被系统打入 Suspended
await BackgroundTaskHelper.startContinuousTask();
// 2. 开启 IMU 惯性传感器与计步估算
IMUFloorEstimator.getInstance().start();
// 3. 注册持续高精度定位回调
LocationHelper.startLocationUpdates((location) => {
this.handleLocationUpdate(location);
});
}
// 结束带看作业
public async stopTracking() {
if (!this.isTracking) return;
this.isTracking = false;
// 停止定位与传感器
LocationHelper.stopLocationUpdates();
IMUFloorEstimator.getInstance().stop();
// 释放后台常驻,归还系统能耗,通知栏小图标自动消失
await BackgroundTaskHelper.stopContinuousTask();
}
}5. 总结与下期预告
通过调用系统的 backgroundTaskManager,我们的“轻轨迹”拿到了退居后台和锁屏状态下的“免挂起金牌”。当应用退到后台时,用户会清晰地看到状态栏常驻的绿色定位小胶囊,点击它便可一键安全回到轻轨迹主页。
后台保活的生命线被打通后,下一个挑战是:如何优雅、流畅地在地图上将这些经纬度坐标绘制出来?当多个轨迹点高频到达时,如何避免界面闪烁与卡死?
在下一篇文章中,我们将踏入地图渲染的核心领域:Map Kit 深度实践,轻轨迹实时路网绘制与起点终点自动捕获! 敬请期待。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。