尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

鸿蒙长时任务开发指南:后台定位、播放与下载的合规实现

鸿蒙长时任务开发指南:后台定位、播放与下载的合规实现 做鸿蒙应用开发几乎每个人都会撞上同一个问题应用一退后台过不了几分钟就被系统挂起定位不动了、下载停了、音频断了。去年我给一个运动类App适配鸿蒙的时候用户反馈最多的就是“轨迹老是断线”打开日志一看应用进入后台两分钟后CPU和网络全部被冻结GPS回调直接停更。后来我把整套后台机制研究了一遍才算真正搞明白鸿蒙里的后台运行不是安卓那套进程保活逻辑它有自己明确的三层框架——长时任务、延迟任务、后台代理。而长时任务就是其中最重要的那一层。这篇文章我把长时任务的完整接入过程、边界条件、踩坑记录都整理出来。适用对象是正在做鸿蒙原生应用开发需要实现后台定位、后台播放、后台上传下载、导航等场景的开发者也适合刚从安卓/iOS转过来的同学快速理解鸿蒙的后台机制。文章不会讲什么黑科技保活只讲系统允许的、正规的、能过审的方案。1. 先搞清楚系统为什么“管得宽”鸿蒙后台运行的三层机制1.1 应用退到后台后发生了什么“后台运行”在用户眼里是个很朴素的动作把App切走让它继续干活。但在鸿蒙系统视角里应用退到后台后系统不是简单地“杀掉进程”或者“保留进程”而是分级管理当应用还在前台时进程正常调度所有任务畅通无阻。切成后台后系统给一个短暂缓冲期应用仍然可以执行一些收尾代码比如保存草稿、释放资源。缓冲期结束进程被冻结Freeze无法访问CPU、网络、传感器等资源内存虽然还在但逻辑代码完全停止执行。用户再次回到应用时进程恢复界面状态重建。这个“冻结”动作是全局性的。就算应用还在冷却的内存里躺着只要没被冻结任何定时器、网络请求、传感器回调统统不再触发。很多安卓开发者会奇怪“我的Service怎么不跑了”因为鸿蒙不认Service那套它只认“前台时期望继续运行的任务类型”。你可以把鸿蒙的后台管理想象成一个非常严格的物业你离开房间之后灯可以继续亮着但前提是你得登记而且窗户必须让保安看得见里面的情况。如果不登记就偷偷亮灯物业直接把电闸拉了下次再想开灯就更难。这种设计的目的很简单手机上跑着几十个App如果每个App都有自己的后台逻辑电量半天就没了还会互相抢占资源。鸿蒙选择了一个统一的资源调度框架让应用在后台的“活动”必须在系统允许的框架内进行。1.2 系统给开发者留的三条合规出口系统知道自己不能把后台全部堵死某些场景确实需要后台能力。所以它开放了三条合法路径这也是你在做后台方案选型时必须先建立的整体认知长时任务Continuous Task对应那些用户能明确感知、需要长时间持续运行的任务比如音乐播放、导航、录音、下载大文件。这类任务系统网开一面允许进程在后台持续运行但有明确的场景限制和通知展示要求。延迟任务Work Scheduler对应那些可以“等一会儿再执行”的任务比如网络恢复后上传日志、凌晨做数据校准。系统会选择一个合适的时机集中执行对电量最友好。后台代理System Agent系统代替应用执行的能力比如消息推送、地理位置围栏、数据预取应用不需要在后台运行系统直接把结果下发给你。这三条路径里长时任务的争议和误解最大。它性能最高、不被冻结但申请门槛也最高——你必须有明确的业务场景并且要让用户能感知到任务正在运行。后面几个章节我全部围绕长时任务来展开。2. 长时任务能申请哪些场景边界比你想的更严格2.1 长时任务允许的场景类型在HarmonyOS的API中长时任务的类型是一个枚举。不同版本集合稍有差异但常用场景很稳定我整理了一个表方便你对照自己的业务模式枚举典型场景用户可感知点DATA_TRANSFER大文件下载、视频上传、大量数据同步下载进度条、上传状态AUDIO_PLAYBACK音乐播放、有声书正在播放的曲目信息AUDIO_RECORDING录音、语音备忘录录音波形、计时LOCATION连续定位、轨迹记录地图上的轨迹变化GPS_NAVIGATION实时导航、路线引导路线提示、导航语音BLUETOOTH_INTERACTION蓝牙耳机交互、外设连接蓝牙设备连接状态MULTI_DEVICE_CONNECTION多设备协同传输设备连接状态VOIP音视频通话通话界面、通话计时申请时你需要声明自己属于哪一种而不是随便选。比如你做一个听书App后台播放音频选AUDIO_PLAYBACK没问题但你如果只是想借长时任务在后台跑个定时器刷数据那不属于任何类型严格来说就不符合长时任务的定义系统也不会替你背书。实际开发中我见过不少申请LOCATION来做打卡提醒的案例这就是典型的“场景错配”审核阶段和系统判定阶段都会被卡。2.2 长时任务不等于无限后台长时任务有三个硬边界我在实际开发中挨个撞过这里一次说清楚第一必须在应用活跃期间启动。准确说启动长时任务时应用应当已经在前台运行过且长时任务启动后应用可以在用户切到后台后继续保持运行。反过来说如果你先让应用切到后台然后再调用启动接口这个请求很可能会直接失败。官方文档对这个时机的表述是“建议在应用处于前台时启动长时任务”但不少开发者踩过“退后台再启动”的坑包括我自己。第二任务必须可结束。长时任务不是给你常驻后台用的“免死金牌”。系统对单次长时任务的持续时间有明确上限而且文档中要求业务结束必须主动调用停止接口。如果你一直不停止系统可能在任务时长超限后直接销毁你的进程甚至对后续任务的申请做降级处理。所以长时任务的正确姿势是需要的时候启动不需要的时候立刻停止。第三必须让用户看到“你还在干活”。启动长时任务后系统要求应用发布一条常驻通知通常建议在启动成功后立即发布。通知里的内容应当反映任务的实际状态比如“正在下载45%”。这条通知不是装饰它承担着“向用户解释为什么你这个应用还在后台耗电”的责任。反过来讲如果用户从通知栏手动取消这个通知也就意味着用户希望终止任务应用应当感知并停止对应的后台工作否则会出现“用户明明关了通知后台还在跑”的糟糕体验。2.3 哪些需求其实不该用长时任务最容易误用的是以下三类场景我在技术社区里经常看到有人问顺手列出来定时器类型的“每5分钟轮询一次接口”。这个不属于长时任务场景应该用延迟任务或后台代理硬上长时任务反而容易被系统识别为异常。纯消息推送。推送有系统级的推送服务不需要App自己在后台维持长连接更不需要长时任务。保活类需求比如“让用户进程一直在内存里”。长时任务不是保活工具也不应该被拿来当保活工具。滥用会导致应用在应用市场上架被拒审也会被系统降级限制后台能力。判断自己的业务是否适合长时任务有一个非常简单的问题用户能说出“这个App刚才在我切走之后干了什么事”吗如果能才需要考虑长时任务。3. 从权限申请到任务停止长时任务接入全流程3.1 权限与模块声明第一步是配置权限。在项目根模块的module.json5文件中添加{ module: { requestPermissions: [ { name: ohos.permission.KEEP_BACKGROUND_RUNNING } ] } }如果你要用的是LOCATION或GPS_NAVIGATION模式还需要额外申请定位权限{ name: ohos.permission.LOCATION, reason: $string:location_reason, usedScene: { abilities: [EntryAbility], when: inuse } }这里有个容易忽略的细节如果选择LOCATION模式仅声明KEEP_BACKGROUND_RUNNING还不够定位权限的usedScene里要如实声明后台场景。另外连续定位会触发系统弹窗让用户授权用户拒绝后长时任务中的定位能力将不可用。这里的reason字段是向用户解释“为什么需要这个权限”的文案一定要写清楚被拒的可能性会低很多。3.2 启动长时任务的代码逻辑以API 12的推荐写法为例导入路径在新旧SDK之间略有差异新版使用kit.BackgroundTasksKit旧版用ohos.app.ability.backgroundTaskManagerimport { backgroundTaskManager } from kit.BackgroundTasksKit; import { wantAgent, WantAgent } from kit.AbilityKit; function startContinuousTask(context: Context, mode: backgroundTaskManager.BackgroundMode) { // 1. 构造 wantAgent用于系统回调以及点击常驻通知时跳转页面 const wantAgentInfo: wantAgent.WantAgentInfo { wants: [ { bundleName: com.example.myapp, abilityName: EntryAbility, parameters: { from: continuousTask } } ], actionType: wantAgent.OperationType.START_ABILITY, requestCode: 0, }; wantAgent.getWantAgent(wantAgentInfo).then((agent: WantAgent) { // 2. 调用长时任务启动接口 return backgroundTaskManager.startBackgroundRunning( context, mode, agent, ); }).then(() { console.info(长时任务启动成功); // 3. 启动成功后立即发布常驻通知 publishContinuousNotification(); }).catch((err: BusinessError) { console.error(长时任务启动失败: code${err.code}, message${err.message}); }); }这段代码不算复杂但有一个点值得展开wantAgent是一个“代理意图”它不是直接拉起一个Ability而是把启动这个动作交给系统去做。在长时任务里它有两个作用一是系统需要拿着这个Agent才能在必要时唤起你的应用二是点击通知栏里的常驻通知时通过它跳转到对应页面。很多新手在这里直接传空值然后发现启动逻辑报错或者点通知没反应原因就是wantAgent没构造对。3.3 常驻通知与任务停止的配套处理常驻通知我建议用系统通知接口发布代码示例如下import { notificationManager } from kit.NotificationKit; function publishContinuousNotification() { const request: notificationManager.NotificationRequest { id: 1, content: { notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: 任务运行中, text: 正在执行后台任务, }, }, isOngoing: true, isUnremovable: false, }; notificationManager.publish(request).then(() { console.info(常驻通知发布成功); }).catch((err: BusinessError) { console.error(通知发布失败: code${err.code}); }); }isOngoing字段控制这条通知是否可以滑动删除通常设成true表示任务持续中。工程上建议增加一个用户移除监听的回调用户主动移除通知时应用应当感知并停止对应的后台工作。停止长时任务的代码对应如下function stopContinuousTask(context: Context) { backgroundTaskManager.stopBackgroundRunning(context).then(() { console.info(长时任务已停止); notificationManager.cancel(1); }).catch((err: BusinessError) { console.error(停止失败: code${err.code}); }); }这里有个我踩过的配套问题长时任务停止后业务逻辑也要跟着停止否则会出现“进程不冻结但业务还在跑”的情况或者“业务停了但通知还在”的情况。我一般会用一个统一的TaskController类来管理业务状态和长时任务状态停止动作同时做两件事避免资源残留。4. 定位、播放、上传三个高频场景的差异化处理4.1 连续定位场景先想清楚定位的连续性等级在运动类App里LOCATION模式是最常见的。轨迹记录、跑步配速、骑行里程这些都是典型的连续定位场景。接入时我总结了几条经验使用geoLocationManager的持续定位接口开始接收定位回调根据自己的业务需要设置优先级和更新频率不要无脑开最高精度特别费电。在长时任务运行期间不要多次调用启动定位的接口否则可能出现多个定位实例叠加系统资源被重复占用。停止定位时同时调用停止定位的接口和停止长时任务的接口顺序不要反。有一个很容易被忽略的细节不是所有需要后台定位的业务都适合LOCATION模式。如果你的App只在用户点击“开始运动”之后才需要记录轨迹那用LOCATION完全没问题但如果你是一个到家提醒类App希望“全天候判断用户是否进入某个地理围栏”这更适合用系统提供的地理围栏能力而不是自己申请长时任务硬扛。区别在于前者是用户主动触发、用户明确感知的后者应该交给系统级能力。4.2 音频播放场景长时任务和音频焦点的配合后台播放和长时任务的关系是绑定最紧的没有长时任务音乐App切后台必断。这个场景的核心要点有三个第一选择AUDIO_PLAYBACK模式同时启动自己的音频播放实例。长时任务保证的是“进程不被冻结”但音频能不能继续出声还取决于音频会话是否正常两者需要同时就绪。第二必须处理音频焦点。电话进来时你的App应该暂停播放并释放音频焦点通话结束后再恢复。如果只靠长时任务硬撑不处理焦点逻辑用户会被突然出现的背景音乐激怒审核阶段也会被判定体验不合格。第三停止顺序问题。音频播放场景停任务时一定要先把播放实例释放掉再调用停止长时任务的接口。如果反着来长时任务已经停止进程随时可能被冻结你再去释放播放器经常会出现底层会话已经失效的异常。这个顺序问题在第五章我会再展开说。4.3 数据传输场景断点续传和进度同步大文件下载、视频上传、大量数据同步选DATA_TRANSFER模式。这个场景相对前两个稍有不同因为数据传输本身对“连续性”的敏感度略低但对“进度一致性”的要求更高。我的做法是用系统提供的下载服务或自带文件传输模块时注意保存任务的唯一标识方便长时任务中断后重新拉起。通知栏上的进度必须和真实进度同步。我见过很多App通知栏显示的进度和任务实际进度对不上用户投诉“一直卡在99%”本质就是进度是假的。长时任务机制无法保证传输永远不中断网络切换、系统重启、任务超时都可能打断传输。所以需要设计断点续传逻辑任务被打断后重新启动长时任务接着上次的位置继续传而不是重新传整个文件。数据传输场景还有一个容易被忽略的点上传和下载对用户的可感知程度不一样。下载大文件用户能理解后台一直上传用户就会起疑心“这个App在我后台偷偷传什么东西”。所以通知文案要直白写明“正在上传xx文件”消除不信任感。5. 真机调试踩坑记录从“模拟器一切正常”到“真机三分钟掉线”5.1 坑一退到后台才启动长时任务启动直接失败我第一次调试时为了模拟真实场景先把App退到后台然后通过某种方式触发长时任务结果立刻收到启动失败的回调。当时我一度怀疑是权限配置问题查了半天没结果。后来搜到官方文档里一句“建议在应用处于前台时启动”才明白问题出在启动时机上。解决办法很简单在业务拉起时也就是页面还在前台的时候先调用startBackgroundRunning然后立即把App切到后台验证。模拟器上这个现象不明显因为模拟器对电源策略没那么敏感所以长时任务这种跟系统调度强相关的能力一定要真机调试而且最好在设置了锁屏密码、开启了省电模式的手机上测。5.2 坑二通知没及时发布任务被系统悄悄回收某次我把启动长时任务的代码和发布通知的代码分开写通知发布逻辑放在一个异步回调里里面还顺便做了几个网络请求。结果任务启动一会儿之后就没动静了后台业务完全停摆。我查日志才发现长时任务启动后的很短时间内系统如果没看到应用发布常驻通知会直接撤销任务权限然后把进程冻结掉。从那以后我的做法是启动长时任务之前就把通知内容准备好启动成功后立刻发布中间不要穿插耗时逻辑。如果确实有别的初始化工作要做放在启动长时任务之前做而不是夹在中间。5.3 坑三停止任务后的资源残留与状态不一致还有一次我停止长时任务后音频还在播放。当时我的代码顺序是先stopBackgroundRunning然后再release播放器。结果长时任务权限已经没了进程被冻结的瞬间播放器底层会话直接报异常但音频卡在了“半死”状态通知也取消不了用户那边看起来就是这个App卡死了。正确顺序应该是先暂停/释放业务资源再停止长时任务。这个顺序我后来写进了代码规范里团队其他人也照着改类似问题再没出现过。5.4 排查思路如何确认任务是否被冻结我现在排查后台问题的套路比较固定分享给你看日志中长时任务相关的输出启动成功与否会有明确日志。切后台后观察业务是否继续打印日志。如果日志断了几分钟说明冻结已经发生。看通知栏里的常驻通知是否还在通知消失往往意味着任务被系统回收或者应用被杀。连接hdc工具查看进程状态确认进程是正常运行还是已被置为冻结状态。排查顺序我建议按这个来先确认“任务在不在”再查“业务为什么停”不要一上来就去翻业务代码很多时候根本不是业务逻辑的问题。6. 后台能力的最终选型不是所有“后台”都该用长时任务6.1 三条路径怎么选把长时任务研究明白之后你会发现它只是鸿蒙后台体系里的一块拼图具体选哪条路径取决于你的业务特征。我做了个对比表方便你快速定位需求特征推荐方案原因连续定位、音频播放、导航、录音、长传输长时任务系统允许持续运行但需要常驻通知可以推迟一段时间、特定条件下触发的工作延迟任务系统集中调度省电不占资源推送、地理围栏、数据预取后台代理系统级能力应用无需常驻只是回到前台后立刻做的事什么都不用申请正常前台逻辑即可6.2 一个判断法则我这些年做后台方案总结出的一句话是用户能直接感知到“进行中”的用长时任务用户不关心什么时候完成的用延迟任务用户根本不需要感知、系统自己也能办的用后台代理。按这个法则来选基本不会错。比如下载一部电影用户看着进度条这是长时任务App初始化后的日志上报用户完全不知道也不需要知道这是延迟任务消息推送到达用户看到了一个系统的推送卡片自己App都没启动这是后台代理。6.3 最后几句实在话长时任务并不是什么“黑科技”它是鸿蒙给正规场景开的一扇门。不要拿它做保活、做轮询、做隐形后台操作。合规使用的好处是系统资源调度有保障上架审核也顺利用户也不会因为后台耗电问题跑到应用商店给你一星差评。如果项目使用的是跨端框架比如热词里提到的uniapp这类方案我的建议是把长时任务这类原生强相关的能力封装成原生插件来调用而不是指望框架层能自动映射到鸿蒙的后台机制避免框架层拦截系统接口而出现各种奇怪问题。鸿蒙的官方开发文档里对长时任务的描述其实不难懂只是散落在各个章节里我把整个流程串了一遍之后最大的感受是只要你把“系统为什么要限制后台”这个问题想明白了长时任务的所有设计细节都会变得非常自然。
返回列表