Read the original article:Managing Background Tasks with Deferred Tasks
Requirement Description
Modern applications frequently need to carry out non-urgent tasks in the background without continuously using system resources or affecting the user experience. For example, an application might need to synchronize data, upload logs, or retrieve occasional updates when the device is idle or certain conditions are met. To achieve this, a mechanism is required to schedule and execute tasks efficiently in the background, ensuring a balance between application functionality, system health, and battery life.
Background Knowledge
In HarmonyOS Next, deferred tasks provide a robust solution for running non-urgent background operations. Unlike transient tasks, which are designed for short, time-sensitive operations right as an app goes into the background, deferred tasks are meant for non-critical work that can be postponed and executed when system conditions are optimal.
The system intelligently schedules deferred tasks based on various conditions, such as:
Network availability: For tasks requiring internet access (e.g., data synchronization).
Charging status: To perform heavier tasks when the device is charging.
Battery level: To avoid draining the battery when it's low.
Storage status: To ensure sufficient space for operations.
Device idle state: To run tasks when the device is inactive.
Application activity groups: The system categorizes applications based on user usage patterns and adjusts the frequency of deferred task execution accordingly.
This intelligent scheduling ensures that background tasks are executed efficiently, minimizing their impact on system performance, battery consumption, and overall user experience.
Implementation Steps
Implementing deferred tasks in HarmonyOS Next involves defining the background worker, specifying task conditions, and managing the task lifecycle.
1.Create a WorkSchedulerExtensionAbility: This core component runs your deferred task logic in the background. It extends WorkSchedulerExtensionAbility and contains onWorkStart() and onWorkStop() callbacks.
- onWorkStart(workInfo: workScheduler.WorkInfo): This callback is invoked when the system decides to run your deferred task based on the defined conditions. You should implement your background logic here.
- onWorkStop(workInfo: workScheduler.WorkInfo): This callback is invoked when the task finishes, is canceled, or times out (after a maximum of 2 minutes). Use this for cleanup, logging, or saving the task's state.
2.Configure module.json5: Register your WorkSchedulerExtensionAbility in the extensionAbilities section of your module.json5 file, specifying its name, source entry, and type as "workScheduler."
3.Define WorkInfo: Create a WorkInfo object to specify the conditions under which your deferred task should run. This includes the task's ID, bundle name, ability name, and desired conditions like networkType, isCharging, batteryLevel, isRepeat, and repeatCycleTime.
4.Schedule the Task: Use workScheduler.startWork(workInfo) to request the system to schedule your deferred task. It's good practice to check if a task with the same ID already exists before creating a new one, especially for recurring tasks.
5.Cancel/Stop the Task: If needed, you can stop a deferred task using workScheduler.stopWork(workInfo).
Code Snippet
- WorkSchedulerExtensionAbility Implementation (e.g., MyWorker.ets)
import { WorkSchedulerExtensionAbility, workScheduler } from '@kit.BackgroundTasksKit';
export default class MyWorker extends WorkSchedulerExtensionAbility {
onWorkStart(workInfo: workScheduler.WorkInfo) {
console.info(`MyWorker: onWorkStart called for workId: ${workInfo.workId}`);
// Simulate a background task, e.g., fetching data or syncing.
// This is where your actual background logic goes.
setTimeout(() => {
console.info(`MyWorker: Task for workId ${workInfo.workId} completed.`);
}, 1000); // Simulate a 1-second task
}
onWorkStop(workInfo: workScheduler.WorkInfo) {
console.info(`MyWorker: onWorkStop called for workId: ${workInfo.workId}`);
// Perform cleanup, save state, or log completion.
// For example, update shared preferences with the last update time.
}
}
2.module.json5 Configuration
{
"module": {
"extensionAbilities": [
{
"name": "MyWorker",
"srcEntry": "./ets/workers/MyWorker.ets", // Adjust path as per your project
"type": "workScheduler"
}
]
}
}
3.Task Management (e.g., in your EntryAbility.ets or a TaskManager class)
import { workScheduler } from '@kit.BackgroundTasksKit';
import { BusinessError } from '@kit.BasicServicesKit';
// Function to schedule a deferred task
async function scheduleDeferredTask() {
const WORK_ID = 1001; // Unique ID for your task
const BUNDLE_NAME = 'com.example.yourapp'; // Replace with your app's bundle name
const ABILITY_NAME = 'MyWorker'; // The name of your WorkSchedulerExtensionAbility
const workInfo: workScheduler.WorkInfo = {
workId: WORK_ID,
bundleName: BUNDLE_NAME,
abilityName: ABILITY_NAME,
networkType: workScheduler.NetworkType.NETWORK_TYPE_WIFI, // Only run when Wi-Fi is available
isCharging: true, // Only run when the device is charging
// isRepeat: true, // Uncomment for repeating tasks
// repeatCycleTime: 3 * 60 * 60 * 1000, // Repeat every 3 hours (min 2 hours for repeating tasks)
isPersisted: true // Task can be restored after system restart
};
try {
// Check if the task already exists before scheduling to avoid duplicates
const existingWorks = await workScheduler.obtainAllWorks();
const taskExists = existingWorks.some(work => work.workId === WORK_ID);
if (!taskExists) {
workScheduler.startWork(workInfo);
console.info(`Deferred task with ID ${WORK_ID} scheduled successfully.`);
} else {
console.info(`Deferred task with ID ${WORK_ID} already exists. Skipping scheduling.`);
// You might want to update existing task conditions here if needed
}
} catch (error) {
console.error(`Failed to schedule deferred task. Code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
}
}
// Function to cancel a deferred task
function cancelDeferredTask() {
const WORK_ID = 1001;
const BUNDLE_NAME = 'com.example.yourapp';
const ABILITY_NAME = 'MyWorker';
const workInfo: workScheduler.WorkInfo = {
workId: WORK_ID,
bundleName: BUNDLE_NAME,
abilityName: ABILITY_NAME
// Other properties are not strictly required for stopWork if workId is unique
};
try {
workScheduler.stopWork(workInfo);
console.info(`Deferred task with ID ${WORK_ID} canceled successfully.`);
} catch (error) {
console.error(`Failed to cancel deferred task. Code: ${(error as BusinessError).code}, message: ${(error as BusinessError).message}`);
}
}
// Example usage: Schedule when the app goes into the background
// In your EntryAbility.onBackground() or a similar lifecycle callback:
// scheduleDeferredTask();
Test Results
To verify deferred tasks, monitor the system logs for your application. You should observe:
Scheduling confirmation: When workScheduler.startWork() is called, logs indicate that the task has been accepted by the system.
onWorkStart invocation: Logs from your WorkSchedulerExtensionAbility when the system triggers the task. This will happen when the specified WorkInfo conditions are met and the system deems it an opportune time.
onWorkStop invocation: Logs indicating the task has finished or been terminated.
Frequency observation: The system will intelligently decide the frequency of task execution based on factors like app usage, battery, and network conditions. You may notice delays in execution if conditions are not met or if the device is under heavy load.
To invoke a deferred task manually, you should command:
hidumper -s 1904 -a '-t com.example.application MyWorkSchedulerExtensionAbility'
Limitations or Considerations
Quantity Limit: An application can request a maximum of 10 deferred tasks concurrently.
Execution Frequency Limit: The system controls how often deferred tasks run based on the application's activity group (e.g., active, frequently used, rarely used). The minimum interval can range from 2 hours to 48 hours, or even be forbidden for restricted applications.
Timeout: A WorkSchedulerExtensionAbility has a maximum execution duration of 2 minutes for a single onWorkStart callback. If the task doesn't complete and explicitly calls stopWork() within this time, the system will forcibly terminate its process, followed by an onWorkStop() callback.
Scheduling Delay: Deferred tasks are not real-time. The system may delay scheduling based on current memory, power consumption, device temperature, and user habits to optimize overall system performance.
API Restrictions: Certain APIs, particularly those related to real-time operations like camera, audio, and some background task management functions, cannot be called within WorkSchedulerExtensionAbility to prevent resource misuse in the background.
Persistence: Ensure isPersisted is set to true in WorkInfo if you want the task to be restored after a device reboot.
Redundant Scheduling: Always query for existing tasks before scheduling a new one to avoid creating duplicate tasks, especially for non-repeating tasks or when the app frequently enters the background.
Related Documents or Links
You can look at the official documentation of deferred tasks from here.
You can view WorkSchedularExtensionAbility from this page.
You can get general information and the work logic of abilities from here.
Top comments (0)