一、概述
1.1 功能介紹
應用退至後台後,在後台需要長時間運行用户可感知的任務,如播放音樂、導航等。為防止應用進程被掛起,導致對應功能異常,可以申請長時任務,使應用在後台長時間運行。在長時任務中,支持同時申請多種類型的任務,也可以對任務類型進行更新。應用退至後台執行業務時,系統會做一致性校驗,確保應用在執行相應的長時任務。應用在申請長時任務成功後,通知欄會顯示與長時任務相關聯的消息,用户刪除通知欄消息時,系統會自動停止長時任務。
1.2 使用場景
下表給出了當前長時任務支持的類型,包含數據傳輸、音視頻播放、錄製、定位導航、藍牙相關業務、多設備互聯、音視頻通話和計算任務。可以參考下表中的場景舉例,選擇合適的長時任務類型。
長時任務類型
|
參數名
|
描述
|
配置項
|
場景舉例
|
|
DATA_TRANSFER
|
數據傳輸
|
dataTransfer
|
非託管形式的上傳、下載,如在瀏覽器後台上傳或下載數據。
|
|
AUDIO_PLAYBACK
|
音視頻播放
|
audioPlayback
|
音頻、視頻在後台播放,音視頻投播。
説明: 支持在元服務中使用。
|
|
AUDIO_RECORDING
|
錄製
|
audioRecording
|
錄音、錄屏退後台。
|
|
LOCATION
|
定位導航
|
location
|
定位、導航。
|
|
BLUETOOTH_INTERACTION
|
藍牙相關業務
|
bluetoothInteraction
|
通過藍牙傳輸文件時退後台。
|
|
MULTI_DEVICE_CONNECTION
|
多設備互聯
|
multiDeviceConnection
|
分佈式業務連接、投播。
説明: 支持在元服務中使用。
|
|
VOIP
|
音視頻通話
|
voip
|
某些聊天類應用(具有音視頻業務)音頻、視頻通話時退後台。
|
|
TASK_KEEPING
|
計算任務(僅對2in1設備開放)
|
taskKeeping
|
如殺毒軟件。
|
關於DATA_TRANSFER(數據傳輸)説明:
- 在數據傳輸時,若應用使用上傳下載代理接口託管給系統,即使申請DATA_TRANSFER的後台任務,應用退後台時還是會被掛起。
- 在數據傳輸時,應用需要更新進度,如果進度長時間(超過10分鐘)未更新,數據傳輸的長時任務會被取消。更新進度的通知類型必須為實況窗,具體實現可參考startBackgroundRunning()中的示例。
關於AUDIO_PLAYBACK(音視頻播放)説明:
- 音視頻投播,是指將一台設備的音視頻投至另一台設備播放。投播退至後台,長時任務會檢測音視頻播放和投屏兩個業務,只要有其一正常運行,長時任務就不會終止。
- 當應用需要在後台播放媒體類型(流類型為STREAM_USAGE_MUSIC、STREAM_USAGE_MOVIE和STREAM_USAGE_AUDIOBOOK)和遊戲類型(流類型為STREAM_USAGE_GAME)時,必須接入媒體會話服務(AVSession)並申請AUDIO_PLAYBACK類型長時任務。
- 除了上述播放類型,針對用户可感知的其他播放任務,如果應用需要在後台長時間運行該任務,必須申請AUDIO_PLAYBACK類型長時任務,無需接入AVSession。
- 如果應用不滿足上述接入規範,退至後台播放時會被系統靜音並凍結,無法在後台正常播放,直到應用重新切回前台時,才會解除靜音並恢復播放。
- 從API version 20開始,申請AUDIO_PLAYBACK類型長時任務但不接入AVSession,申請長時任務成功後會在通知欄顯示通知;接入AVSession後,後台任務模塊不會發送通知欄通知,由AVSession發送通知。對於API version 19及之前的版本,後台任務模塊不會在通知欄顯示通知。
1.3 約束與限制
申請限制:Stage模型中,長時任務僅支持UIAbility申請;FA模型中,長時任務僅支持ServiceAbility申請。長時任務支持設備當前應用申請,也支持跨設備或跨應用申請,跨設備或跨應用僅對系統應用開放。 數量限制:一個UIAbility(FA模型則為ServiceAbility)同一時刻僅支持申請一個長時任務,即在一個長時任務結束後才可能繼續申請。如果一個應用同時需要申請多個長時任務,需要創建多個UIAbility;一個應用的一個UIAbility申請長時任務後,整個應用下的所有進程均不會被掛起。
- 運行限制:
- 申請長時任務後,應用未執行相應的業務,系統會對應用進行管控,即應用退至後台會被掛起。如系統檢測到應用申請了AUDIO_PLAYBACK(音視頻播放),但實際未播放音樂。
- 申請長時任務後,應用執行的業務類型與申請的不一致,系統會對應用進行管控,即應用退至後台會被掛起。如系統檢測到應用只申請了AUDIO_PLAYBACK(音視頻播放),但實際上除了播放音樂(對應AUDIO_PLAYBACK類型),還在進行錄製(對應AUDIO_RECORDING類型)。
- 申請長時任務後,應用的業務已執行完,系統會對應用進行管控,即應用退至後台會被掛起。
- 若運行長時任務的進程後台負載持續高於所申請類型的典型負載,系統會對應用進行管控,即應用退至後台會被掛起或終止。
説明 應用按需求申請長時任務,當應用無需在後台運行(任務結束)時,要及時主動取消長時任務,否則應用退至後台會被系統掛起。例如用户主動點擊音樂暫停播放時,應用需及時取消對應的長時任務;用户再次點擊音樂播放時,需重新申請長時任務。
若音頻在後台播放時被打斷,系統會自行檢測和停止長時任務,音頻重啓播放時,需要再次申請長時任務。
後台播放音頻的應用,在停止長時任務的同時,需要暫停或停止音頻流,否則應用會被系統強制終止。
二、接口説明
主要接口
以下是長時任務開發使用的相關接口,下表均以Promise形式為例,更多接口及使用方式請見後台任務管理。
|
接口名
|
描述
|
|
startBackgroundRunning(context: Context, bgMode: BackgroundMode, wantAgent: WantAgent): Promise\
|
申請長時任務
|
|
stopBackgroundRunning(context: Context): Promise\
|
取消長時任務
|
三、開發步驟
本文以申請錄製長時任務為例,實現如下功能:
- 點擊“申請長時任務”按鈕,應用申請錄製長時任務成功,通知欄顯示“正在運行錄製任務”通知。
- 點擊“取消長時任務”按鈕,取消長時任務,通知欄撤銷相關通知。
3.1 Stage模型
- 需要申請ohos.permission.KEEP_BACKGROUND_RUNNING權限,配置方式請參見聲明權限。
- 聲明後台模式類型。 在module.json5文件中為需要使用長時任務的UIAbility聲明相應的長時任務類型,配置文件中填寫長時任務類型的配置項。
"module": {
"abilities": [
{
"backgroundModes": [
// 長時任務類型的配置項
"audioRecording",
"bluetoothInteraction",
"audioPlayback"
]
}
],
// ...
}
- 導入模塊。 長時任務相關的模塊為@ohos.resourceschedule.backgroundTaskManager和@ohos.app.ability.wantAgent,其餘模塊按實際需要導入。
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { wantAgent, WantAgent } from '@kit.AbilityKit';
// 在元服務中,請刪除WantAgent導入
- 申請和取消長時任務。
效果圖
設備當前應用申請和取消長時任務示例代碼如下:
function callback(info: backgroundTaskManager.ContinuousTaskCancelInfo) {
// 長時任務id
console.info('OnContinuousTaskCancel callback id ' + info.id);
// 長時任務取消原因
console.info('OnContinuousTaskCancel callback reason ' + info.reason);
}
@Entry
@Component
struct Index {
@State message: string = 'ContinuousTask';
// 通過getUIContext().getHostContext()方法,來獲取page所在的UIAbility上下文
private context: Context | undefined = this.getUIContext().getHostContext();
OnContinuousTaskCancel() {
try {
backgroundTaskManager.on("continuousTaskCancel", callback);
console.info(`Succeeded in operationing OnContinuousTaskCancel.`);
} catch (error) {
console.error(`Operation OnContinuousTaskCancel failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
}
OffContinuousTaskCancel() {
try {
// callback參數不傳,則取消所有已註冊的回調
backgroundTaskManager.off("continuousTaskCancel", callback);
console.info(`Succeeded in operationing OffContinuousTaskCancel.`);
} catch (error) {
console.error(`Operation OffContinuousTaskCancel failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
}
// 申請長時任務.then()寫法
startContinuousTask() {
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],
// 車鑰匙長時任務子類型,從API version 16開始支持。只有申請bluetoothInteraction類型的長時任務,車鑰匙子類型才能生效。
// 確保extraInfo參數中的Key值為backgroundTaskManager.BackgroundModeType.SUB_MODE,否則子類型不生效。
// extraInfo: { [backgroundTaskManager.BackgroundModeType.SUB_MODE] : backgroundTaskManager.BackgroundSubMode.CAR_KEY }
};
try {
// 通過wantAgent模塊下getWantAgent方法獲取WantAgent對象
// 在元服務中,使用wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: object) => {替換下面一行代碼
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
try {
let list: Array<string> = ["audioRecording"];
// let list: Array<string> = ["bluetoothInteraction"]; 長時任務類型包含bluetoothInteraction,CAR_KEY子類型合法
// 在元服務中,let list: Array<string> = ["audioPlayback"];
backgroundTaskManager.startBackgroundRunning(this.context, list, wantAgentObj).then((res: backgroundTaskManager.ContinuousTaskNotification) => {
console.info("Operation startBackgroundRunning succeeded");
// 此處執行具體的長時任務邏輯,如錄音,錄製等。
}).catch((error: BusinessError) => {
console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
});
} catch (error) {
console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
});
} catch (error) {
console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
}
// 取消長時任務.then()寫法
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}`);
});
}
build() {
Row() {
Column() {
Text("Index")
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button() {
Text('申請長時任務').fontSize(25).fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250)
.height(40)
.onClick(() => {
// 通過按鈕申請長時任務
this.startContinuousTask();
})
Button() {
Text('取消長時任務').fontSize(25).fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250)
.height(40)
.onClick(() => {
// 此處結束具體的長時任務的執行
// 通過按鈕取消長時任務
this.stopContinuousTask();
})
Button() {
Text('註冊長時任務取消回調').fontSize(25).fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250)
.height(40)
.onClick(() => {
// 通過按鈕註冊長時任務取消回調
this.OnContinuousTaskCancel();
})
Button() {
Text('取消註冊長時任務取消回調').fontSize(25).fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250)
.height(40)
.onClick(() => {
// 通過按鈕取消註冊長時任務取消回調
this.OffContinuousTaskCancel();
})
}
.width('100%')
}
.height('100%')
}
}
- 申請和取消長時任務async/await寫法。
效果圖
設備當前應用申請和取消長時任務async/await寫法示例代碼如下:
@Entry
@Component
struct Index {
@State message: string = 'ContinuousTask';
// 通過getUIContext().getHostContext()方法,來獲取page所在的UIAbility上下文
private context: Context | undefined = this.getUIContext().getHostContext();
// 申請長時任務async/await寫法
async startContinuousTask() {
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],
// 車鑰匙長時任務子類型,從API version 16開始支持。只有申請bluetoothInteraction類型的長時任務,車鑰匙子類型才能生效。
// 確保extraInfo參數中的Key值為backgroundTaskManager.BackgroundModeType.SUB_MODE,否則子類型不生效。
// extraInfo: { [backgroundTaskManager.BackgroundModeType.SUB_MODE] : backgroundTaskManager.BackgroundSubMode.CAR_KEY }
};
try {
// 通過wantAgent模塊下getWantAgent方法獲取WantAgent對象
// 在元服務中,使用const wantAgentObj: object = await wantAgent.getWantAgent(wantAgentInfo);替換下面一行代碼
const wantAgentObj: WantAgent = await wantAgent.getWantAgent(wantAgentInfo);
try {
let list: Array<string> = ["audioRecording"];
// let list: Array<string> = ["bluetoothInteraction"]; 長時任務類型包含bluetoothInteraction,CAR_KEY子類型合法
// 在元服務中,let list: Array<string> = ["audioPlayback"];
const res: backgroundTaskManager.ContinuousTaskNotification = await backgroundTaskManager.startBackgroundRunning(this.context as Context, list, wantAgentObj);
console.info(`Operation startBackgroundRunning succeeded, notificationId: ${res.notificationId}`);
// 此處執行具體的長時任務邏輯,如錄音,錄製等。
} catch (error) {
console.error(`Failed to Operation startBackgroundRunning. Code is ${(error as BusinessError).code}, message is ${(error as BusinessError).message}`);
}
} catch (error) {
console.error(`Failed to Operation getWantAgent. Code is ${(error as BusinessError).code}, message is ${(error as BusinessError).message}`);
}
}
// 取消長時任務async/await寫法
async stopContinuousTask() {
try {
await backgroundTaskManager.stopBackgroundRunning(this.context);
console.info(`Succeeded in operationing stopBackgroundRunning.`);
} catch (error) {
console.error(`Failed to operation stopBackgroundRunning. Code is ${(error as BusinessError).code}, message is ${(error as BusinessError).message}`)
}
}
build() {
Row() {
Column() {
Text("Index")
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button() {
Text('申請長時任務').fontSize(25).fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250)
.height(40)
.onClick(() => {
// 通過按鈕申請長時任務
this.startContinuousTask();
})
Button() {
Text('取消長時任務').fontSize(25).fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.margin({ top: 10 })
.backgroundColor('#0D9FFB')
.width(250)
.height(40)
.onClick(() => {
// 此處結束具體的長時任務的執行
// 通過按鈕取消長時任務
this.stopContinuousTask();
})
}
.width('100%')
}
.height('100%')
}
}