目录

应用后台运行

概述

为有效降低设备耗电量并确保用户体验流畅,系统会对后台应用进行管理,包括进程挂起和终止。当应用处于音乐播放或地图导航等场景时,如果用户将应用退至后台、锁屏或切换应用,系统会自动将应用转入后台运行。为确保应用在后台期间仍能正常使用,系统提供了符合规范的后台任务机制。本文将介绍应用切后台的常见问题及解决方案,并通过分析应用短暂切换前后台和后台长时间运行的开发实践案例,指导开发者如何有效使用后台任务处理冻结场景,从而确保应用在前后台之间能够流畅切换。

应用后台规格建议

当前系统将对切换至后台的应用程序所使用的资源进行严格管控,具体措施包括挂起和终止进程。如果不进行后台处理,可能会导致应用程序功能异常。请参考官网指南和最佳实践文档,了解具体的限制条件和建议。

实现原理

[h2]后台任务类型

标准系统支持规范内受约束的后台任务,包括短时任务、长时任务、延迟任务、代理提醒。开发者可以根据如下介绍,选择合适的后台任务类型,以满足应用退至后台后继续运行的需求。

以下表格对比总结了各类后台任务的概念、适用场景以及任务执行过程中的应用状态。

适用场景

概念

常见问题

应用退至后台状态

任务类型

不执行任何任务的后台场景:直接退至后台。

不执行任何任务,直接退至后台。

-

几秒后应用挂起。

无后台

任务类型:小文件下载、缓存、信息发送等时效性高、需要临时占用资源的任务。

任务类型:实时性要求高、耗时不长的任务。

应用短暂切换前后台,进程被挂起。

在单次配额内,应用不会被挂起直到取消任务。单次配额超时不取消,应用进程会被终止。

短时任务

任务类型:数据传输、音频播放、录音、定位导航、蓝牙、WLAN相关、多设备互联、音视频通话、计算任务。

任务类型:长时间运行在后台且用户可感知的任务。

应用后台长时间运行后会中断。

应用不会被挂起,直到任务取消。如果任务结束时不取消应用进程,进程会被终止。

长时任务

任务类型:软件更新、信息收集、数据处理等。

任务类型:实时性要求不高、可延迟执行的任务。满足条件后,任务将放入执行队列,系统会根据内存和功耗进行统一调度。

-

应用退到后台时会挂起,满足任务设定条件时由系统统一调度拉起应用,创建Extension进程执行任务。单次回调最长运行时间为2分钟,如果超时未取消,系统会终止对应的Extension进程。

延迟任务

任务类型:闹钟、倒计时、日历。

任务类型:系统代理应用做出相应提醒。

-

应用挂起或进程终止时,满足条件后系统会自动进行相应的提醒。

代理提醒

场景示例

[h2]应用短暂切换前后台,避免进程挂起

