从 Firebase JobDispatcher 迁移到 WorkManager

WorkManager 是一个库,用于在 Android 系统中调度和执行可延期的后台工作。我们建议用其取代 Firebase JobDispatcher。以下指南会引导您完成将 Firebase JobDispatcher 实现迁移到 WorkManager 的过程。

Gradle 设置

如需将 WorkManager 库导入到 Android 项目中,请添加 WorkManager 使用入门中列出的依赖项。

从 JobService 到工作器

FirebaseJobDispatcher 使用 JobService 用作入口点,用于定义需要完成的工作。您可以直接使用 JobService,或使用 SimpleJobService。

JobService 类似于以下示例:

Kotlin

import com.firebase.jobdispatcher.JobParameters
import com.firebase.jobdispatcher.JobService

class MyJobService : JobService() {
    override fun onStartJob(job: JobParameters): Boolean {
        // Do some work here
        return false // Answers the question: "Is there still work going on?"
    }
    override fun onStopJob(job: JobParameters): Boolean {
        return false // Answers the question: "Should this job be retried?"
    }
}

Java

import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;

public class MyJobService extends JobService {
    @Override
    public boolean onStartJob(JobParameters job) {
        // Do some work here

        return false; // Answers the question: "Is there still work going on?"
    }

    @Override
    public boolean onStopJob(JobParameters job) {
        return false; // Answers the question: "Should this job be retried?"
    }
}

如果您使用的是 SimpleJobService,则将会替换 onRunJob(),这会返回 @JobResult int 类型。

关键区别在于:直接使用 JobService 时,将在主线程上调用 onStartJob(),并且由应用负责将工作分摊到后台线程。另一方面,如果您使用的是 SimpleJobService,则由该服务负责在后台线程上执行您的工作。

WorkManager 具有类似的概念。WorkManager 中的基本工作单元是 ListenableWorker。另外还有其他有用的工作器子类型,如 Worker、RxWorker 和 CoroutineWorker(使用 Kotlin 协程时)。

JobService 映射到 ListenableWorker

如果您直接使用 JobService,则它映射到的工作器为 ListenableWorker。如果您使用的是 SimpleJobService,则应使用 Worker。

我们继续使用上面的示例 (MyJobService),看看如何将其转换为 ListenableWorker。

Kotlin

import android.content.Context
import androidx.work.ListenableWorker
import androidx.work.ListenableWorker.Result
import androidx.work.WorkerParameters
import com.google.common.util.concurrent.ListenableFuture

class MyWorker(appContext: Context, params: WorkerParameters) :
    ListenableWorker(appContext, params) {

    override fun startWork(): ListenableFuture<ListenableWorker.Result> {
        // Do your work here.
        TODO("Return a ListenableFuture<Result>")
    }

    override fun onStopped() {
        // Cleanup because you are being stopped.
    }
}

Java

import android.content.Context;
import androidx.work.ListenableWorker;
import androidx.work.ListenableWorker.Result;
import androidx.work.WorkerParameters;
import com.google.common.util.concurrent.ListenableFuture;

class MyWorker extends ListenableWorker {

  public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) {
    super(appContext, params);
  }

  @Override
  public ListenableFuture<ListenableWorker.Result> startWork() {
    // Do your work here.
    Data input = getInputData();

    // Return a ListenableFuture<>
  }

  @Override
  public void onStopped() {
    // Cleanup because you are being stopped.
  }
}

WorkManager 中的基本工作单元是 ListenableWorker。与 JobService.onStartJob() 类似,startWork() 在主线程中调用。这里 MyWorker 实现 ListenableWorker 并返回 ListenableFuture 实例,该实例用于异步指示工作完成。您应该在此处选择自己的线程策略。

此处的 ListenableFuture 最终会返回 ListenableWorker.Result 类型,该类型可以是 Result.success()、Result.success(Data outputData)、Result.retry()、Result.failure() 或 Result.failure(Data outputData)。如需了解详情,请参阅 ListenableWorker.Result 参考页面。

此处调用了 onStopped() 来告知系统 ListenableWorker 需要停止,要么是因为限制条件不再能够满足(例如,因为网络不再可用),要么是因为调用了 WorkManager.cancel…() 方法。此外,如果操作系统出于某种原因决定关闭您的工作,也可能会调用 onStopped()。

SimpleJobService 映射到工作器

使用 SimpleJobService 时,上述工作器将如下所示:

Kotlin

import android.content.Context;
import androidx.work.Data;
import androidx.work.ListenableWorker.Result;
import androidx.work.Worker;
import androidx.work.WorkerParameters;


