OpenHarmony实战开发-合理运行后台任务

简介

设备返回主界面、锁屏、应用切换等操作会使应用退至后台。为了降低设备耗电速度、保障用户使用流畅度,系统会对退至后台的应用进行管控,包括进程挂起和进程终止。为了保障后台音乐播放、日历提醒等功能的正常使用,系统提供了受规范约束的后台任务,扩展应用在后台的运行时间。本文将介绍各类后台任务的基本概念和适用场景,并且通过对短时任务和长时任务两个场景的性能分析说明合理运行后台任务的必要性。

短时任务

应用退至后台一小段时间后,应用进程会被挂起,无法执行对应的任务。如果应用在后台仍需要执行耗时不长的任务,可以申请短时任务,扩展应用在后台的运行时间。

短时任务适用于小文件下载、缓存、信息发送等实时性高、需要临时占用资源执行的任务。详细的开发指导可参考短时任务。

场景示例

下面代码在申请短时任务后执行了一个耗时计算任务。

import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
import { BusinessError } from '@ohos.base';
import util from '@ohos.util';
import hiTraceMeter from '@ohos.hiTraceMeter';const totalTimes: number = 50000000; // 循环次数
const calculateResult: string = 'Total time costed = %s ms.'; // 文本格式@Entry
@Component
struct Index {@State message: string = 'Click button to calculate.';private requestId: number = 0;// 申请短时任务requestSuspendDelay() {try {let delayInfo = backgroundTaskManager.requestSuspendDelay('compute', () => {console.info('Request suspension delay will time out.');// 任务即将超时,取消短时任务this.cancelSuspendDelay();})this.requestId = delayInfo.requestId;} catch (error) {console.error(`requestSuspendDelay failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);}}// 取消短时任务cancelSuspendDelay() {backgroundTaskManager.cancelSuspendDelay(this.requestId);console.info('Request suspension delay cancel.');}// 计算任务computeTask(times: number): number {let start: number = new Date().getTime();let a: number = 1;let b: number = 1;let c: number = 1;for (let i: number = 0; i < times; i++) {a = a * Math.random() + b * Math.random() + c * Math.random();b = a * Math.random() + b * Math.random() + c * Math.random();c = a * Math.random() + b * Math.random() + c * Math.random();}let end: number = new Date().getTime();return end - start;}// 点击回调clickCallback = () => {this.requestSuspendDelay();hiTraceMeter.startTrace('computeTask', 0); // 开启性能打点let timeCost = this.computeTask(totalTimes);this.message = util.format(calculateResult, timeCost.toString());hiTraceMeter.finishTrace('computeTask', 0); // 结束性能打点this.cancelSuspendDelay();}build() {Column() {Row(){Text(this.message)}Row() {Button('开始计算').onClick(this.clickCallback)}.width('100%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').justifyContent(FlexAlign.Center)}
}

使用 IDE 中的 Time Profiler 获取示例应用从开始计算任务并退到后台执行一分钟内的性能数据。获取到的数据如下图。

图1 短时任务 Time Profiler 泳道图
在这里插入图片描述

  • ArkTS Callstack:基于时间轴展示 CPU 占用率和状态的变化。
  • User Trace:基于时间轴展示当前时段内触发用户自定义打点任务的具体情况。H:computeTask 表示短时任务执行用时。
  • Native Callstack:基于时间轴展示 CPU 占用率变化和进程/线程的活动状态以及函数调用栈。

从上图中可以看出,Native Callstack 泳道与 H:computeTask 相对应的时间段内应用进程处于活跃状态,CPU 占用率在较高范围内变化。任务取消后,应用仍然处于运行状态,但是进程的活跃程度和 CPU 占用率都明显下降,直到在几秒后系统将应用挂起,不再占用 CPU。

分别框选任务执行阶段和任务取消后未被挂起阶段对应的 Native Callstack 如下图,查看应用主线程在两个阶段的平均 CPU 占用率和最高 CPU 占用率情况。

图2 任务执行阶段的 CPU 占用率
在这里插入图片描述

图3 任务取消后未被挂起阶段的 CPU 占用率
在这里插入图片描述

