Android中的WorkManager

图片
workmanager

在后台运行任务会消耗设备有限的资源,如RAM和电池。这可能会导致用户体验不佳。例如,后台任务可能会降低设备的电池寿命或用户在观看视频、玩游戏、使用相机等时可能会遇到设备性能不佳的情况。

为了提高电池性能,Android在早期版本中发布了一些资源,如Doze模式、应用程序待机、限制位置访问和其他一些内容。

最佳使用场景

图片
WorkManager situation

WorkManager是用于后台执行的推荐解决方案,考虑到所有操作系统后台执行的限制。如果您需要保证一个任务即使被延迟也会运行,那么您应该使用WorkManager。该API允许您安排作业(一次性或重复),并链接和组合作业。您还可以将执行限制应用于它们,例如当设备处于空闲或充电状态时触发,或在内容提供程序更改时执行。

图片
WorkManager使用场景汇总

开始使用 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. 1. 创建一个 Data.Builder 对象。在请求时导入 androidx.work.Data
  2. 2. 如果imageUri是非空的 URI,则使用 putString 方法将其添加到 Data 对象中。此方法接受一个键和一个值。您可以使用 Constants类中的字符串常量 KEY_IMAGE_URI
  3. 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()

更新UploadWorkerdoWork()以获取输入

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. 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. 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

联系我们
联系我们
分享本页
返回顶部