鸿蒙系统app Service服务的用法
后台任务调度和管控
- HarmonyOS将应用的资源使用生命周期划分为前台、后台和挂起三个阶段。前台运行不受资源调度的约束,后台会根据应用业务的具体任务情况进行资源使用管理,在挂起状态时,会对应用的资源使用进行调度和控制约束,以保障其他体验类业务对资源的竞争使用。
- 后台任务调度和管控主要对在后台状态下的资源使用进行控制,应用从前台退到后台,可能有各种业务诉求,为了达到系统资源使用能效最优的目的,HarmonyOS提供了后台任务能力。
后台任务类型
本文描述的后台任务特指应用或业务模块处于后台(无可见界面)时,有需要继续执行或者后续执行的业务。对这些应用不可见但要继续或者将要执行的业务动作,为避免后台任务调度和管控对业务执行的影响,HarmonyOS将后台任务分为三种类型:
- 无后台业务:退后台后,无任务需要处理。
- 短时任务:退后台后,如果有紧急不可推迟且短时间能完成的任务,如应用退后台要进行数据压缩,不可中断,则使用短时任务申请延迟进入挂起(Suspend)状态。
- 长驻任务:如果是用户发起的可感知业务需要长时间后台运行的,如后台播放音乐、导航、上传下载、设备连接、VoIP等,则使用长驻任务避免进入挂起(Suspend)状态。
短时任务
退到后台的应用有不可中断且短时间能完成的任务时,可以使用短时任务机制,该机制允许应用在后台短时间内完成任务,保障应用业务运行不受后台生命周期管理的影响。
- 注意:短时任务仅针对应用的临时任务提供资源使用生命周期保障,限制单次最大使用时长为3分钟,全天使用配额默认为10分钟(具体时长系统根据应用场景和系统状态智能调整)。
- 接口请参考:BackgroundTaskManager接口说明。
短时任务使用约束
短时任务的使用需要遵从如下约束和规则:
- 申请时机:允许应用在前台时,或退后台在被挂起之前(应用退到后台默认有6~12秒的运行时长,具体时长由系统根据具体场景决定)申请延迟挂起,否则可能被挂起(Suspend),导致申请失败。
- 超时:延迟挂起超时(Timeout),系统通过回调知会应用,应用需要取消对应的延迟挂起,或再次申请延迟挂起。超期不取消或不处理,该应用会被强制取消延迟挂起。
- 取消时机:任务完成后申请方应用主动取消延时申请,不要等到超时后被系统取消,否则会影响该应用的后台允许运行时长配额。
- 配额机制:为了防止应用滥用保活,或者申请后不取消,每个应用每天都会有一定配额(会根据用户的使用习惯动态调整),配额消耗完就不再允许申请短时任务,所以应用完成短时任务后立刻取消延时申请,避免消耗配额。(注,这个配额指的是申请的时长,系统默认应用在后台运行的时间不计算在内)。
长驻任务
长驻任务类型给用户能直观感知到的且需要一直在后台运行的业务提供后台运行生命周期的保障。比如,业务需要在后台播放声音,或者需要在后台持续导航定位等,此类用户可以感知到的后台业务行为,可以通过使用长驻任务对应的后台模式保障业务在后台的运行,支撑应用完成在后台的业务。
后台模式分类
HarmonyOS提供了十种后台模式,供需要在后台做长驻任务的业务使用,具体的后台模式类型如下:
长驻任务后台模式 | 英文名 | 描述 |
---|---|---|
数据传输 | data-transfer | 通过网络/对端设备进行数据下载、备份、分享、传输等业务 |
播音 | audio-playback | 音频输出业务 |
录音 | audio-recording | 音频输入业务 |
画中画 | picture-in-picture | 画中画、小窗口播放视频业务 |
音视频通话 | voip | 音视频电话,VoIP业务 |
导航/位置更新 | location | 定位、导航业务 |
蓝牙设备连接及传输 | bluetooth-interaction | 蓝牙扫描、连接、传输业务 |
WLAN设备连接及传输 | wifi-interaction | WLAN扫描、连接、传输业务 |
屏幕抓取 | screen-fetch | 录屏、截屏业务 |
多设备互联 | multiDeviceConnection | 多设备互联,分布式调度和迁移等业务 |
使用长驻任务
- HarmonyOS应用开发工具DevEco Studio在业务的ServiceAbility创建的时候提供了后台模式的选择,针对当前创建的ServiceAbility可以赋予对应的后台模式类型设置,如下图所示:
业务根据需要选择对应的后台模式以后,会在应用的config.json文件中新创建的ServiceAbility组件下生成对应选择的后台模式配置,如下图所示:
说明
只有ServiceAbility才有对应的后台模式类型选择和配置。
- 在Service创建的方法里面调用keepBackgroundRunning(),将Service与通知绑定。
调用keepBackgroundRunning()方法前需要在配置文件中声明ohos.permission.KEEP_BACKGROUND_RUNNING权限。
完成对应的后台业务以后,在销毁服务的方法中调用cancelBackgroundRunning()方法,即可停止使用长驻任务。
请参考:前台Service。
长驻任务使用约束
- 如果用户选择可感知业务(如播音、导航、上传下载等),触发对应后台模式,在任务启动时或退入后台时,需要提醒用户。
- 如果任务结束,应用应主动退出后台模式。若在后台运行期间,系统监测到应用并未使用对应后台模式的资源,则会被挂起(Suspend)。
- 避免不合理地申请后台长驻任务,长驻任务类型要与应用的类型匹配,如任务类型与应用类型匹配表所示,如果执行的任务和申请的类型不匹配,也会被系统检测到并被挂起(Suspend)。
- 长驻任务是为了真正在后台长时间执行某任务,如果一个应用申请了长驻任务,但在实际运行过程中,并未真正运行或执行此类任务时,也会被系统检测到并被挂起(Suspend)。
托管任务
托管任务是系统提供的一种后台代理机制。通过系统提供的代理API接口,用户可以把任务(如后台下载、定时提醒、后台非持续定位)交由系统托管。
托管任务类型
- 托管任务-后台非持续定位(non-sustained Location):如果应用未申请location常驻模式,且在后台依然尝试获取位置信息,此时应用行为被视为使用非持续定位能力,后台非持续定位限制每30分钟提供一次位置信息。应用不需要高频次定位时,建议优先使用非持续定位。
- 托管任务-后台提醒代理(Reminder):后台提醒代理主要提供了一种机制,使开发者在应用开发时,可以调用这些接口去创建定时提醒,包括倒计时、日历、闹钟三种提醒类型。使用后台代理提醒能力后,应用可以被冻结或退出,计时和弹出提醒的功能将被后台系统服务代理。请参考:Reminder接口使用说明。
- 托管任务-后台下载代理:系统提供DownloadSession接口实现下载任务代理功能,应用提交下载任务后,应用被退出,下载任务仍然可以继续执行,且支持下载任务断点续传。请参考:DownloadSession接口使用说明。
托管任务使用约束
- 后台下载代理,系统会根据用户场景和设备状态,对不同的下载任务进行相应的管控,避免影响功耗和性能。
- 后台非持续定位和后台提醒代理需要申请对应的权限。后台提醒需要申请ohos.permission.PUBLISH_AGENT_REMINDER权限,后台非持续定位需要申请ohos.permission.LOCATION和ohos.permission.LOCATION_IN_BACKGROUND权限。
- 资源滥用会影响系统性能和功耗,托管任务类型要与应用类型匹配
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayoutxmlns:ohos="http://schemas.huawei.com/res/ohos"ohos:height="match_parent"ohos:width="match_parent"ohos:alignment="center"ohos:orientation="vertical"><Buttonohos:id="$+id:btn_start"ohos:height="match_content"ohos:width="match_content"ohos:background_element="#A306F52E"ohos:layout_alignment="horizontal_center"ohos:text="启动服务"ohos:text_size="40vp"/><Buttonohos:id="$+id:btn_stop"ohos:height="match_content"ohos:width="match_content"ohos:background_element="#A3235DEE"ohos:layout_alignment="horizontal_center"ohos:text="停止服务"ohos:text_size="40vp"/><Buttonohos:id="$+id:btn_connect"ohos:height="match_content"ohos:width="match_content"ohos:background_element="#A306F52E"ohos:layout_alignment="horizontal_center"ohos:text="连接服务"ohos:text_size="40vp"/><Buttonohos:id="$+id:btn_disconnect"ohos:height="match_content"ohos:width="match_content"ohos:background_element="#A3235DEE"ohos:layout_alignment="horizontal_center"ohos:text="断开连接服务"ohos:text_size="40vp"/><Buttonohos:id="$+id:btn_foreground"ohos:height="match_content"ohos:width="match_content"ohos:background_element="#A306F52E"ohos:layout_alignment="horizontal_center"ohos:text="启动前台service"ohos:text_size="40vp"/><Buttonohos:id="$+id:btn_foreground_stop"ohos:height="match_content"ohos:width="match_content"ohos:background_element="#A3235DEE"ohos:layout_alignment="horizontal_center"ohos:text="停止后台服务"ohos:text_size="40vp"/><TextFieldohos:id="$+id:text_filed_info"ohos:height="300vp"ohos:background_element="#FC0A84EF"ohos:text="信息显示区域"ohos:width="350vp"ohos:hint=""ohos:margin="2vp"ohos:text_size="20vp"/><Clockohos:id="$+id:clock"ohos:height="match_content"ohos:width="match_content"ohos:background_element="#FF80EF66"ohos:layout_alignment="left"ohos:text_size="20vp"/></DirectionalLayout>
创建服务类
package com.example.serviceability;import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.event.notification.NotificationRequest;
import ohos.rpc.IRemoteObject;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;public class ForegroundServiceAbility extends Ability {private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");@Overridepublic void onStart(Intent intent) {HiLog.error(LABEL_LOG, "ForegroundServiceAbility::onStart");NotificationRequest.NotificationNormalContent content3 = new NotificationRequest.NotificationNormalContent().setTitle("测试hap应用").setText("该service将会常留后台");NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content3);NotificationRequest request3 = new NotificationRequest(1001).setContent(notificationContent);keepBackgroundRunning(1001,request3);super.onStart(intent);}@Overridepublic void onBackground() {super.onBackground();HiLog.info(LABEL_LOG, "ForegroundServiceAbility::onBackground");}@Overridepublic void onStop() {super.onStop();HiLog.info(LABEL_LOG, "ForegroundServiceAbility::onStop");}@Overridepublic void onCommand(Intent intent, boolean restart, int startId) {}@Overridepublic IRemoteObject onConnect(Intent intent) {return null;}@Overridepublic void onDisconnect(Intent intent) {}
}
package com.example.serviceability;import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.LocalRemoteObject;
import ohos.aafwk.content.Intent;
import ohos.rpc.IRemoteObject;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;public class ServiceAbility extends Ability {private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");@Overridepublic void onStart(Intent intent) {HiLog.error(LABEL_LOG, "ServiceAbility::onStart");super.onStart(intent);}@Overridepublic void onBackground() {super.onBackground();HiLog.info(LABEL_LOG, "ServiceAbility::onBackground");}@Overridepublic void onStop() {super.onStop();HiLog.info(LABEL_LOG, "ServiceAbility::onStop");}@Overridepublic void onCommand(Intent intent, boolean restart, int startId) {}@Overridepublic IRemoteObject onConnect(Intent intent) {HiLog.info(LABEL_LOG,"OnConnect");return new MyRemoteObject();}@Overridepublic void onDisconnect(Intent intent) {HiLog.info(LABEL_LOG,"断开服务");}public class MyRemoteObject extends LocalRemoteObject{public MyRemoteObject(){HiLog.info(LABEL_LOG,"my remoteobject被创建");}//自定义方法,控制service的public String manipulateService(){HiLog.info(LABEL_LOG,"自定义方法,控制service的");return "exec_value666";}}}
package com.example.serviceability.slice;import com.example.serviceability.ForegroundServiceAbility;
import com.example.serviceability.ResourceTable;
import com.example.serviceability.ServiceAbility;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.ability.IAbilityConnection;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.components.Button;
import ohos.agp.components.Clock;
import ohos.agp.components.Component;
import ohos.agp.components.TextField;
import ohos.bundle.ElementName;
import ohos.rpc.IRemoteObject;public class MainAbilitySlice extends AbilitySlice {TextField textField;Clock clock2;@Overridepublic void onStart(Intent intent) {super.onStart(intent);super.setUIContent(ResourceTable.Layout_ability_main);textField = (TextField) findComponentById(ResourceTable.Id_text_filed_info);clock2 = (Clock) findComponentById(ResourceTable.Id_clock);clock2.setFormatIn24HourMode("yyyy-MM-dd HH:mm:ss");Button btn_start =(Button) findComponentById(ResourceTable.Id_btn_start);Button btn_stop =(Button) findComponentById(ResourceTable.Id_btn_stop);Button btn_con =(Button) findComponentById(ResourceTable.Id_btn_connect);Button btn_discon =(Button) findComponentById(ResourceTable.Id_btn_disconnect);Button btn_foreground = (Button) findComponentById(ResourceTable.Id_btn_foreground);Button btn_foreground_stop = (Button) findComponentById(ResourceTable.Id_btn_foreground_stop);btn_foreground_stop.setClickedListener(new Component.ClickedListener() {@Overridepublic void onClick(Component component) {StopForegoundService();textField.append(clock2.getText()+"已停止后台服务\n");}});btn_foreground.setClickedListener(new Component.ClickedListener() {@Overridepublic void onClick(Component component) {StartForegoundService();textField.append(clock2.getText()+"启动后台服务\n");}});btn_start.setClickedListener(new Component.ClickedListener() {@Overridepublic void onClick(Component component) {StartService();}});btn_stop.setClickedListener(new Component.ClickedListener() {@Overridepublic void onClick(Component component) {StopService();}});btn_con.setClickedListener(new Component.ClickedListener() {@Overridepublic void onClick(Component component) {ConnectService();}});btn_discon.setClickedListener(new Component.ClickedListener() {@Overridepublic void onClick(Component component) {disconnectAbility(connection3);textField.append(clock2.getText()+"已断开服务\n");}});}public void StartService(){Intent intent1 = new Intent();Operation operation = new Intent.OperationBuilder().withDeviceId("").withBundleName("com.example.serviceability").withAbilityName("com.example.serviceability.serviceability").build();intent1.setOperation(operation);startAbility(intent1);}public void StopService(){Intent intent1 = new Intent();Operation operation = new Intent.OperationBuilder().withDeviceId("").withBundleName("com.example.serviceability").withAbilityName("com.example.serviceability.serviceability").build();intent1.setOperation(operation);stopAbility(intent1);}//连接服务public void ConnectService(){Intent intent1 = new Intent();Operation operation = new Intent.OperationBuilder().withDeviceId("").withBundleName("com.example.serviceability").withAbilityName("com.example.serviceability.ServiceAbility").build();intent1.setOperation(operation);connectAbility(intent1,connection3);}@Overridepublic void onActive() {super.onActive();}@Overridepublic void onForeground(Intent intent) {super.onForeground(intent);}//连接远程的Service的IAbilityConnection对象private IAbilityConnection connection3 = new IAbilityConnection() {//通过远程对象操纵Service@Overridepublic void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int i) {ServiceAbility.MyRemoteObject object3 = (ServiceAbility.MyRemoteObject) iRemoteObject;String aa = object3.manipulateService();textField.append(clock2.getText()+" 服务信息:"+aa);}@Overridepublic void onAbilityDisconnectDone(ElementName elementName, int i) {}};public void StartForegoundService(){Intent intent1 = new Intent();Operation operation = new Intent.OperationBuilder().withDeviceId("").withBundleName("com.example.serviceability").withAbilityName(ForegroundServiceAbility.class.getName()).build();intent1.setOperation(operation);startAbility(intent1);}public void StopForegoundService(){Intent intent1 = new Intent();Operation operation = new Intent.OperationBuilder().withDeviceId("").withBundleName("com.example.serviceability").withAbilityName(ForegroundServiceAbility.class.getName()).build();intent1.setOperation(operation);stopAbility(intent1);}
}