告别繁琐,使用WorkManager解决后台工作的难题
前言
执行后台任务是Android开发中比较麻烦的部分,需要考虑许多事情。首先是省电续航问题,后台任务频繁执行,电量像流水一样消耗,这是用户和系统都无法忍受的。谷歌为了治理Android应用野蛮地在后台偷偷工作的恶行,推出各种后台限制机制(Doze Mode、App Standby、Background Execution Limits),新版本Android的后台管理越来越严格,给开发者带来一定的兼容性难题。最重要的是开发者难以保证应用的后台任务的可靠性,系统常常因为各种原因暂停或者杀死后台任务。
WorkManager是Android Jetpack 家族中的一位"后台任务管家",是适用于持久性工作和一般后台处理操作的官方推荐 API。本文以开发一个下载器为例子介绍WorkManager的使用方法。下载器的代码可以在下面仓库找到:github.com/Ilovecat194...
欢迎大家关注我的音视频开发项目:github.com/Ilovecat194... 。这是一个开源的Android音视频编辑器,支持ffmpeg命令行、视频编码压缩和格式转换、视频剪裁变速等多种音视频功能。
WorkManager为什么是最佳选择
1.官方背书,可靠性强:作为谷歌官方推荐的后台任务解决方案,WorkManager 会持续得到支持和优化,具有简单且一致的 API 。
2.持久可靠地执行:这是WorkManager最大的亮点。即使用户导航离开屏幕、退出应用或重启设备也不影响工作的执行。对于失败的任务,WorkManager提供了灵活的重试政策,包括可配置的指数退避政策。
3.高效节能:WorkManager遵循低电耗模式等省电功能和最佳做法,具有强大的调度能力。
4.约束条件灵活:开发者可以为任务设置各种约束条件,让它在最合适的时机执行,从而节省电量和流量。
例如:
网络状态:只在Wi-Fi连接时执行。
充电状态:只在设备充电时执行。
存储空间:只在存储空间足够时执行。
5.任务类型多样:支持一次性任务、周期性任务和链式任务,满足开发者的多种需求。
核心理念与实践:WorkManager是如何工作的?
WorkManager 的设计理念非常清晰,它将后台任务分解为三个核心组件:
1.工作单元:Worker
理念:WorkManager将后台任务抽象成一个个独立的Worker。每个Worker只负责执行一个具体的任务,例如:上传日志、同步数据、下载文件等。
实现:继承Worker类并重写doWork()方法。在这个方法中放入你的后台任务逻辑。
2.任务请求:WorkRequest
理念:WorkRequest是用来描述任务如何执行的,它包含了任务的约束条件、延迟执行时间等信息。
实践:使用OneTimeWorkRequest(一次性任务)或PeriodicWorkRequest(周期性任务)来创建任务请求,并通过Constraints构建器设置约束。
3.任务调度:WorkManager
理念:WorkManager是后台任务的调度中心,它负责接收WorkRequest,并根据约束条件和系统状态来决定何时执行Worker。
实践:通过WorkManager.getInstance(context).enqueue(workRequest)来将任务提交给WorkManager。
用代码说话:使用WorkManager开发一个下载器
设计
我们在开发之前,需要先想好我们开发的应用是怎么样的。
1.使用kotlin语言开发、使用jetpack compose开发UI。
2.能够申请实现下载所需的权限,如网络权限,文件读写权限。
3.界面上支持用户输入下载链接并开启下载。
4.有下载任务列表,展示下载进度和状态。
6.支持下载进度通知。
这里需要强调一下通知的重要性。对于长时间运行且需要持续访问网络或 CPU 的任务,它需要一个"通行证"来告知系统该任务的重要性。为了解决这个问题,我们需要将下载任务升级为 "前台服务(Foreground Service)"。前台服务会向系统声明任务正在进行,并且对用户可见(通过通知),因此系统不会轻易终止它。
代码实现
我们首先需要在AndroidManifest.xml里声明所需要的权限。对于Android13以及后面的Android版本,我们需要显式地声明通知权限。
xml
<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" />
我们还需要运行时检查和请求权限,还有创建通知通道。
kotlin
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()
...
}
基于自己的需求定义一个工作类,执行任务的具体逻辑都写在这个工作类里面。
kotlin
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)
}
}
我们通过上下文获得一个WorkManager的实例,这个实例可以直接帮我们完成任务数据库和调度系统的全部工作。WorkManager 使用了一个 Room 数据库(一个 SQLite 数据库的抽象层),这个数据库位于应用私有的存储空间中,从而实现任务的持久化和状态管理。我们可以直接从WorkManager的实例中获取所有任务的信息。配合LiveData,应用界面能实时观察并显示任务的状态和进度,实现后台任务与UI的同步。
scss
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)
}
}
定义一个下载启动方法,封装提交任务给WorkManager的流程。
kotlin
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)
}
以上就是使用使用WorkManager开发一个下载器的思路。

展望未来:Android后台任务的新趋势与谷歌政策
谷歌一直在收紧后台任务的权限,以保护用户隐私和设备电量。未来的Android版本可能会有更严格的后台执行限制,这意味着传统的Service和BroadcastReceiver等方式会越来越难用。 在这样的背景下,WorkManager 作为官方推荐的方案,其重要性将日益凸显。它能帮助我们遵循谷歌的政策,同时保证任务的可靠执行。
总结
回顾 WorkManager 的优势:可靠、兼容、持久、灵活。它解放了开发者,让我们可以专注于业务逻辑,而不用再为复杂的后台任务适配和兼容性问题而焦头烂额。
如果你还在为后台任务而烦恼,是时候拥抱 WorkManager 了。它不仅能让你的应用更稳定,也能让你的开发工作更高效。
下载器的代码可以在下面仓库找到:github.com/Ilovecat194...
欢迎大家关注我的音视频开发项目:ilovecat1949.github.io/AudioAndVid... 。这是一个开源的Android音视频编辑器,支持ffmpeg命令行、视频编码压缩和格式转换、视频剪裁变速等多种音视频功能。