Android中的WorkManager

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. 创建一个 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()

更新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. 创建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

猜你喜欢

转载自blog.csdn.net/u011897062/article/details/131004306