可以看到应用主线程在任务执行阶段的平均 CPU 占用率为 12.6%,最高 CPU 占用率为 40.0%,在任务取消后未被挂起阶段的平均 CPU 占用率为 2.2%,最高 CPU 占用率为 28.6%。

在后台运行短时任务,会占用系统 CPU,在后台执行过多的短时任务就有可能会导致前台的应用卡顿,因此建议非必要情况不使用短时任务,使用时也避免同时申请过多的短时任务。

更多短时任务的使用限制和注意事项可以参考短时任务约束与限制。

长时任务

应用退至后台后,在后台需要长时间运行用户可感知的任务,如播放音乐、导航等。为防止应用进程被挂起,导致对应功能异常,可以申请长时任务,使应用在后台长时间运行。申请长时任务后,系统会做相应的校验,确保应用在执行相应的长时任务。详细的开发指导可参考长时任务。

长时任务支持的类型包括数据传输、音视频播放、录音、定位导航、蓝牙相关、多设备互联、WLAN 相关、音视频通话、计算任务。可以根据下表的场景举例选择相应的长时任务类型。
在这里插入图片描述

  • 申请了数据传输的长时任务,系统仅会提升应用进程的优先级,降低系统终止应用进程的概率,但仍然会挂起对应的应用进程。对于上传下载对应的功能,需要调用系统上传下载代理接口托管给系统执行,可以参考文件上传下载性能提升指导。
  • 申请音视频播放长时任务必须使用媒体会话服务,否则无法在后台播放。
  • 申请录音类型长时任务,需要有显著的用户提示,必须通过动态授权弹框来提供用户授权界面,请求用户授权麦克风权限。

场景示例

下面模拟一个后台定位的场景。应用订阅设备位置变化,每隔一秒获取位置信息,为了保证应用在退到后台后仍然可以使用定位服务,申请了定位类型的长时任务。

首先需要在 module.json5 配置文件中为需要使用长时任务的 EntryAbility 声明任务类型。

{"module": {..."abilities": [{"name": "EntryAbility",..."backgroundModes": ["location"]}],}
}

需要使用到的相关权限如下:

  • ohos.permission.INTERNET
  • ohos.permission.LOCATION_IN_BACKGROUND
  • ohos.permission.APPROXIMATELY_LOCATION
  • ohos.permission.LOCATION
  • ohos.permission.KEEP_BACKGROUND_RUNNING

权限申请方式参考配置文件权限申明,在 module.json5 中进行配置。其中部分权限申请以及打开使能通知开关需要用户手动确认。系统为申请的长时任务发布通知栏消息时,应用的使能通知开关必须处于开启状态,否则用户无法感知后台正在运行的长时任务。

后台定位的实现代码如下:

import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent';
import common from '@ohos.app.ability.common';
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
import { BusinessError } from '@ohos.base';
import geolocation from '@ohos.geoLocationManager';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import notificationManager from '@ohos.notificationManager';const TAG: string = 'BackgroundLocation';@Entry
@Component
export struct LongTermTaskView {@State latitude: number = 0;@State longitude: number = 0;aboutToAppear() {// 请求发送通知的许可notificationManager.requestEnableNotification().then(() => {console.info(`[EntryAbility] requestEnableNotification success`);// 申请定位相关权限let atManager = abilityAccessCtrl.createAtManager();try {atManager.requestPermissionsFromUser(getContext(this), ['ohos.permission.INTERNET','ohos.permission.LOCATION','ohos.permission.LOCATION_IN_BACKGROUND','ohos.permission.APPROXIMATELY_LOCATION']).then((data) => {console.info(`[EntryAbility], data: ${JSON.stringify(data)}`);}).catch((err: BusinessError) => {console.info(`[EntryAbility], err: ${JSON.stringify(err)}`);})} catch (err) {console.info(`[EntryAbility], catch err->${JSON.stringify(err)}`);}}).catch((err: BusinessError) => {console.error(`[EntryAbility] requestEnableNotification failed, code is ${err.code}, message is ${err.message}`);});}// 位置变化回调locationChange = async (location: geolocation.Location) => {console.info(TAG, `locationChange location =${JSON.stringify(location)}`);this.latitude = location.latitude;this.longitude = location.longitude;}// 获取定位async getLocation() {console.info(TAG, `enter getLocation`);let requestInfo: geolocation.LocationRequest = {priority: geolocation.LocationRequestPriority.FIRST_FIX, // 快速获取位置优先scenario: geolocation.LocationRequestScenario.UNSET, // 未设置场景信息timeInterval: 1, // 上报位置信息的时间间隔distanceInterval: 0, // 上报位置信息的距离间隔maxAccuracy: 100 // 精度信息};console.info(TAG, `on locationChange before`);geolocation.on('locationChange', requestInfo, this.locationChange);console.info(TAG, `on locationChange end`);}// 开始长时任务startContinuousTask() {let context: Context = getContext(this);// 通知参数,指定点击长时任务通知后跳转的应用let wantAgentInfo: wantAgent.WantAgentInfo = {wants: [{bundleName: (context as common.UIAbilityContext).abilityInfo.bundleName,abilityName: (context as common.UIAbilityContext).abilityInfo.name}],operationType: wantAgent.OperationType.START_ABILITY,requestCode: 0,wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]};wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {backgroundTaskManager.startBackgroundRunning(context,backgroundTaskManager.BackgroundMode.LOCATION, wantAgentObj).then(() => {console.info(`Succeeded in operationing startBackgroundRunning.`);}).catch((err: BusinessError) => {console.error(`Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);});});}// 停止长时任务stopContinuousTask() {backgroundTaskManager.stopBackgroundRunning(getContext()).then(() => {console.info(`Succeeded in operationing stopBackgroundRunning.`);}).catch((err: BusinessError) => {console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);});}build() {Column() {Column() {Text(this.latitude.toString())Text(this.longitude.toString())}.width('100%')Column() {Button('开启定位服务').onClick(() => {this.startContinuousTask();this.getLocation();})Button('关闭定位服务').onClick(async () => {await geolocation.off('locationChange');this.stopContinuousTask();}).margin({ top: 10 })}.width('100%')}.width('100%').height('100%').justifyContent(FlexAlign.Center)}
}

基于上述场景,使用功耗测试工具获取 30min 设备功耗,得到的数据如下表。
在这里插入图片描述

  • 归一电流:电压处于 3.8V 时的电流平均值。
  • 归一耗电的计算方式为:归一电流*测试时长/3600。3600 表示一小时的秒数。

对比后台存在长时定位任务和不存在长时任务时的功耗数据,当后台存在定位任务持续运行时,设备在 30 分钟内的功耗明显增加。

从功耗角度考虑,应用应该避免过多使用长时任务,针对必须使用长时任务的场景,也可以优化任务执行过程,减少设备功耗。以下是一些优化建议:

  • 对定位要求不太高的场景可以适当调整上报时间间隔和上报距离间隔,减少更新频率。
  • 尽可能的减少网络请求次数和减小网络请求时间间隔。
  • 数据传输中使用高效率的数据格式和解析方法,减少任务执行时间。

更多长时任务的使用限制和注意事项可以参考长时任务约束与限制。

延迟任务

应用退至后台后,如果需要执行实时性要求不高的任务,可以使用延迟任务。当应用满足设定条件(包括网络类型、充电类型、存储状态、电池状态、定时状态等)时,将任务添加到执行队列,系统会根据内存、功耗、设备温度、用户使用习惯等统一调度拉起应用。

延迟任务适用于软件更新、信息收集、数据处理等场景。详细的开发指导可参考延迟任务。

代理提醒

应用退到后台或进程终止后,仍然有一些提醒用户的定时类任务,例如购物类应用抢购提醒等,为满足此类功能场景,系统提供了代理提醒(reminderAgentManager)的能力。当应用退至后台或进程终止后,系统会代理应用做相应的提醒。详细的开发指导可参考代理提醒。

当前支持的提醒类型包括:

  • 倒计时:基于倒计时的提醒功能,适用于短时的计时提醒场景,例如抢购倒计时。
  • 日历:基于日历的提醒功能,适用于较长时间的日程提醒场景,例如生日提醒。
  • 闹钟:基于时钟的提醒功能,适用于闹钟相关场景,例如起床闹钟。

总结

合理的选择和使用后台任务对于优化用户体验,减少性能消耗非常重要。以下表格对比总结了各类后台任务的概念、适用场景以及任务执行过程中的应用状态。

在这里插入图片描述

如果大家还没有掌握鸿蒙,现在想要在最短的时间里吃透它,我这边特意整理了《鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程》以及《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

OpenHarmony APP开发教程步骤:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述

鸿蒙开发学习手册》:

如何快速入门:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.基本概念
2.构建第一个ArkTS应用
3.……

在这里插入图片描述

开发基础知识:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙生态应用开发白皮书V2.0PDF:https://docs.qq.com/doc/DZVVBYlhuRkZQZlB3

在这里插入图片描述


http://www.ppmy.cn/news/1431934.html

相关文章

【行为型模型】迭代器模式

一、迭代器模式概述 迭代器模式定义&#xff1a;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露其内部的表示。把游走的任务放在送代器上&#xff0c;而不是聚合上。这样简化了聚含的接口和实现,也让责任各得其所。(对象行为型) 迭代器模式的优缺点&…

MathType安装导致的Word粘贴操作出现运行时错误‘53’:文件未找到:MathPage.WLL

MathType安装导致的Word粘贴操作出现运行时错误‘53’&#xff1a;文件未找到&#xff1a;MathPage.WLL 解决方案 1、确定自己电脑的位数&#xff1b; 2、右击MathType桌面图标&#xff0c;点击“打开文件所在位置”&#xff0c;然后找到MathPage.WLL &#xff0c;复制一份进行…

图像处理基础知识

图像处理基础知识 图像 1、模拟图像 模拟图像&#xff0c;又称连续图像&#xff0c;是指在二维坐标系中连续变化的图像&#xff0c;即图像的像点是无限稠密的&#xff0c;同时具有灰度值&#xff08;即图像从暗到亮的变化值&#xff09;。 2、数字图像 数字图像&#xff0…

leetcode377--组合总数IV

1. 题意 给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。 请你从 nums 中找出并返回总和为 target 的元素组合的个数 2. 题解 与爬楼梯相似&#xff0c; 只是一次可以爬的阶梯数变多了&#xff0c;爬楼梯一次只能 c l i b [ 1 , 2 ] clib[1,2…

[leetcode] 57. 插入区间

文章目录 题目描述解题方法模拟java代码复杂度分析 相似题目 题目描述 给你一个 无重叠的 &#xff0c;按照区间起始端点排序的区间列表 intervals&#xff0c;其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束&#xff0c;并且 intervals 按照 starti 升序排列…

算法训练营第42天|一维背包问题 二维背包问题 LeetCode.416分割等和子集

二维背包问题 题目链接&#xff1a; 二维背包 代码&#xff1a; vector<int> weight(n, 0); // 存储每件物品所占空间vector<int> value(n, 0); // 存储每件物品价值for(int i 0; i < n; i) {cin >> weight[i];}for(int j 0; j < n; j) {cin &g…

杰理695的UI模式LED灯控制

UI模式LED灯修改每个模式对应的LED灯闪烁修改在ui_normal_status_deal(u8 *status, u8 *power_status, u8 ui_mg_para)

【机器学习】分类与预测算法的评价与优化

以实际案例解析F1值与P-R曲线的应用 一、分类算法与性能评价的重要性二、F1值与P-R曲线的概念与意义三、实例解析&#xff1a;以垃圾邮件检测为例四、代码实现与结果分析五、结论与展望 在数据驱动的时代&#xff0c;机器学习算法以其强大的数据处理和分析能力&#xff0c;成为…

Spring Cloud面试篇

面试篇-nacos面试题 1. springboot常见组件 注册中心组件&#xff1a;Eureka、Nacos 负载均衡组件&#xff1a;Ribbon 远程调用组件&#xff1a;OpenFeign 网关组件&#xff1a;Zuul、Gateway 服务保护组件&#xff1a;Hystrix、Sentinel 服务配置管理组件&#xff1a;SpringCl…

算法——BFS算法

1. 什么是BFS算法 BFS&#xff08;广度优先搜索&#xff0c;Breadth-First Search&#xff09;算法是一种用于图和树等数据结构中进行搜索的基本算法。它从指定的起始节点开始&#xff0c;逐层地向外扩展搜索&#xff0c;直到找到目标节点或遍历完整个图。 BFS算法的基本思想是…

【漏洞复现】锐捷 EG易网关 phpinfo.view.php 信息泄露漏洞

0x01 产品简介 锐捷EG易网关是一款综合网关产品&#xff0c;集成了先进的软硬件体系构架&#xff0c;并配备了DPI深入分析引擎、行为分析/管理引擎。这款产品能在保证网络出口高效转发的基础上&#xff0c;提供专业的流控功能、出色的URL过滤以及本地化的日志存储/审计服务。 …

webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)

