Android WorkManager 入门与实践
WorkManager 是 Android Jetpack 组件,用于管理需可靠执行的后台任务,即使应用退出或设备重启也能保证运行。它支持一次性与周期性任务,可根据网络、电量等约束条件灵活调度。通过 Worker 定义任务逻辑,WorkRequest 配置执行方式,WorkManager 统一管理任务队列与状态。2.7.0 版本起支持加急任务,适合需要尽快执行的场景。WorkManager 并非替代线程,而是为可延迟且需保证完成的任务提供标准化解决方案,兼顾功能需求与系统资源优化。
博主博客
一. WorkManager 介绍
1.1 什么是 WorkManager?
WorkManager 是 Android Jetpack 的一部分,是一个用于管理后台任务的库。它旨在解决应用在退出或设备重启后仍需可靠运行任务的问题。
核心特性:
- 向后兼容:内部根据设备 API 级别自动选择底层调度服务(JobScheduler, AlarmManager + BroadcastReceiver 等)
- 生命周期感知:与 Activity/Fragment 生命周期解耦,但可观察任务状态
- 约束感知:可在满足条件(如网络连接、充电状态)时执行任务
- 灵活调度:支持一次性任务和周期性任务
- 加急任务:Android 5.0+ 支持需要立即执行的任务
1.2 WorkManager 与线程的区别
WorkManager 不是 用来替代线程或协程的。Google 将后台任务分为四类:
| 任务类型 | 特点 | 推荐方案 |
|---|---|---|
| Immediate Tasks | 用户操作时需要立即完成的任务 | Kotlin 协程、Thread |
| Exact Tasks | 需要在精确时间执行的任务 | AlarmManager |
| Expedited Tasks | 需要尽快开始执行的任务 | WorkManager (setExpedited) |
| Deferred Tasks | 不需要立即执行,可延迟的任务 | WorkManager |
关键理解:WorkManager 最适合处理可延迟且需要保证执行的任务,即使应用被关闭或设备重启。
1.3 加急任务(Expedited Work)
从 WorkManager 2.7.0(现已成为主流版本)开始,可以使用 setExpedited() 声明加急任务:
val request = OneTimeWorkRequestBuilder<MyWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
注意:
- Android 12+ 要求:必须在前台服务中运行,需重写
getForegroundInfo()方法 - 配额限制:系统对加急任务有限制,超出配额会降级为普通任务
- 立即执行:会尽快执行,但不保证立即执行
二. WorkManager 核心组件
2.1 Worker & CoroutineWorker
Worker:定义要执行的任务(同步执行)
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// 在主线程之外的线程执行
try {
// 执行任务
return Result.success()
} catch (e: Exception) {
return Result.failure()
}
}
}
CoroutineWorker:支持协程的 Worker(推荐使用)
class MyCoroutineWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
// 在协程上下文中执行
return try {
// 可调用 suspend 函数
performTask()
Result.success(createOutputData())
} catch (throwable: Throwable) {
if (throwable is IOException && shouldRetry()) {
Result.retry() // 重试
} else {
Result.failure(createOutputData())
}
}
}
private suspend fun performTask() {
delay(1000) // 可安全使用协程操作
}
}
doWork() 返回值:
Result.success()- 任务成功完成Result.failure()- 任务失败Result.retry()- 任务需要重试(WorkManager 会根据退避策略重新调度)
2.2 WorkRequest
定义任务如何执行,有两种类型:
1. OneTimeWorkRequest(一次性任务)
// 创建输入数据
val inputData = workDataOf(
"KEY_URL" to "https://example.com/file",
"KEY_FILENAME" to "movie.mp4"
)
// 创建约束条件
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
.build()
// 创建 WorkRequest
val downloadRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(inputData)
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.addTag("download")
.build()
2. PeriodicWorkRequest(周期性任务)
// 注意:最短间隔为 15 分钟
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
1, TimeUnit.HOURS, // 重复间隔
15, TimeUnit.MINUTES // 弹性时间
).build()
2.3 WorkManager
管理 WorkRequest 的生命周期:
class MyViewModel : ViewModel() {
private val workManager = WorkManager.getInstance(application)
fun startDownload() {
val downloadRequest = createDownloadRequest()
// 1. 简单入队
workManager.enqueue(downloadRequest)
// 2. 唯一工作链(避免重复任务)
workManager.beginUniqueWork(
"unique_download_work",
ExistingWorkPolicy.KEEP, // 或 REPLACE, APPEND
downloadRequest
).enqueue()
// 3. 链式任务
workManager
.beginWith(downloadRequest)
.then(processRequest)
.then(uploadRequest)
.enqueue()
}
fun cancelWork() {
// 通过ID取消
workManager.cancelWorkById(workId)
// 通过Tag取消
workManager.cancelAllWorkByTag("download")
// 取消唯一工作
workManager.cancelUniqueWork("unique_download_work")
// 取消所有工作
workManager.cancelAllWork()
}
}
2.4 观察任务状态
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 通过 LiveData 观察任务状态
workManager.getWorkInfosByTagLiveData("download")
.observe(this) { workInfos ->
workInfos.firstOrNull()?.let { info ->
when (info.state) {
WorkInfo.State.ENQUEUED -> {
// 任务已入队,等待条件满足
}
WorkInfo.State.RUNNING -> {
// 任务正在运行
val progress = info.progress.getInt("PROGRESS", 0)
updateProgress(progress)
}
WorkInfo.State.SUCCEEDED -> {
// 任务成功完成
val result = info.outputData.getString("RESULT_KEY")
showResult(result)
}
WorkInfo.State.FAILED -> {
// 任务失败
showError()
}
WorkInfo.State.CANCELLED -> {
// 任务被取消
}
WorkInfo.State.BLOCKED -> {
// 任务被阻塞(例如等待前置任务完成)
}
}
}
}
// 也可以通过 WorkQuery 进行复杂查询
val workQuery = WorkQuery.Builder
.fromTags(listOf("download"))
.addStates(listOf(WorkInfo.State.RUNNING, WorkInfo.State.SUCCEEDED))
.addUniqueWorkNames(listOf("unique_download_work"))
.build()
workManager.getWorkInfosLiveData(workQuery)
.observe(this) { workInfos ->
// 处理查询结果
}
}
}
三. 实践:文件下载示例
3.1 创建 Worker(支持前台服务)
class DownloadWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
companion object {
const val TAG = "DownloadWorker"
const val INPUT_URL = "input_url"
const val INPUT_FILENAME = "input_filename"
const val OUTPUT_RESULT = "output_result"
const val OUTPUT_FILE_PATH = "output_file_path"
const val NOTIFICATION_ID = 1001
// 创建通知渠道(Android 8.0+ 需要)
fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"download_channel",
"文件下载",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "显示下载进度"
}
val manager = context.getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
}
private lateinit var notificationBuilder: NotificationCompat.Builder
// 重写此方法以支持加急任务(Android 12+ 必须)
override suspend fun getForegroundInfo(): ForegroundInfo {
val notification = createNotification("准备下载...", 0)
return ForegroundInfo(NOTIFICATION_ID, notification)
}
override suspend fun doWork(): Result {
createNotificationChannel(applicationContext)
val url = inputData.getString(INPUT_URL)
val filename = inputData.getString(INPUT_FILENAME)
if (url.isNullOrEmpty() || filename.isNullOrEmpty()) {
return Result.failure(workDataOf(OUTPUT_RESULT to "输入参数错误"))
}
return try {
// 模拟下载进度
for (progress in 0..100 step 10) {
if (isStopped) {
return Result.failure()
}
// 更新进度(可通过观察 WorkInfo.progress 获取)
setProgress(workDataOf("progress" to progress))
// 更新通知
updateNotification("下载中...", progress)
delay(200) // 模拟下载耗时
}
// 下载完成
val resultData = workDataOf(
OUTPUT_RESULT to "下载完成",
OUTPUT_FILE_PATH to "/storage/emulated/0/Download/$filename"
)
updateNotification("下载完成", 100, true)
Result.success(resultData)
} catch (e: Exception) {
Log.e(TAG, "下载失败", e)
updateNotification("下载失败", 0, true)
Result.failure(workDataOf(OUTPUT_RESULT to "下载失败: ${e.message}"))
}
}
private fun createNotification(contentText: String, progress: Int): Notification {
// 创建取消下载的 PendingIntent
val cancelIntent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(id)
notificationBuilder = NotificationCompat.Builder(
applicationContext,
"download_channel"
).apply {
setContentTitle("文件下载")
setContentText(contentText)
setSmallIcon(R.drawable.ic_download)
priority = NotificationCompat.PRIORITY_LOW
setOngoing(true)
setOnlyAlertOnce(true)
// 添加取消按钮
addAction(
R.drawable.ic_cancel,
"取消",
cancelIntent
)
// 设置进度条
if (progress < 100) {
setProgress(100, progress, false)
} else {
setProgress(0, 0, false)
}
}
return notificationBuilder.build()
}
private fun updateNotification(
contentText: String,
progress: Int,
isComplete: Boolean = false
) {
if (::notificationBuilder.isInitialized) {
notificationBuilder.apply {
setContentText(contentText)
if (isComplete) {
setOngoing(false)
setAutoCancel(true)
setProgress(0, 0, false)
// 添加打开文件的 Action
val intent = Intent(Intent.ACTION_VIEW).apply {
// 这里应该设置实际的文件 URI
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
val pendingIntent = PendingIntent.getActivity(
applicationContext,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
addAction(
R.drawable.ic_open,
"打开文件",
pendingIntent
)
} else {
setProgress(100, progress, false)
}
}
// 更新通知
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
}
private val notificationManager: NotificationManager
get() = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
3.2 在 ViewModel 中管理工作
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
private val workManager = WorkManager.getInstance(application)
private val _workInfo = MutableLiveData<WorkInfo?>()
val workInfo: LiveData<WorkInfo?> = _workInfo
private val _progress = MutableLiveData<Int>()
val progress: LiveData<Int> = _progress
fun startDownload(url: String, filename: String) {
val inputData = workDataOf(
DownloadWorker.INPUT_URL to url,
DownloadWorker.INPUT_FILENAME to filename
)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val downloadRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(inputData)
.setConstraints(constraints)
.addTag(DownloadWorker.TAG)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
// 观察任务状态
workManager.getWorkInfoByIdLiveData(downloadRequest.id)
.observeForever { info ->
_workInfo.value = info
// 获取进度
info?.progress?.getInt("progress", 0)?.let { progressValue ->
_progress.value = progressValue
}
}
// 入队任务
workManager.enqueue(downloadRequest)
}
fun cancelDownload() {
workManager.cancelAllWorkByTag(DownloadWorker.TAG)
}
fun getWorkStatus(): LiveData<List<WorkInfo>> {
return workManager.getWorkInfosByTagLiveData(DownloadWorker.TAG)
}
}
3.3 在 Activity/Fragment 中使用
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: DownloadViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupUI()
setupObservers()
}
private fun setupUI() {
binding.btnDownload.setOnClickListener {
val url = binding.etUrl.text.toString()
val filename = binding.etFilename.text.toString()
if (url.isNotEmpty() && filename.isNotEmpty()) {
viewModel.startDownload(url, filename)
binding.btnDownload.text = "取消下载"
} else {
Toast.makeText(this, "请输入URL和文件名", Toast.LENGTH_SHORT).show()
}
}
}
private fun setupObservers() {
viewModel.workInfo.observe(this) { info ->
info?.let {
when (it.state) {
WorkInfo.State.ENQUEUED -> {
binding.tvStatus.text = "等待下载..."
}
WorkInfo.State.RUNNING -> {
binding.tvStatus.text = "下载中..."
binding.btnDownload.text = "取消下载"
binding.btnDownload.setOnClickListener {
viewModel.cancelDownload()
}
}
WorkInfo.State.SUCCEEDED -> {
val result = it.outputData.getString(DownloadWorker.OUTPUT_RESULT)
binding.tvStatus.text = result ?: "下载成功"
binding.btnDownload.text = "开始下载"
resetDownloadButton()
}
WorkInfo.State.FAILED -> {
val error = it.outputData.getString(DownloadWorker.OUTPUT_RESULT)
binding.tvStatus.text = error ?: "下载失败"
binding.btnDownload.text = "重试下载"
resetDownloadButton()
}
WorkInfo.State.CANCELLED -> {
binding.tvStatus.text = "下载已取消"
binding.btnDownload.text = "开始下载"
resetDownloadButton()
}
else -> {}
}
}
}
viewModel.progress.observe(this) { progress ->
binding.progressBar.progress = progress
binding.tvProgress.text = "$progress%"
}
}
private fun resetDownloadButton() {
binding.btnDownload.setOnClickListener {
val url = binding.etUrl.text.toString()
val filename = binding.etFilename.text.toString()
if (url.isNotEmpty() && filename.isNotEmpty()) {
viewModel.startDownload(url, filename)
}
}
}
}
四. 最佳实践与常见问题
4.1 最佳实践
- 使用 CoroutineWorker:除非必须用 Java 线程,否则优先使用 CoroutineWorker
- 合理设置约束:避免不必要的唤醒,节省电量
- 处理任务取消:定期检查
isStopped状态,及时释放资源 - 使用唯一工作链:避免重复执行相同任务
- 合理设置退避策略:对于可能失败的任务,设置合适的重试策略
- 最小化数据传递:输入/输出数据应尽量小,避免传递大对象
4.2 常见问题
Q1: WorkManager 任务没有立即执行?
A: 这是正常行为。WorkManager 不是为即时任务设计的,即使加急任务也可能有延迟。如需立即执行,请使用前台服务。
Q2: 如何传递复杂对象给 Worker?
A: 推荐使用数据库或文件存储,只传递 ID 或路径给 Worker。
Q3: 周期性任务的最小间隔?
A: 15 分钟(PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS)
Q4: Worker 中的异常处理?
A: Worker 抛出的未捕获异常会导致任务失败,返回 Result.failure()
Q5: 如何测试 WorkManager?
A: 使用 WorkManagerTestInitHelper 进行测试:
@RunWith(AndroidJUnit4::class)
class DownloadWorkerTest {
@get:Rule
val rule = InstantTaskExecutorRule()
private lateinit var context: Context
private lateinit var workManager: WorkManager
@Before
fun setup() {
context = ApplicationProvider.getApplicationContext()
val config = Configuration.Builder()
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
workManager = WorkManager.getInstance(context)
}
@Test
fun testDownloadWorker() = runBlocking {
val inputData = workDataOf("url" to "test_url")
val request = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(inputData)
.build()
workManager.enqueue(request).result.get()
val workInfo = workManager.getWorkInfoById(request.id).get()
assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
}
}
五. 总结
WorkManager 是 Android 后台任务管理的推荐解决方案,特别适合需要保证执行的可延迟任务。关键点:
- 选择合适的任务类型:根据需求选择 Immediate/Exact/Expedited/Deferred 任务
- 合理使用约束:平衡功能需求和电量消耗
- 处理好生命周期:WorkManager 自动处理应用生命周期变化
- 支持加急任务:Android 5.0+ 可使用加急任务获得更高优先级
- 易于测试:提供专门的测试工具
通过合理使用 WorkManager,可以创建既功能强大又对系统友好的后台任务,提升应用体验和用户满意度。
Android WorkManager 入门与实践
https://blog.uso6.com/archives/android-workmanager-ru-men-yu-shi-jian
评论