简介
为什么会有后台任务?简单的说是对系统资源cpu使用和电量的一个整体优化。如果软件在后台无限制的话,很容易造成系统资源浪费,耗电量增加的问题。
- 什么是退至后台?
设备返回主界面
、锁屏
、应用切换
等操作会使应用退至后台
。
- 退至后台后会怎样
应用退至后台后,如果继续活动
,可能会造成设备耗电快
、用户界面卡顿
等现象。
- 进程的挂起和终止
为了降低设备耗电速度
、保障用户使用流畅度
,系统会对退至后台的应用进行管控,包括进程挂起
和进程终止
。挂起后,应用进程无法使用软件资源(如公共事件、定时器等)和硬件资源(CPU、网络、GPS、蓝牙等)。
- 应用
退至后台一小段时间
(由系统定义),应用进程会被挂起
。 - 应用退至后台,
在后台被访问一小段时间(由系统定义)后
,应用进程会被挂起
。 资源不足时
,系统会终止
部分应用进程(即回收该进程的所有资源)。
- 鸿蒙后台任务的引入
为了保障后台音乐播放
、日历提醒
等功能的正常使用,系统提供了规范内受约束的
后台任务,扩展应用在后台运行时间。
鸿蒙系统app资源使用约束
对于运行的进程,系统会给予一定的资源配额约束,包括进程在连续一段时间内内存的使用、CPU使用占比,以及24小时磁盘写的IO量,均有对应的配额上限。超过配额上限时,如果进程处于前台,系统会有对应的warning日志,如果进程处于后台,系统会终止该进程。
总结就是,cpu,内存,io使用都受系统约束。超出的话在前台给警告,后台就强杀
后台任务类型
标准系统支持规范内受约束的后台任务,包括短时任务
、长时任务
、延迟任务
、代理提醒
和能效资源
。
开发者可以根据如下的功能介绍,选择合适的后台任务,以满足应用退至后台后继续运行的需求。
-
短时任务:适用于实时性要求高、耗时不长的任务,例如状态保存。
-
长时任务:适用于长时间运行在后台、用户可感知的任务,例如后台播放音乐、导航、设备连接等,使用长时任务避免应用进程被挂起。
-
延迟任务:对于实时性要求不高、可延迟执行的任务,系统提供了延迟任务,即满足条件的应用退至后台后被放入执行队列,系统会根据内存、功耗等统一调度。
-
代理提醒:代理提醒是指应用退后台或进程终止后,系统会代理应用做相应的提醒。适用于定时提醒类业务,当前支持的提醒类型包括倒计时、日历和闹钟三类。
图1 后台任务类型选择
注意:
系统仅支持规范内受约束的后台任务。应用退至后台后,若未使用规范内的后台任务或选择的后台任务类型不正确,对应的应用进程会被挂起或终止。
应用申请了规范内的后台任务,仅会提升应用进程被回收的优先级。当系统资源严重不足时,即使应用进程申请了规范内的后台任务,系统仍会终止部分进程,用以保障系统稳定性。
短时任务
简介
用于应用在后台执行耗时不长的任务的情况,如状态保存等。
约束与限制
-
申请时机:要在前台或onBackground回调内,申请短时任务,否则会申请失败。
-
数量限制:一个应用同一时刻最多同时运行3个短时任务。。
-
配额机制 :一个应用会有一定的短时任务配额(根据系统状态和用户习惯调整),单日(24小时内)配额默认为10分钟,单次配额最大为3分钟,低电量时单次配额默认为1分钟,配额消耗完后不允许再申请短时任务。同时,系统提供获取对应短时任务剩余时间的查询接口,用以查询本次短时任务剩余时间,以确认是否继续运行其他业务。
-
配额计算:仅当应用在后台时,对应用下的短时任务计时;同一个应用下的同一个时间段的短时任务,不重复计时。
图1 短时任务配额计算原理图
说明:
-
任务数量统计:
在①②③时间段内的任意时刻,应用运行了2个短时任务;在④时间段内的任意时刻,应用运行了1个短时任务
-
任务耗时统计:
应用有两个短时任务A和B,在前台时申请短时任务A,应用退至后台后开始计时为①,应用进入前台②后不计时,再次进入后台③后开始计时,短时任务A结束后,由于阶段④仍然有短时任务B,所以该阶段继续计时。因此,在这个过程中,该应用短时任务总耗时为①+③+④。
-
任务取消:
任务完成后,应用需主动取消短时任务,否则会影响应用当日短时任务的剩余配额。
-
任务超时:
短时任务即将超时时,系统会回调应用,应用需要取消短时任务。如果超时不取消,系统会终止对应的应用进程。
-
代码示例
- 导入模块
javascript
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';
- 申请+回调
ini
let id: number; // 申请短时任务ID
let delayTime: number; // 本次申请短时任务的剩余时间
// 申请短时任务
function requestSuspendDelay() {
let myReason = 'test requestSuspendDelay'; // 申请原因
let delayInfo = backgroundTaskManager.requestSuspendDelay(myReason, () => {
// 回调函数。应用申请的短时任务即将超时,通过此函数回调应用,执行一些清理和标注工作,并取消短时任务
console.info('suspend delay task will timeout');
backgroundTaskManager.cancelSuspendDelay(id);
})
id = delayInfo.requestId;
delayTime = delayInfo.actualDelayTime;
}
- 获取剩余时间
typescript
let id: number; // 申请短时任务ID
async function getRemainingDelayTime() {
backgroundTaskManager.getRemainingDelayTime(id).then((res: number) => {
console.info('Succeeded in getting remaining delay time.');
}).catch((err: BusinessError) => {
console.error(`Failed to get remaining delay time. Code: ${err.code}, message: ${err.message}`);
})
}
查询本次短时任务的剩余时间,用以判断是否继续运行其他业务,例如应用有两个小任务,在执行完第一个小任务后,可以判断本次短时任务是否还有剩余时间来决定是否执行第二个小任务。
- 取消短时任务
bash
let id: number; // 申请短时任务ID
function cancelSuspendDelay() {
backgroundTaskManager.cancelSuspendDelay(id);
}
- 短时任务具体任务执行代码在哪里?
看完华为官方文档,一脸懵逼。介绍了一遍短时任务的申请取消和获取短时任务剩余时间。那么具体用户执行短时任务时自定义的业务逻辑代码在哪执行?这么重要的事竟然没在文档中解释。如想了解请看这篇论坛帖子。在这里我想吐槽一下,这么重要的信息竟然不在官方文档中体现,也真是。。。。傲慢。
- 对短时任务配额限制的思考
短时任务规则中有这么一段话"配额默认为10分钟,单次配额最大为3分钟,低电量时单次配额默认为1分钟,配额消耗完后不允许再申请短时任务"。
问题1: 单次配额默认为3分钟,低电量时默认为1分钟。那么我们在申请和执行短时任务时,是不是还得判断电量?电量到多少是低电量?
问题2: 配额默认10分钟,而且24小时内不能超出。假如说是第单时长高频次的任务,整体叠合下来超过10分钟,那么每日超过十分钟的逻辑代码该放到哪里执行?长时任务?如果放在长时任务中靠谱,那么我们为什么不用长时任务的靠谱api?用这不靠谱的垃圾货?
问题3: 你这短时任务不靠谱,设计它的意义何在?
长时任务
简介
-
应用退至后台后,在后台需要
长时间运行用户可感知的任务
,如播放音乐、导航等。 -
长时任务中可以申请
多种类型
的任务,并对任务类型进行更新。 -
应用退后台执行业务时,系统会做
一致性校验
,确保应用在执行相应的
长时任务。 -
同时,系统有与长时任务相关联的
通知栏消息
,用户删除通知栏消息时
,系统会自动停止长时任务
。
长时任务类型
表1 长时任务类型
参数名 | 描述 | 配置项 | 场景 |
---|---|---|---|
DATA_TRANSFER | 数据传输 | dataTransfer | 非托管形式的上传、下载,如在浏览器后台上传或下载数据。 |
AUDIO_PLAYBACK | 音视频播放 | audioPlayback | 音频、视频在后台播放,投播。说明: 支持在元服务中使用。 |
AUDIO_RECORDING | 录制 | audioRecording | 录音、录屏退后台。 |
LOCATION | 定位导航 | location | 定位、导航。 |
BLUETOOTH_INTERACTION | 蓝牙相关 | bluetoothInteraction | 通过蓝牙传输文件时退后台。 |
MULTI_DEVICE_CONNECTION | 多设备互联 | multiDeviceConnection | 分布式业务连接、投播。说明: 支持在元服务中使用。 |
TASK_KEEPING | 计算任务(仅对2in1开放) | taskKeeping | 如杀毒软件。 |
关于DATA_TRANSFER(数据传输)说明:
- 在数据传输时,若应用使用上传下载代理接口托管给系统,即使申请DATA_TRANSFER的后台任务,应用退后台时还是会被挂起。
- 在数据传输时,应用
需要更新进度
。如果进度长时间(超过10分钟)不更新,数据传输的长时任务会被取消。
关于AUDIO_PLAYBACK(音视频播放)说明:
- 若要通过AUDIO_PLAYBACK实现后台播放,须使用媒体会话服务(AVSession)的进行音视频开发。
- 投播,是指将一台设备是音视频投至另一台设备播放。投播退至后台,长时任务会检测音视频播放和投屏两个业务,只要有其一正常运行,长时任务就不会终止。
约束与限制
申请限制:Stage模型中,长时任务仅支持UIAbility申请;FA模型中,长时任务仅支持ServiceAbility申请。
数量限制:一个UIAbility(FA模型则为ServiceAbility)同一时刻仅支持申请一个长时任务,即在一个长时任务结束后才可能继续申请。如果一个应用同时需要申请多个长时任务,需要创建多个UIAbility;一个应用的一个UIAbility申请长时任务后,整个应用下的所有进程均不会被挂起。
运行限制:
-
申请了,但没用
申请长时任务后,应用未执行相应的业务,系统会对应用进行管控。如系统检测到应用申请了AUDIO_PLAYBACK(音视频播放),但实际未播放音乐,长时任务会被取消。
-
挂羊头,卖狗肉
申请长时任务后,应用执行的业务类型与申请的不一致,系统会对应用进行管控。如系统检测到应用只申请了AUDIO_PLAYBACK(音视频播放),但实际上除了播放音乐(对应AUDIO_PLAYBACK类型),还在进行录制(对应AUDIO_RECORDING类型)。
-
注意用完需关闭
应用按需求申请长时任务,当应用在后台运行
任务结束
时,要及时主动取消长时任务,否则系统会强行取消。例如用户主动点击音乐暂停播放时,应用需及时取消对应的长时任务;用户再次点击音乐播放时,需重新申请长时任务。另外音频在后台播放时被打断,系统会
自行检测和停止
长时任务,音频重启播放时,需要再次申请长时任务。播放音频的应用在后台停止长时任务的
同时
,需要暂停或停止音频流
,否则应用会被系统强制终止。 -
超负荷运行
若运行长时任务的进程后台负载
持续高于所申请类型的典型负载
,系统会对应用进行管控。
代码示例
-
申请权限
需要申请ohos.permission.KEEP_BACKGROUND_RUNNING权限,如何?申请权限。
-
声明后台模式类型
在module.json5配置文件中为需要使用长时任务的UIAbility声明相应的长时任务类型(配置文件中填写长时任务类型的配置项)。
json"module": { "abilities": [ { "backgroundModes": [ // 长时任务类型的配置项 "audioRecording" ] } ], ... }
-
导入模块
长时任务相关的模块为
@ohos.resourceschedule.backgroundTaskManager
和@ohos.app.ability.wantAgent
,其余模块按实际需要导入。javascriptimport { backgroundTaskManager } from '@kit.BackgroundTasksKit'; import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; import { rpc } from '@kit.IPCKit' import { BusinessError } from '@kit.BasicServicesKit'; import { wantAgent, WantAgent } from '@kit.AbilityKit';
-
申请
在Stage模型中,长时任务支持设备本应用申请,也支持跨设备或跨应用申请,跨设备或跨应用仅对系统应用开放。
设备本应用申请长时任务示例代码如下:
javascriptstartContinuousTask() { let wantAgentInfo: wantAgent.WantAgentInfo = { // 点击通知后,将要执行的动作列表 // 添加需要被拉起应用的bundleName和abilityName wants: [ { bundleName: "com.example.myapplication", abilityName: "MainAbility" } ], // 指定点击通知栏消息后的动作是拉起ability actionType: wantAgent.OperationType.START_ABILITY, // 使用者自定义的一个私有值 requestCode: 0, // 点击通知后,动作执行属性 actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] }; // 通过wantAgent模块下getWantAgent方法获取WantAgent对象 wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => { backgroundTaskManager.startBackgroundRunning(this.context, backgroundTaskManager.BackgroundMode.AUDIO_RECORDING, 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}`); }); }); }
通过getContext方法,来获取page所在的UIAbility上下文。private context: Context = getContext(this);
-
取消
javascript
stopContinuousTask() {
backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
console.info(`Succeeded in operationing stopBackgroundRunning.`);
}).catch((err: BusinessError) => {
console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
});
}
wantAgetn介绍
wantAgent是HarmonyOS中用于处理应用组件之间复杂交互的一个模块。它允许应用在一个统一的框架下管理多个应用组件的启动和数据传递,从而实现更灵活和强大的功能组合。
-
主要功能
- 启动应用组件 :wantAgent可以用来启动其他的ability或者service,这使得它成为应用内部以及应用间通信的重要工具。
- 传递数据 :在启动其他应用组件的同时,wantAgent还可以传递必要的数据,如参数、URI等,从而允许被启动的组件执行特定的操作。
-
使用示例
以下是一个使用wantAgent启动一个应用组件的示例:
typescriptimport { WantAgent, WantAgentInfo, BusinessError } from '@ohos.app.ability.wantAgent'; // 定义WantAgent的信息 let wantAgentInfo: WantAgentInfo = { wants: [{ action: 'action.START', entities: ['entity1'], type: 'MIMETYPE', uri: 'key={true,true,false}', parameters: { mykey0: 2222, mykey1: [1, 2, 3], mykey2: '[1, 2, 3]', mykey3: 'ssssssssssssssssssssssssss', mykey4: [false, true, false], mykey5: ['qqqqq', 'wwwwww', 'aaaaaaaaaaaaaaaaa'], mykey6: true, } } as Want], actionType: wantAgent.OperationType.START_ABILITIES, requestCode: 0, wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG] }; // 获取WantAgent的回调函数 function getWantAgentCallback(err: BusinessError, data: WantAgent) { if (err) { console.error(`getWantAgent failed, code: ${JSON.stringify(err.code)}, message: ${JSON.stringify(err.message)}`); } else { wantAgentData = data; } } // 尝试获取WantAgent try { wantAgent.getWantAgent(wantAgentInfo, getWantAgentCallback); } catch (err) { console.error(`getWantAgent failed, error: ${JSON.stringify(err)}`); }
在这个示例中,我们首先定义了一个WantAgentInfo对象,它包含了想要启动的应用组件的详细信息以及如何启动这些组件的指令。然后,我们使用
getWantAgent
方法来尝试创建一个WantAgent实例,这个实例将负责执行实际的启动和数据传递操作。通过使用wantAgent,开发者可以更加灵活地管理和控制应用内部以及应用之间的复杂交互,从而提高应用的功能性和可用性。
注意在数据传输类型的时候切记要更新进度。具体进度更新逻辑:暂无。
延迟任务
简介
- 应用退至后台后,需要执行实时性要求不高的任务,例如有网络时不定期主动获取邮件等,可以使用延迟任务。
- 当应用满足设定条件(包括网络类型、充电类型、存储状态、电池状态、定时状态等)时,将任务添加到执行队列,系统会根据内存、功耗、设备温度、用户使用习惯等统一调度拉起应用。
原理
介绍:
- 添加调度:应用调用延迟任务接口添加、删除、查询延迟任务
- 延迟任务管理模块:会根据任务设置的条件(通过WorkInfo参数设置,包括网络类型、充电类型、存储状态等)和系统状态(包括内存、功耗、设备温度、用户使用习惯等)统一决策调度时机
- 调度开始或结束 当满足调度条件或调度结束时,系统会回调应用WorkSchedulerExtensionAbility中 onWorkStart() 或 onWorkStop() 的方法,同时会为应用单独创建一个Extension扩展进程用以承载WorkSchedulerExtensionAbility,并给WorkSchedulerExtensionAbility一定的活动周期,开发者可以在对应回调方法中实现自己的任务逻辑。
约束与限制
-
数量限制 :一个应用同一时刻最多申请
10个
延迟任务。 -
执行频率限制 :系统会根据应用的活跃分组,对延迟任务做
分级管控
,限制延迟任务调度的执行频率。表1 应用活跃程度分组
应用活跃分组 延迟任务执行频率 活跃分组 最小间隔2小时 经常使用分组 最小间隔4小时 常用使用 最小间隔24小时 极少使用分组 最小间隔48小时 受限使用分组 禁止 从未使用分组 禁止 -
超时 :WorkSchedulerExtensionAbility单次回调最长运行
2分钟
。如果超时不取消,系统会终止
对应的Extension进程。 -
调度延迟 :系统会根据内存、功耗、设备温度、用户使用习惯等统一调度,如当
系统内存资源不足
或温度达到一定挡位
时,系统将延迟调度该任务。 -
WorkSchedulerExtensionAbility接口调用限制:为实现对WorkSchedulerExtensionAbility能力的管控,在WorkSchedulerExtensionAbility中限制以下接口的调用:
@ohos.resourceschedule.backgroundTaskManager (后台任务管理)
@ohos.backgroundTaskManager (后台任务管理)
相关介绍
-
主要接口
接口名 接口描述 startWork(work: WorkInfo): void; 申请延迟任务 stopWork(work: WorkInfo, needCancel?: boolean): void; 取消延迟任务 getWorkStatus(workId: number, callback: AsyncCallback): void; 获取延迟任务状态(Callback形式) getWorkStatus(workId: number): Promise; 获取延迟任务状态(Promise形式) obtainAllWorks(callback: AsyncCallback<Array>): void; 获取所有延迟任务(Callback形式) obtainAllWorks(): Promise<Array>; 获取所有延迟任务(Promise形式) stopAndClearWorks(): void; 停止并清除任务 isLastWorkTimeOut(workId: number, callback: AsyncCallback): void; 获取上次任务是否超时(针对RepeatWork,Callback形式) isLastWorkTimeOut(workId: number): Promise; 获取上次任务是否超时(针对RepeatWork,Promise形式) -
WorkInfo参数
名称 类型 必填 说明 workId number 是 延迟任务ID。 bundleName string 是 延迟任务所在应用的包名。 abilityName string 是 包内ability名称。 networkType NetworkType 否 网络类型。 isCharging boolean 否 是否充电。- true表示充电触发延迟回调,false表示不充电触发延迟回调。 chargerType ChargingType 否 充电类型。 batteryLevel number 否 电量。 batteryStatus BatteryStatus 否 电池状态。 storageRequest StorageRequest 否 存储状态。 isRepeat boolean 否 是否循环任务。- true表示循环任务,false表示非循环任务。 repeatCycleTime number 否 循环间隔,单位为毫秒。 repeatCount number 否 循环次数。 isPersisted boolean 否 注册的延迟任务是否可保存在系统中。- true表示可保存,即系统重启后,任务可恢复。false表示不可保存。 isDeepIdle boolean 否 是否要求设备进入空闲状态。- true表示需要,false表示不需要。 idleWaitTime number 否 空闲等待时间,单位为毫秒。 parameters [key: string]: number 或 string 或 boolean 否 携带参数信息。 WorkInfo参数用于设置应用条件,参数设置时需遵循以下规则:
- workId、bundleName、abilityName为必填项,bundleName需为本应用包名。
- 携带参数信息仅支持number、string、boolean三种类型。
- 至少设置一个满足的条件,包括网络类型、充电类型、存储状态、电池状态、定时状态等。
- 对于重复任务,任务执行间隔至少2小时。设置重复任务时间间隔时,须同时设置是否循环或循环次数中的一个。
-
延迟任务开始和结束的回调
接口名 | 接口描述 |
---|---|
onWorkStart(work: workScheduler.WorkInfo): void | 延迟调度任务开始的回调 |
onWorkStop(work: workScheduler.WorkInfo): void | 延迟调度任务结束的回调 |
代码示例
延迟任务调度开发步骤分为两步:1,实现延迟任务。2,申请和取消延迟任务。
-
实现延迟任务
-
新建工程目录及文件。
- 在工程entry Module对应的ets目录(./entry/src/main/ets)下,新建一个目录并命名为WorkSchedulerExtension。
- 在WorkSchedulerExtension目录下,新建一个ArkTS文件并命名为WorkSchedulerExtension.ets,用以实现延迟任务回调接口。
-
在WorkSchedulerExtension.ets文件中导包并创建MyWorkSchedulerExtensionAbility生命周期接口。
javascript//导包 import { WorkSchedulerExtensionAbility, workScheduler } from '@kit.BackgroundTasksKit'; //创建具体延迟任务执行逻辑类 export default class MyWorkSchedulerExtensionAbility extends WorkSchedulerExtensionAbility { // 延迟任务开始回调 onWorkStart(workInfo: workScheduler.WorkInfo) { console.info(`onWorkStart, workInfo = ${JSON.stringify(workInfo)}`); // 打印 parameters中的参数,如:参数key1 // console.info(`work info parameters: ${JSON.parse(workInfo.parameters?.toString()).key1}`) } // 延迟任务结束回调 onWorkStop(workInfo: workScheduler.WorkInfo) { console.info(`onWorkStop, workInfo is ${JSON.stringify(workInfo)}`); } }
-
在module.json5配置文件中注册WorkSchedulerExtensionAbility,并设置如下标签:
- type标签设置为"workScheduler"。
- srcEntry标签设置为当前ExtensionAbility组件所对应的代码路径。
bash{ "module": { "extensionAbilities": [ { "name": "MyWorkSchedulerExtensionAbility", "srcEntry": "./ets/WorkSchedulerExtension/WorkSchedulerExtension.ets", "label": "$string:WorkSchedulerExtensionAbility_label", "description": "$string:WorkSchedulerExtensionAbility_desc", "type": "workScheduler" } ] } }
-
-
申请和取消延迟任务
- 导入模块
javascriptimport { workScheduler } from '@kit.BackgroundTasksKit'; import { BusinessError } from '@kit.BasicServicesKit';
- 申请延迟任务
javascript// 创建workinfo const workInfo: workScheduler.WorkInfo = { workId: 1, networkType: workScheduler.NetworkType.NETWORK_TYPE_WIFI, bundleName: 'com.example.application', abilityName: 'MyWorkSchedulerExtensionAbility' } try { workScheduler.startWork(workInfo); console.info(`startWork success`); } catch (error) { console.error(`startWork failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`); }
- 取消延迟任务
javascript// 创建workinfo const workInfo: workScheduler.WorkInfo = { workId: 1, networkType: workScheduler.NetworkType.NETWORK\_TYPE\_WIFI, bundleName: 'com.example.application', abilityName: 'MyWorkSchedulerExtensionAbility' } try { workScheduler.stopWork(workInfo); console.info(`stopWork success`); } catch (error) { console.error(`stopWork failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`); }
任务超出时的处理?
在鸿蒙(HarmonyOS)的WorkSchedulerExtensionAbility中,onWorkStart()
和 onWorkStop()
方法是用于处理延迟任务的开始和结束回调。根据官方文档,这些方法存在执行时间的限制:
onWorkStart()
和onWorkStop()
方法的单次回调最长运行时间为2分钟。- 如果回调方法的执行时间超过2分钟而没有返回,系统会终止对应的Extension进程。
这意味着,如果在这些方法中执行的任务超过了时间限制,系统将不得不强制终止这些进程,以防止系统资源被过度消耗。
为了避免这种情况,您可以采取以下措施:
- 优化代码性能 :确保在回调方法中执行的业务逻辑足够高效,避免不必要的复杂计算或操作。
- 使用异步处理 :如果任务需要较长时间来完成,考虑使用异步处理方式,这样可以避免阻塞回调方法的执行线程。
- 设置超时处理 :在任务执行前,设置一个超时机制,如果达到预设时间仍未完成,则自动取消或简化任务。
通过上述方法,可以帮助您在使用WorkSchedulerExtensionAbility进行任务调度时,有效管理和减少超时风险,确保应用的稳定运行。
延迟任务问题和解答
-
问题:
1.WorkInfo中的workId可以设置任意数字吗?与其他workId相同会有问题吗?
2.WorkInfo中的其他参数是什么意思,比如网络类型,是手机在这个网络类型的情况下才执行这个延时任务吗?电池电量,是手机高于这个电量还是低于这个电量才执行这个延时任务吗?
3.WorkInfo的约束中,要求"重复任务时间间隔至少20分钟",但是对活跃应用,执行频率要求"最小间隔2小时",不活跃应用执行间隔更长,是不是"重复任务时间间隔至少20分钟"就没有意义了?
4.WorkInfo中,repeatCycleTime和repeatCount是不是只能设置一个?这个时候isRepeat都要设置为true吗?还是isRepeat和repeatCount只能设置一个,但都要设置repeatCycleTime?isRepeat是永久循环吗?
5.workScheduler.stopWork中的第二个参数是"是否需要取消的工作",是什么意思?如果填true,意思是停止的过程中可以取消?
6.延时任务在应用退到后台时还可以继续执行吗?受短时、长时任务约束吗?
-
答案:
1.可以,不能与其他workId相同,如果相同StartWork执行时会抛出异常
"StartWork failed. The work has been already added."
2.表示满足条件会执行延迟任务,比如手机会在这个网络类型的情况下才执行这个延时任务,电量高于设定电量会执行延迟任务。
3.系统应用可通过申请workscheduler能效资源使得不受执行频率限制,此时"重复任务时间间隔至少是20分钟"有意义,文档中描述的执行频率限制是针对三方应用而言
4.isRepeat和repeatCount只能设置一个,两种情况:1)设置isRepeat为true和repeatCycleTime为重复间隔,表示永久循环;2) 设置repeatCount为循环次数,repeatCycleTime为重复间隔。
5.表示停止的过程中是否需要将延迟任务从任务列表删除,如果填true,stopWork后该延迟任务不存在;如果为false,stopWork后延迟任务依然存在。
6.应用退到后台可以继续执行,不受短时长时任务约束,三种任务相互独立。
-
其他:
- 如何延迟任务分组?(暂无答案)
代理提醒
简介
- 应用场景 应用退到后台或进程终止后,仍然有一些提醒用户的定时类任务,例如时钟提醒等,为满足此类功能场景,系统提供了代理提醒(reminderAgentManager)的能力。
- 系统代理 当应用退至后台或进程终止后,系统会代理应用做相应的提醒。
- 提醒类型 当前支持的提醒类型包括:倒计时(基于倒计时的提醒功能)、日历(基于日历的提醒功能)和闹钟(基于时钟的提醒功能)。
- 管控机制 为了防止代理提醒被滥用于广告、营销类提醒,影响用户体验,代理增加了管控机制,管控后的使用方法请参考管控限制。
约束与限制
-
个数限制:一个三方应用支持最多30个有效提醒。
说明:
-
当到达设置的提醒时间点时,通知中心会弹出相应提醒。若未点击提醒上的关闭/CLOSE按钮,则代理提醒是有效/未过期的;若点击了关闭/CLOSE按钮,则代理提醒过期。
-
当代理提醒是周期性提醒时,如设置每天提醒,无论是否点击关闭/CLOSE按钮,代理提醒都是有效的。
-
-
跳转限制:点击提醒通知后跳转的应用必须是申请代理提醒的本应用。
-
管控限制 :管控后可通过日历Calendar Kit 替代代理提醒,实现相应的提醒功能,具体请参考# Calendar Kit简介;或者参考如下邮件格式向华为侧申请代理提醒权限,申请通过后会开通权益,即可正常调用代理提醒接口,当前仅对纯工具类应用开放申请。
邮件格式: 通过hwpush@huawei.com邮箱向华为侧申请,邮件会在10个工作日内回复(含权益开通结果),请留意邮箱消息。邮件的规定格式如下:
markdown邮件主题:【代理提醒权限申请】 邮件正文: 申请权限名称:代理提醒 企业名称: 应用名称:*** 应用包名:com. **.** 使用场景:提供申请理由/用途/尽可能附上图片,及使用代理提醒发通知/提醒的必要性。 通知标题:*** 通知文本:*** 通知场景:*** 通知频率:***
接口说明
接口名 | 描述 |
---|---|
publishReminder(reminderReq: ReminderRequest): Promise | 发布一个定时提醒类通知 |
cancelReminder(reminderId: number): Promise | 取消一个指定的提醒类通知 |
getValidReminders(): Promise<Array> | 获取当前应用设置的所有有效的提醒 |
cancelAllReminders(): Promise | 取消当前应用设置的所有提醒 |
addNotificationSlot(slot: NotificationSlot): Promise | 注册一个提醒类需要使用的通知通道(NotificationSlot) |
removeNotificationSlot(slotType: notification.SlotType): Promise | 删除指定的通知通道(NotificationSlot) |
开发步骤
-
申请权限
ohos.permission.PUBLISH_AGENT_REMINDER
-
导入模块
javascriptimport { reminderAgentManager } from '@kit.BackgroundTasksKit'; import { notificationManager } from '@kit.NotificationKit'; import { BusinessError } from '@kit.BasicServicesKit';
-
定义目标代理提醒
- 倒计时类型
rustlet targetReminderAgent: reminderAgentManager.ReminderRequestTimer = { reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_TIMER, // 提醒类型为倒计时类型 triggerTimeInSeconds: 10, actionButton: [ // 设置弹出的提醒通知信息上显示的按钮类型和标题 { title: 'close', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE } ], wantAgent: { // 点击提醒通知后跳转的目标UIAbility信息 pkgName: 'com.example.myapplication', abilityName: 'EntryAbility' }, maxScreenWantAgent: { // 全屏显示提醒到达时自动拉起的目标UIAbility信息 pkgName: 'com.example.myapplication', abilityName: 'EntryAbility' }, title: 'this is title', // 指明提醒标题 content: 'this is content', // 指明提醒内容 expiredContent: 'this reminder has expired', // 指明提醒过期后需要显示的内容 notificationId: 100, // 指明提醒使用的通知的ID号,相同ID号的提醒会覆盖 slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION // 指明提醒的Slot类型 }
- 日历类型
yamllet targetReminderAgent: reminderAgentManager.ReminderRequestCalendar = { // 提醒类型为日历类型 reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR, // 指明提醒的目标时间 dateTime: { year: 2023, month: 1, day: 1, hour: 11, minute: 14, second: 30 }, repeatMonths: [1], // 指明重复提醒的月份 repeatDays: [1], // 指明重复提醒的日期 // 设置弹出的提醒通知信息上显示的按钮类型和标题 actionButton: [ { title: 'close', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE }, { title: 'snooze', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE }, ], // 点击提醒通知后跳转的目标UIAbility信息 wantAgent: { pkgName: 'com.example.myapplication', abilityName: 'EntryAbility' }, // 全屏显示提醒到达时自动拉起的目标UIAbility信息 maxScreenWantAgent: { pkgName: 'com.example.myapplication', abilityName: 'EntryAbility' }, ringDuration: 5, // 指明响铃时长(单位:秒) snoozeTimes: 2, // 指明延迟提醒次数 timeInterval: 5*60, // 执行延迟提醒间隔(单位:秒) title: 'this is title', // 指明提醒标题 content: 'this is content', // 指明提醒内容 expiredContent: 'this reminder has expired', // 指明提醒过期后需要显示的内容 snoozeContent: 'remind later', // 指明延迟提醒时需要显示的内容 notificationId: 100, // 指明提醒使用的通知的ID号,相同ID号的提醒会覆盖 slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION // 指明提醒的Slot类型 }
- 闹钟类型
rustlet targetReminderAgent: reminderAgentManager.ReminderRequestAlarm = { // 提醒类型为闹钟类型 reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM, hour: 23, // 指明提醒的目标时刻 minute: 9, // 指明提醒的目标分钟 daysOfWeek: [2], // 指明每周哪几天需要重复提醒 actionButton: [ // 设置弹出的提醒通知信息上显示的按钮类型和标题 { title: 'close', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE }, { title: 'snooze', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE }, ], // 点击提醒通知后跳转的目标UIAbility信息 wantAgent: { pkgName: 'com.example.myapplication', abilityName: 'EntryAbility' }, // 全屏显示提醒到达时自动拉起的目标UIAbility信息 maxScreenWantAgent: { pkgName: 'com.example.myapplication', abilityName: 'EntryAbility' }, ringDuration: 5, // 指明响铃时长(单位:秒) snoozeTimes: 2, // 指明延迟提醒次数 timeInterval: 5*60, // 执行延迟提醒间隔(单位:秒) title: 'this is title', // 指明提醒标题 content: 'this is content', // 指明提醒内容 expiredContent: 'this reminder has expired', // 指明提醒过期后需要显示的内容 snoozeContent: 'remind later', // 指明延迟提醒时需要显示的内容 notificationId: 99, // 指明提醒使用的通知的ID号,相同ID号的提醒会覆盖 slotType: notificationManager.SlotType.SOCIAL_COMMUNICATION // 指明提醒的Slot类型 }
-
发布代理提醒
typescript
reminderAgentManager.publishReminder(targetReminderAgent).then((res: number) => {
console.info('Succeeded in publishing reminder. ');
let reminderId: number = res; // 发布的提醒ID
}).catch((err: BusinessError) => {
console.error(`Failed to publish reminder. Code: ${err.code}, message: ${err.message}`);
})
- 取消代理提醒
typescript
let reminderId: number = 1;
// reminderId的值从发布提醒代理成功之后的回调中获得
reminderAgentManager.cancelReminder(reminderId).then(() => {
console.log('Succeeded in canceling reminder.');
}).catch((err: BusinessError) => {
console.error(`Failed to cancel reminder. Code: ${err.code}, message: ${err.message}`);
});