文章目录 ⭐前言⭐canvas绘制图片&#x1f496;状态保存和恢复&#x1f496;移动、旋转、缩放、变形&#x1f496;移动绘制一个渐变的box&#x1f496;旋转&#x1f496;缩放 ⭐模拟冒泡排序过程⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享webgl canvas系…

MySQL中InnoDB的行级锁

InnoDB 实现了以下两种类型的行锁。 共享锁&#xff08;S&#xff09;&#xff1a;又称为读锁&#xff0c;简称S锁&#xff0c;共享锁就是多个事务对于同一数据可以共享一把锁&#xff0c;都能访问到数据&#xff0c;但是只能读不能修改。 排他锁&#xff08;X&#xff09;&am…

CANN 开发工具介绍

1、ATC工具 ATC&#xff08;Ascend Tensor Compiler&#xff09;是异构计 算架构CANN体系下的模型转换工具&#xff0c; 它可 以将开源框架的网络模型以及Ascend IR定义 的单算子描述文件&#xff08;json格式&#xff09;转换为昇腾 AI处理器支持的.om格式离线模型。 2、精度…

echerts饼图分割操作

在饼图制作中遇到了一个难点就是饼图中间是分散的 试了很多方法&#xff0c;最后选择了给每个值中间再加一节的处理方式&#xff0c;并把颜色设置为透明就能达到相同效果。 处理后的样式&#xff1a; 代码&#xff1a; let list this.data.list;/饼图内部展示数据// let _t…

数据结构和算法:贪心

贪心算法 贪心算法是一种常见的解决优化问题的算法&#xff0c;其基本思想是在问题的每个决策阶段&#xff0c;都选择当前看起来最优的选择&#xff0c;即贪心地做出局部最优的决策&#xff0c;以期获得全局最优解。 贪心算法和动态规划都常用于解决优化问题。它们之间存在一…

【C++】一篇文章带你深入了解list

目录 一、list的介绍二、 标准库中的list类2.1 list的常见接口说明2.1.1 list对象的常见构造2.1.1.1 [无参构造函数](https://legacy.cplusplus.com/reference/list/list/list/)2.1.1.2 [有参构造函数(构造并初始化n个val)](https://legacy.cplusplus.com/reference/list/list/…

[Windows] Bypass分流抢票 v1.16.25 五一黄金周自动抢票软件(2024.02.08更新)

五一黄金周要来了&#xff0c;火车票难买到&#xff0c;即便官网候选订票也要看运气&#xff0c;推荐使用这个靠谱的自动抢票软件&#xff0c; 该工具是目前市面上最好用口碑最好的电脑抢票软件&#xff0c;从13年到现在&#xff0c;作者依旧在更新&#xff0c;可以自动识别123…

C++循环结构案例 水仙花数

for循环 #include <iostream> using namespace std;// 水仙花数是指一个 3 数&#xff0c;它的每个位上的数字的 3次幂之和等于它本身 // 利用do...while 语句 求出所有3位数中的水仙花数// 在100到999之间找到&#xff0c;所有 3次幂之和等于它本身 的数int main() {i…

Android自带模拟器如何获得ROOT权限

如果在模拟器中不能切换到root权限&#xff0c;很可能是镜像使用的不对。 一.选择镜像标准&#xff1a; 1.运行在PC端选X86_64镜像&#xff0c;才能流畅运行 2.不带google api的镜像 二.步骤 在虚拟机管理器中新建AVD&#xff0c;并下载符合要求的镜像文件 三.验证