DEV Community

Cover image for Say goodbye to tedious work and use WorkManager to solve the problem of background work

Say goodbye to tedious work and use WorkManager to solve the problem of background work

Say goodbye to tedious work and use WorkManager to solve the problem of background work

Preface

Executing background tasks is a complex part of Android development, requiring numerous considerations. First and foremost is battery life. Frequent background tasks consume a significant amount of battery life, a problem unbearable for both users and the system. To combat the rampant and surreptitious background work of Android apps, Google has introduced various background restriction mechanisms (Doze Mode, App Standby, and Background Execution Limits). Newer versions of Android have tightened background management, creating compatibility challenges for developers. Most importantly, developers struggle to ensure the reliability of their apps' background tasks, as the system often pauses or kills them for various reasons.

WorkManager, a background task manager within the Android Jetpack family, is the officially recommended API for persistent work and general background processing. This article will demonstrate the use of WorkManager using the example of developing a downloader. The downloader code can be found in the following repository: https://github.com/Ilovecat1949/AndroidApps.

You are also welcome to check out my audio and video development project: https://github.com/Ilovecat1949/AudioAndVideoEditor. This is an open source Android audio and video editor that supports ffmpeg command line, video encoding compression and format conversion, video cropping and speed change, and other audio and video functions.

Why WorkManager is the best choice

  1. Officially Endorsed and Reliable: As Google's officially recommended background task solution, WorkManager continues to receive support and optimization, with a simple and consistent API.

  2. Persistent and Reliable Execution: This is WorkManager's greatest strength. Work execution is maintained even if the user navigates away from the screen, exits the app, or reboots the device. For failed tasks, WorkManager provides flexible retry policies, including a configurable exponential backoff policy.

  3. Energy Efficiency: WorkManager adheres to power-saving features and best practices like Doze and offers powerful scheduling capabilities.

  4. Flexible Constraints: Developers can set various constraints for tasks, allowing them to execute at the most appropriate time, saving battery and data usage.

For example:

Network Status: Execute only when connected to Wi-Fi.

Charging Status: Execute only when the device is charging.

Storage Space: Execute only when sufficient storage space is available.

  1. Diverse Task Types: Supports one-time, periodic, and chained tasks to meet the diverse needs of developers.

    Core Concepts and Practices: How does WorkManager work?

    WorkManager's design philosophy is very clear, breaking down background tasks into three core components:

  2. Worker: Unit of Work

Concept: WorkManager abstracts background tasks into independent Workers. Each Worker is responsible for performing a specific task, such as uploading logs, synchronizing data, or downloading files.

Implementation: Inherit the Worker class and override the doWork() method. Implement your background task logic in this method.

  1. Task Request: WorkRequest

Concept: A WorkRequest describes how a task should be executed, including constraints, execution delay, and other information.

Practice: Create a task request using OneTimeWorkRequest (for one-time tasks) or PeriodicWorkRequest (for periodic tasks) and set constraints using the Constraints builder.

  1. Task Scheduling: WorkManager

Concept: WorkManager is the scheduling center for background tasks. It receives WorkRequests and decides when to execute a Worker based on constraints and system status.

Practice: Submit tasks to the WorkManager using WorkManager.getInstance(context).enqueue(workRequest) .

Talking with code: Developing a downloader using WorkManager

design

Before developing, we need to first consider the nature of our application.

  1. Develop it in Kotlin and develop the UI using Jetpack Compose.
  2. Be able to request the necessary permissions for downloads, such as network permissions and file read/write permissions.
  3. The interface should allow users to enter a download link and initiate the download.
  4. Provide a download task list to display download progress and status.
  5. Support download progress notifications. The importance of notifications is crucial. Long-running tasks that require continuous network or CPU access require a "pass" to inform the system of their importance. To address this, we need to upgrade the download task to a "foreground service." A foreground service notifies the system that the task is in progress and is visible to the user (via notifications), preventing the system from easily terminating it. ### Code Implementation We first need to declare the required permissions in AndroidManifest.xml. For Android 13 and later Android versions, we need to explicitly declare the notification permission.
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <!-- 声明网络权限,这是下载功能所必需的 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Enter fullscreen mode Exit fullscreen mode