class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        TODO("Return a Result")
    }

    override fun onStopped() {
        super.onStopped()
        TODO("Cleanup, because you are being stopped")
    }
}

Java

import android.content.Context;
import androidx.work.Data;
import androidx.work.ListenableWorker.Result;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

class MyWorker extends Worker {

  public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters params) {
    super(appContext, params);
  }

  @Override
  public Result doWork() {
    // Do your work here.
    Data input = getInputData();

    // Return a ListenableWorker.Result
    Data outputData = new Data.Builder()
        .putString(“Key”, “value”)
        .build();
    return Result.success(outputData);
  }

  @Override
  public void onStopped() {
    // Cleanup because you are being stopped.
  }
}

此处 doWork() 会返回 ListenableWorker.Result 的实例,以同步表示工作完成。这类似于将作业安排在后台线程上完成的 SimpleJobService。

JobBuilder 映射到 WorkRequest

FirebaseJobBuilder 使用 Job.Builder 来表示 Job 元数据。WorkManager 使用 WorkRequest 来填充此角色。

WorkManager 有两种类型的 WorkRequest:OneTimeWorkRequest 和 PeriodicWorkRequest。

如果您当前使用的是 Job.Builder.setRecurring(true),那么应创建一个新的 PeriodicWorkRequest。否则,应使用 OneTimeWorkRequest。

我们来看看使用 FirebaseJobDispatcher 调度复杂 Job 的代码示例:

Kotlin

val input: Bundle = Bundle().apply {
    putString("some_key", "some_value")
}

val job = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyService::class.java)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // one-off job
    .setRecurring(false)
    // don't persist past a device reboot
    .setLifetime(Lifetime.UNTIL_NEXT_BOOT)
    // start between 0 and 60 seconds from now
    .setTrigger(Trigger.executionWindow(0, 60))
    // don't overwrite an existing job with the same tag
    .setReplaceCurrent(false)
    // retry with exponential backoff
    .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)

    .setConstraints(
        // only run on an unmetered network
        Constraint.ON_UNMETERED_NETWORK,
        // // only run when the device is charging
        Constraint.DEVICE_CHARGING
    )
    .setExtras(input)
    .build()

dispatcher.mustSchedule(job)

Java

Bundle input = new Bundle();
input.putString("some_key", "some_value");

Job myJob = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyJobService.class)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // one-off job
    .setRecurring(false)
    // don't persist past a device reboot
    .setLifetime(Lifetime.UNTIL_NEXT_BOOT)
    // start between 0 and 60 seconds from now
    .setTrigger(Trigger.executionWindow(0, 60))
    // don't overwrite an existing job with the same tag
    .setReplaceCurrent(false)
    // retry with exponential backoff
    .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
    // constraints that need to be satisfied for the job to run
    .setConstraints(
        // only run on an unmetered network
        Constraint.ON_UNMETERED_NETWORK,
        // only run when the device is charging
        Constraint.DEVICE_CHARGING
    )
    .setExtras(input)
    .build();

dispatcher.mustSchedule(myJob);

要使用 WorkManager 达到同一目的,您需要:

  • 构建可用作 Worker 输入的输入数据。
  • 使用以上输入数据以及上文为 FirebaseJobDispatcher 指定的限制条件来构建 WorkRequest。
  • 将 WorkRequest 加入队列。

设置工作器的输入

FirebaseJobDispatcher 使用 Bundle 将输入数据发送到 JobService。WorkManager 改为使用 Data。所以,编写的代码变成:

Kotlin

import androidx.work.workDataOf
val data = workDataOf("some_key" to "some_val")

Java

import androidx.work.Data;
Data input = new Data.Builder()
    .putString("some_key", "some_value")
    .build();

设置工作器的限制条件

FirebaseJobDispatcher 人使用 Job.Builder.setConstaints(...) 对作业设置限制条件WorkManager 改为使用 Constraints。

Kotlin

import androidx.work.*

val constraints: Constraints = Constraints.Builder().apply {
    setRequiredNetworkType(NetworkType.CONNECTED)
    setRequiresCharging(true)
}.build()

Java

import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;

Constraints constraints = new Constraints.Builder()
    // The Worker needs Network connectivity
    .setRequiredNetworkType(NetworkType.CONNECTED)
    // Needs the device to be charging
    .setRequiresCharging(true)
    .build();

创建 WorkRequest(一次性或定期)

如需创建 OneTimeWorkRequest 和 PeriodicWorkRequest,您应该使用 OneTimeWorkRequest.Builder 和 PeriodicWorkRequest.Builder。

如需创建类似于上述 Job 的 OneTimeWorkRequest,您应执行以下操作:

Kotlin