在应用进行小文件下载、缓存、信息发送等业务场景时,如果应用短暂退至后台导致进程被挂起,重新切换到前台,可能因应用的前后台周期回调中存在业务代码逻辑,导致应用使用状态异常。此时,可以申请短时任务作为解决方案。以下示例展示了如何使用ApplicationContext订阅应用前后台切换的回调,以在应用切后台时申请短时任务,解决因短暂切换前后台导致的消息发送异常问题。

  1. 定义短时任务信息SuspendTaskInfo()接口,包括短时任务的ID和获取对应短时任务的剩余时间delayTime。
    export interface SuspendTaskInfo {
      id: number; // Short-time task ID
      delayTime: number; // The remaining time of this request short assignment
    }
  2. 在信息发送的场景中,当应用处于前台时,在onAppear()回调函数中启动一个定时器,每隔2秒发送一条消息,模拟后台业务。
    NavDestination() {
      // ...
    }
    .title(this.builderTitle())
    .onAppear(() => {
      this.shortTermTaskModel.subscribeStateChange();
      this.taskTimer = setInterval(() => {
        this.messageCount++;
        hilog.info(0x0000, TAG, `already sent :${this.messageCount} messages`);
      }, 2000);
    })
  3. 在信息发送的场景中,通过ApplicationContext.on('applicationStateChange')注册对当前应用前后台变化的监听。当应用退至后台时,触发onApplicationBackground()回调函数,在此回调函数中申请短时任务。
    // Apply front - and back-end status monitoring
    subscribeStateChange() {
      let that = this;
      // Gets applicationContext
      let applicationContext = this.context.getApplicationContext();
      let applicationStateChangeCallback: ApplicationStateChangeCallback = {
        onApplicationForeground() {
          hilog.info(0x0000, TAG, 'applicationStateChangeCallback onApplicationForeground');
        },
        onApplicationBackground() {
          hilog.info(0x0000, TAG, 'applicationStateChangeCallback onApplicationBackground');
          // Apply for short-time tasks when the application changes from foreground to background
          that.suspendTaskInfo = SuspendTaskUtils.requestSuspendDelay('Suspend Task');
          hilog.info(0x0000, TAG,
            `requestSuspendDelay, id:${that.suspendTaskInfo.id}, delayTime:${that.suspendTaskInfo.delayTime}`);
        }
      }
      try {
        // Registers the background and pre - application status monitoring through applicationContext
        applicationContext.on('applicationStateChange', applicationStateChangeCallback);
      } catch (paramError) {
        hilog.error(0x0000, TAG,
          `error: ${(paramError as BusinessError).code}, ${(paramError as BusinessError).message}`);
      }
    }
  4. 在小文件下载、缓存、信息发送等场景中,应用退至后台时,可使用backgroundTaskManager.requestSuspendDelay()接口,后台应用申请短时任务。短时任务的申请和使用过程中的约束与限制请参考短时任务(ArkTS)的约束与限制。
    /**
     *
     * @param reason Set the delay task suspension reason
     * @returns
     */
    requestSuspendDelay(reason: string): SuspendTaskInfo {
      let id: number; // Apply for a short-time task ID
      let delayTime: number; // The remaining time of this request short assignment
      try {
        // Request deferred task
        let delayInfo = backgroundTaskManager.requestSuspendDelay(reason, () => {
          // This function is used to call back the application when a short task requested by the application is about to time out.
          hilog.info(0x0000, TAG, `Request suspension delay will time out.`);
          backgroundTaskManager.cancelSuspendDelay(delayInfo.requestId);
        })
        id = delayInfo.requestId;
        delayTime = delayInfo.actualDelayTime;
        let taskInfo = {
          id: id,
          delayTime: delayTime
        } as SuspendTaskInfo;
        return taskInfo;
      } catch (err) {
        let taskInfo = {
          id: 0,
          delayTime: 0
        } as SuspendTaskInfo;
        return taskInfo;
      }
    }
  5. 应用返回前台后,调用backgroundTaskManager.getRemainingDelayTime()接口,获取对应短时任务的剩余时间。
    async getRemainingDelayTime(id: number): Promise<number> {
      let delayTime: number = -1;
      await backgroundTaskManager.getRemainingDelayTime(id).then((res: number) => {
        delayTime = res;
        hilog.info(0x0000, TAG, 'Operation getRemainingDelayTime succeeded. Data: ' + JSON.stringify(res));
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, 'Operation getRemainingDelayTime failed. Cause: ' + err.code);
      });
      return delayTime;
    }
  6. 应用退至后台,可调用backgroundTaskManager.cancelSuspendDelay()接口取消短时任务。
    cancelSuspendDelay(id: number): boolean {
      try {
        backgroundTaskManager.cancelSuspendDelay(id);
        hilog.info(0x0000, TAG, 'cancelSuspendDelay succeeded.');
      } catch (err) {
        hilog.error(0x0000, TAG, `cancelSuspendDelay failed. Cause: ${JSON.stringify(err)}`);
        return false;
      }
      return true;
    }

实现效果

[h2]应用后台长时间运行不中断