We also need to check and request permissions at runtime, and create a notification channel.

    private val requestPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
            if (isGranted) {
                // Permission granted. Continue with the app flow.
            } else {
                Toast.makeText(this, "需要通知权限才能显示下载进度", Toast.LENGTH_LONG).show()
            }
        }
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "下载通知"
            val descriptionText = "显示下载文件的进度和状态"
            val importance = NotificationManagerCompat.IMPORTANCE_LOW
            val channel = android.app.NotificationChannel(DOWNLOAD_NOTIFICATION_CHANNEL_ID, name, importance).apply {
                description = descriptionText
            }
            val notificationManager: android.app.NotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    ...
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R){
            val permission = arrayOf(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE
            )
            val requestPermissionLauncher =
                this.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->

                }
            permission.forEach {
                if(! (this.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED)){
                    requestPermissionLauncher.launch(it)
                }
            }
        }
        else{
            if(!Environment.isExternalStorageManager()){
                val builder = android.app.AlertDialog.Builder(this)
                    .setMessage("需要获取文件读写权限")
                    .setPositiveButton("ok") { _, _ ->
                        val packageName = this.packageName
                        val intent = Intent()
                        intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
                        intent.data = Uri.fromParts("package", packageName, null)
                        ContextCompat.startActivity( this, intent, null)
                    }
                    .setNeutralButton("稍后再问"){ _, _ ->

                    }
                builder.show()
            }
        }


        // 请求通知权限 (适用于 Android 13 及以上版本)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
            }
        }

        // 创建通知渠道
        createNotificationChannel()    
    ...    
    }        
Enter fullscreen mode Exit fullscreen mode

Define a work class based on your own needs, and write the specific logic of executing the task in this work class.

class DownloadWorker(
    appContext: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {

    private val notificationManager = NotificationManagerCompat.from(appContext)
    private val notificationBuilder = NotificationCompat.Builder(applicationContext, DOWNLOAD_NOTIFICATION_CHANNEL_ID)
        .setContentTitle("下载中")
        .setSmallIcon(android.R.drawable.stat_sys_download)
        .setPriority(NotificationCompat.PRIORITY_LOW)
        .setOngoing(true)

    // 最大文件名长度
    private val MAX_FILENAME_LENGTH = 50

    // 重写 getForegroundInfo() 以提供前台服务通知信息
    override suspend fun getForegroundInfo(): ForegroundInfo {
        return ForegroundInfo(
            DOWNLOAD_NOTIFICATION_ID,
            notificationBuilder.setContentText("开始下载...").build()
        )
    }

    override suspend fun doWork(): Result {
     //文件下载的具体逻辑写在这里面 
     ...
    }

    // Helper function to set progress for the UI
    private fun setProgress(progress: Int) {
        val progressData = workDataOf("progress" to progress)
        setProgressAsync(progressData)
    }
}


Enter fullscreen mode Exit fullscreen mode

We obtain a WorkManager instance through the context, which directly handles all tasks related to the task database and scheduling system. WorkManager uses a Room database (an abstraction layer for SQLite databases) in the application's private storage space to achieve task persistence and state management. We can directly access all task information from the WorkManager instance. Combined with LiveData, the application interface can observe and display task status and progress in real time, achieving synchronization between background tasks and the UI.

    val workManager = WorkManager.getInstance(context)
    ...
    val workInfos: LiveData<List<WorkInfo>> = remember {
        workManager.getWorkInfosByTagLiveData(DOWNLOAD_WORK_TAG)
    }
    val downloadTasks by workInfos.observeAsState(initial = emptyList())
    ...
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        items(downloadTasks) { workInfo ->
            DownloadTaskItem(workInfo)
        }
    }    
Enter fullscreen mode Exit fullscreen mode

Define a download start method to encapsulate the process of submitting tasks to WorkManager.

fun startDownload(context: Context, url: String) {
    // 1. 创建 WorkRequest,指定要执行的 Worker
    val downloadRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
        .setInputData(workDataOf(DOWNLOAD_WORK_URL to url)) // 2. 传递输入数据
        .addTag(DOWNLOAD_WORK_TAG) // 3. 添加标签以便跟踪
        .build()

    // 4. 将任务加入队列
    WorkManager.getInstance(context).enqueue(downloadRequest)
}
Enter fullscreen mode Exit fullscreen mode

The above is the idea of ​​using WorkManager to develop a downloader.

WorkDownloader

Looking ahead: New trends in Android background tasks and Google policies

Google has been tightening the permissions for background tasks to protect user privacy and device battery life. Future Android versions may introduce even stricter background execution restrictions, making traditional approaches like Services and BroadcastReceivers increasingly difficult to use.
Against this backdrop, WorkManager, as the officially recommended solution, will become increasingly important. It helps us comply with Google's policies while ensuring reliable task execution.

Summarize

Let's review the advantages of WorkManager: reliability, compatibility, durability, and flexibility. It frees developers to focus on business logic without having to worry about complex background task adaptation and compatibility issues.

If you're still struggling with background tasks, it's time to embrace WorkManager. It will not only make your app more stable but also your development more efficient.

The downloader code can be found in the following repository: https://github.com/Ilovecat1949/AndroidApps.

Please check out my audio and video development project: https://ilovecat1949.github.io/AudioAndVideoEditor/. This is an open-source Android audio and video editor that supports various audio and video functions, including ffmpeg command-line functionality, video encoding and compression, format conversion, video cropping, and speed changes.

References

  1. Android Background Worker Official Guide
  2. WorkManager Official Course

Top comments (0)