import androidx.work.*
import java.util.concurrent.TimeUnit

val constraints: Constraints = TODO("Define constraints as above")
val request: OneTimeWorkRequest =
     // Tell which work to execute
     OneTimeWorkRequestBuilder<MyWorker>()
         // Sets the input data for the ListenableWorker
        .setInputData(input)
        // If you want to delay the start of work by 60 seconds
        .setInitialDelay(60, TimeUnit.SECONDS)
        // Set a backoff criteria to be used when retry-ing
        .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS)
        // Set additional constraints
        .setConstraints(constraints)
        .build()

Java

import androidx.work.BackoffCriteria;
import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OneTimeWorkRequest.Builder;
import androidx.work.Data;

// Define constraints (as above)
Constraints constraints = ...
OneTimeWorkRequest request =
    // Tell which work to execute
    new OneTimeWorkRequest.Builder(MyWorker.class)
        // Sets the input data for the ListenableWorker
        .setInputData(inputData)
        // If you want to delay the start of work by 60 seconds
        .setInitialDelay(60, TimeUnit.SECONDS)
        // Set a backoff criteria to be used when retry-ing
        .setBackoffCriteria(BackoffCriteria.EXPONENTIAL, 30000, TimeUnit.MILLISECONDS)
        // Set additional constraints
        .setConstraints(constraints)
        .build();

这里的主要区别在于,WorkManager 的作业始终在设备重新启动后自动保留。

如果您要创建 PeriodicWorkRequest,则需要执行以下操作:

Kotlin

val constraints: Constraints = TODO("Define constraints as above")
val request: PeriodicWorkRequest =
PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
    // Sets the input data for the ListenableWorker
    .setInputData(input)
    // Other setters
    .build()

Java

import androidx.work.BackoffCriteria;
import androidx.work.Constraints;
import androidx.work.Constraints.Builder;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.PeriodicWorkRequest.Builder;
import androidx.work.Data;

// Define constraints (as above)
Constraints constraints = ...

PeriodicWorkRequest request =
    // Executes MyWorker every 15 minutes
    new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.MINUTES)
        // Sets the input data for the ListenableWorker
        .setInputData(input)
        . // other setters (as above)
        .build();

调度工作

现在您已经定义了 Worker 和 WorkRequest,接下来可以调度工作了。

由 FirebaseJobDispatcher 定义的每个 Job 都有一个用于唯一标识 Job 的 tag。它还为应用提供了一种方法,通过调用 setReplaceCurrent,告知调度程序这个 Job 实例是否要替换现有的 Job 副本。

Kotlin

val job = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyService::class.java)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // don't overwrite an existing job with the same tag
    .setRecurring(false)
    // Other setters...
    .build()

Java

Job myJob = dispatcher.newJobBuilder()
    // the JobService that will be called
    .setService(MyJobService.class)
    // uniquely identifies the job
    .setTag("my-unique-tag")
    // don't overwrite an existing job with the same tag
    .setReplaceCurrent(false)
    // other setters
    // ...

dispatcher.mustSchedule(myJob);

使用 WorkManager 时,您可以使用 enqueueUniqueWork() 和 enqueueUniquePeriodicWork() API(分别使用 OneTimeWorkRequest 和 PeriodicWorkRequest)来实现相同的结果。如需了解详情,请参阅 WorkManager.enqueueUniqueWork() 和 WorkManager.enqueueUniquePeriodicWork() 的参考页面。

代码将如下所示:

Kotlin

import androidx.work.*

val request: OneTimeWorkRequest = TODO("A WorkRequest")
WorkManager.getInstance(myContext)
    .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, request)

Java

import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;

OneTimeWorkRequest workRequest = // a WorkRequest;
WorkManager.getInstance(myContext)
    // Use ExistingWorkPolicy.REPLACE to cancel and delete any existing pending
    // (uncompleted) work with the same unique name. Then, insert the newly-specified
    // work.
    .enqueueUniqueWork("my-unique-name", ExistingWorkPolicy.KEEP, workRequest);

取消工作

通过 FirebaseJobDispatcher,您可以使用以下方法取消工作:

Kotlin

dispatcher.cancel("my-unique-tag")

Java

dispatcher.cancel("my-unique-tag");

使用 WorkManager 时,您可以编写以下代码:

Kotlin

import androidx.work.WorkManager
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name")

Java

import androidx.work.WorkManager;
WorkManager.getInstance(myContext).cancelUniqueWork("my-unique-name");

初始化 WorkManager

WorkManager 通常使用 ContentProvider 自行初始化。 如果您需要进一步控制 WorkManager 管理和调度工作的方式,可以自定义 WorkManager 配置和初始化。