当应用涉及数据传输、音频播放、录音操作、定位导航、蓝牙和WLAN相关应用、多设备互联、音视频通话、复杂计算任务等场景时,需要应用在后台长时间运行。为了确保应用在这些情况下正常运作,可以申请后台长时任务来实现。以下示例展示了如何使用长时任务管理应用的定位服务,以实现应用在后台长时间运行时,持续获取设备位置信息的功能。

  1. 在定位、导航类的应用场景下,为了确保应用在后台仍能使用定位服务,需在module.json5配置文件中为EntryAbility声明定位类型的长时任务,并申请定位相关权限。
    {
      "module": {
        // ...
        "abilities": [
          {
            "name": "EntryAbility",
            "srcEntry": "./ets/entryability/EntryAbility.ets",
            // ...
            "backgroundModes": [
              'location'
            ]
          }
        ],
        // ...
        "requestPermissions": [
          {
            "name": "ohos.permission.LOCATION",
            // ...
          },
          {
            "name": "ohos.permission.LOCATION_IN_BACKGROUND",
            // ...
          },
          {
            "name": "ohos.permission.APPROXIMATELY_LOCATION",
            // ...
          },
          {
            "name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
            // ...
          }
        ]
      }
    }
  2. 应用跳转到定位功能页面,系统请求定位权限和网络访问权限。
    // Apply for location-related permissions
    requestPermissionsFromUser(): void {
      let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
      let permissionList: Permissions[] = [
        'ohos.permission.INTERNET',
        'ohos.permission.LOCATION',
        'ohos.permission.APPROXIMATELY_LOCATION'
      ];
      atManager.requestPermissionsFromUser(this.context, permissionList)
        .then((data: PermissionRequestResult) => {
          hilog.info(0x0000, TAG, `data: ${JSON.stringify(data)}`);
        })
        .catch((err: BusinessError) => {
          hilog.error(0x0000, TAG, `requestPermissionsFromUser fail: ${JSON.stringify(err)}`);
        });
    }
  3. 应用需获取位置信息,使用geoLocationManager.on('locationChange')接口,开启位置变化订阅并发起定位请求。
    locationCallback = async (location: geoLocationManager.Location) => {
      hilog.info(0x0000, TAG, `locationCallback: data: ${JSON.stringify(location)}`);
    };
    
    // Get the location
    async getLocation() {
      let request: geoLocationManager.LocationRequest = {
        priority: geoLocationManager.LocationRequestPriority.FIRST_FIX, // Quick location acquisition is preferred
        scenario: geoLocationManager.LocationRequestScenario.UNSET, // Indicates that no scenario information is set
        timeInterval: 1, // Interval for reporting the location information
        distanceInterval: 0, // Distance for reporting location information
        maxAccuracy: 100 // The precision value required when the application requests location information from the system
      };
      try {
        geoLocationManager.on('locationChange', request, this.locationCallback);
      } catch (err) {
        hilog.error(0x0000, TAG, `errCode: ${JSON.stringify(err)}`);
      }
    }
  4. 应用退至后台需持续运行时,应调用backgroundTaskManager.startBackgroundRunning()接口申请长时任务。
    // Start a long task
    startLongTask(): void {
      let wantAgentInfo: wantAgent.WantAgentInfo = {
        wants: [
          {
            bundleName: this.context.abilityInfo.bundleName,
            abilityName: this.context.abilityInfo.name
          }
        ],
        actionType: wantAgent.OperationType.START_ABILITY,
        requestCode: 0,
        wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      };
    
      try {
        // wantAgent object is obtained by getWantAgent method in WantAgent module
        wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
          backgroundTaskManager.startBackgroundRunning(this.context, backgroundTaskManager.BackgroundMode.LOCATION,
            wantAgentObj)
            .then(() => {
              hilog.info(0x0000, TAG, `Operation startBackgroundRunning succeeded`);
            })
            .catch((error: BusinessError) => {
              hilog.error(0x0000, TAG,
                `Operation startBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
            });
        });
      } catch (error) {
        hilog.error(0x0000, TAG, `Operation getWantAgent failed. error is ${JSON.stringify(error)} `);
      }
    }
  5. 在定位和导航类的应用场景中,当应用退出时,需调用geoLocationManager.off('locationChange')接口,关闭位置变化订阅。
    geoLocationManager.off('locationChange');
  6. 应用退出时,调用backgroundTaskManager.stopBackgroundRunning()接口,取消长时任务。
    // Stop a long task
    stopLongTask(): void {
      backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
        hilog.info(0x0000, TAG, `Operation stopBackgroundRunning succeeded`);
      }).catch((error: BusinessError) => {
        hilog.error(0x0000, TAG, `Operation stopBackgroundRunning failed. error is ${JSON.stringify(error)} `);
      });
    }

实现效果

示例代码