在后台运行任务会消耗设备有限的资源,如RAM和电池。这可能会导致用户体验不佳。例如,后台任务可能会降低设备的电池寿命或用户在观看视频、玩游戏、使用相机等时可能会遇到设备性能不佳的情况。
为了提高电池性能,Android在早期版本中发布了一些资源,如Doze模式、应用程序待机、限制位置访问和其他一些内容。
最佳使用场景
WorkManager是用于后台执行的推荐解决方案,考虑到所有操作系统后台执行的限制。如果您需要保证一个任务即使被延迟也会运行,那么您应该使用WorkManager。该API允许您安排作业(一次性或重复),并链接和组合作业。您还可以将执行限制应用于它们,例如当设备处于空闲或充电状态时触发,或在内容提供程序更改时执行。
开始使用 Work Manager
步骤 1:在 Gradle 中定义依赖项
dependencies {
val work_version = "2.8.0"
// (Java only)
implementation("androidx.work:work-runtime:$work_version")
// Kotlin + coroutines
implementation("androidx.work:work-runtime-ktx:$work_version")
// optional - RxJava2 support
implementation("androidx.work:work-rxjava2:$work_version")
// optional - GCMNetworkManager support
implementation("androidx.work:work-gcm:$work_version")
// optional - Test helpers
androidTestImplementation("androidx.work:work-testing:$work_version")
// optional - Multiprocess support
implementation "androidx.work:work-multiprocess:$work_version"
}
步骤2:创建Worker类
使用Worker类定义工作。
class UploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
// Do the work here--in this case, upload the images.
uploadImages()
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
步骤3 创建 Work Request
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<UploadWorker>()
.build()
工作管理器提供服务以安排一次性和周期性请求,可以在一段时间内定期运行。我们将在本文后面详细了解它。
步骤4:提交工作请求
WorkManager
.getInstance(myContext)
.enqueue(uploadWorkRequest)
添加输入和输出
1. 在工作请求中添加输入
我们需要创建一个 bundle 并将其传递给 worker 请求。
以图像上传为例,我们希望将 Uri 作为输入参数传递。
- 1. 创建一个
Data.Builder
对象。在请求时导入androidx.work.Data
。 - 2. 如果
imageUri
是非空的 URI,则使用putString
方法将其添加到 Data 对象中。此方法接受一个键和一个值。您可以使用Constants
类中的字符串常量KEY_IMAGE_URI
。 - 3. 在
Data.Builder
对象上调用build()
方法以创建您的 Data 对象,并将其返回。
private fun createInputDataForUri(): Data {
val builder = Data.Builder()
imageUri?.let {
builder.putString(KEY_IMAGE_URI, imageUri.toString())
}
return builder.build()
}
2. 将数据对象传递给工作请求
val blurRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(createInputDataForUri())
.build()
更新UploadWorker
的doWork()
以获取输入
class UploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
val resourceUri = inputData.getString(KEY_IMAGE_URI)
// Do the work here--in this case, upload the images.
uploadImages(resourceUri)
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
工作请求类型
WorkRequest本身是一个抽象的基类。这个类有两个派生实现,你可以使用它们来创建请求,即OneTimeWorkRequest和PeriodicWorkRequest。正如它们的名字所示,OneTimeWorkRequest适用于安排不重复的工作,而PeriodicWorkRequest更适合安排在某个间隔上重复的工作。
1. 安排一次性工作
对于简单的任务
val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
对于复杂的任务
val uploadWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
// Additional configuration
.build()
2. 安排加速工作
面向 Android 12 或更高版本的应用程序在后台运行时不再能够启动前台服务。这使得 WorkManager 能够在给系统更好地控制资源访问权限的同时执行重要工作。
加速工作具有以下显著特点:
- • 重要性
- • 速度
- • 配额
- • 电源管理
- • 延迟
排序成为加急工作的潜在用例,例如当用户想要发送消息或附加图像时,在聊天应用程序中使用。同样,处理付款或订阅流程的应用程序也可能希望使用加急工作,因为这些任务对用户很重要,在后台快速执行,需要立即开始,并且即使用户关闭应用程序,也应继续执行。
配额
系统必须在运行之前为加急工作分配执行时间。 执行时间不是无限的。相反,每个应用程序都会收到执行时间配额。 当您的应用程序使用其执行时间并达到其分配配额时,您将无法再执行加急工作,直到配额刷新。 这使Android能够更有效地在应用程序之间平衡资源。
执行重要工作 调用setExpedited()
告诉框架,这项工作很重要,应优先于其他已安排的工作。请注意,我们还将OutOfQuotaPolicy
参数传递给setExpedited()
。基于App Standby Buckets的配额适用于加速作业,因此OutOfQuotaPolicy
参数告诉WorkManager,如果您的应用程序尝试在配额不足的情况下运行加速作业,则应执行以下操作:要么完全放弃加速的工作请求(DROP_WORK_REQUEST
),要么将作业作为常规工作请求处理(RUN_AS_NON_EXPEDITED_WORK_REQUEST
)。
3.执行周期性工作
有时候你需要定期执行一些任务,比如同步数据、备份数据、下载最新的数据。
val saveRequest =
PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
// Additional configuration
.build()
这意味着您的工作请求将每隔一小时执行一次。最小时间间隔为15分钟。
灵活的运行间隔
如果您的工作性质使其对运行时间敏感,您可以配置PeriodicWorkRequest
在每个间隔期内的某个灵活期内运行。
val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
1, TimeUnit.HOURS, // repeatInterval (the period cycle)
15, TimeUnit.MINUTES) // flexInterval
.build()
如果假设您的重复间隔为1小时,flexInterval为15分钟。那么您的任务将在(1小时-15分钟)至结束的15分钟之间开始。
重复间隔必须大于或等于PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS
,flex间隔必须大于或等于PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS
。
为了理解周期性工作请求,让我们举个例子:
例如:请在每周五的下午5:00生成一份报告,总结本周的销售数据。
- 1. 创建WeeklyReportWorker类。
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
class WeeklyReportWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
// Code to generate the weekly sales report goes here
// This could involve fetching data from a server or database,
// performing calculations, and storing the results in a file or database.
return Result.success()
}
}
- 1. 使用WorkManager API周期性地安排工作程序运行。
import androidx.appcompat.app.AppCompatActivity
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Schedule the worker to run every Friday at 5:00 pm
val weeklyReportRequest = PeriodicWorkRequestBuilder<WeeklyReportWorker>(
7, // Repeat interval
TimeUnit.DAYS // Interval unit
)
.setInitialDelay(calculateInitialDelay(), TimeUnit.MILLISECONDS) // Initial delay
.build()
WorkManager.getInstance(applicationContext).enqueue(weeklyReportRequest)
}
private fun calculateInitialDelay(): Long {
// Calculate the initial delay based on the current time and the desired
// time for the first run (Friday at 5:00 pm).
val currentTimeMillis = System.currentTimeMillis()
val desiredTimeMillis = getDesiredTimeMillis()
var initialDelay = desiredTimeMillis - currentTimeMillis
if (initialDelay < 0) {
// If the desired time has already passed for this week, schedule the
// first run for next week instead.
initialDelay += TimeUnit.DAYS.toMillis(7)
}
return initialDelay
}
private fun getDesiredTimeMillis(): Long {
val calendar = Calendar.getInstance().apply {
set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY)
set(Calendar.HOUR_OF_DAY, 17) // 5:00 pm
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
return calendar.timeInMillis
}
}
这段代码使用PeriodicWorkRequestBuilder来调度WeeklyReportWorker每7天运行一次(即每周运行一次)。第一次运行的初始延迟是基于当前时间和第一次运行的期望时间(周五下午5点)计算得出的。getDesiredTimeMillis()
方法将第一次运行的期望时间作为Long值返回,表示自 Unix 纪元以来的毫秒数。该值使用设置为期望的一周中某一天(星期五)和时间(下午5点)的 Calendar 对象计算得出。
工作约束
约束条件确保工作在满足特定条件之前不会启动。
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val myWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<MyWork>()
.setConstraints(constraints)
.build()
val constraints = Constraints.Builder()
.setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()
延迟工作
添加延迟意味着你希望工作在一段时间延迟后开始,而不是立即开始。
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
重试和退避策略 这允许用户在一段时间后重试他们的工作,每次重试时间会以线性或指数方式增加。
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build()
让我们用之前的示例来理解它
import androidx.appcompat.app.AppCompatActivity
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Schedule the worker to run every 7 days, with a flexible interval of up to 3 days
val weeklyReportRequest = PeriodicWorkRequestBuilder<WeeklyReportWorker>(
7, // Repeat interval
TimeUnit.DAYS // Interval unit
)
.setInitialDelay(1, TimeUnit.DAYS) // Initial delay
.setBackoffCriteria(
BackoffPolicy.LINEAR,
1, // Initial backoff delay
TimeUnit.HOURS // Backoff delay unit
)
.setFlex(3, TimeUnit.DAYS) // Flexible interval
.build()
WorkManager.getInstance(applicationContext).enqueue(weeklyReportRequest)
}
}
所以当我们尝试获取每周报告时失败了。我们可以在1个小时后再次重试,如果再次失败,则会呈线性增长,因此下次重试将会在2小时后发生。
任务链
当您想按特定顺序运行多个任务时,可以将任务链在一起。
WorkManager.getInstance(myContext)
// Candidates to run in parallel
.beginWith(listOf(plantName1, plantName2, plantName3))
// Dependent work (only runs after all previous work in chain)
.then(cache)
.then(upload)
// Call enqueue to kick things off
.enqueue()
独特的任务
有时候您只希望同时运行一个工作链。例如,您可能有一个工作链可以将本地数据与服务器同步,您可能希望在启动新的同步之前让第一个数据同步完成。为了做到这一点,您需要使用beginUniqueWork
而不是beginWith
,并提供一个唯一的字符串名称。这将为整个工作请求链命名,以便您可以一起引用和查询它们。
val sendLogsWorkRequest =
PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
.setConstraints(Constraints.Builder()
.setRequiresCharging(true)
.build()
)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"sendLogs",
ExistingPeriodicWorkPolicy.KEEP,
sendLogsWorkRequest
)
观察你的任务
// by id
workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
workManager.getWorkInfoByIdLiveData(syncWorker.id)
.observe(viewLifecycleOwner) { workInfo ->
if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
Snackbar.make(requireView(),
R.string.work_completed, Snackbar.LENGTH_SHORT)
.show()
}
}
取消并停止任务
// by id
workManager.cancelWorkById(syncWorker.id)
// by name
workManager.cancelUniqueWork("sync")
// by tag
workManager.cancelAllWorkByTag("syncTag")
参考链接
https://developer.android.com/codelabs/android-workmanager#0 https://medium.com/androiddevelopers/using-workmanager-on-android-12-f7d483ca0ecb https://developer.android.com/guide/background/persistent
声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/41617.html