从 Firebase JobDispatcher 迁移到 WorkManager

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

Gradle 设置

要将 WorkManager 库导入到 Android 项目中,请将以下依赖项添加到应用的 build.gradle 文件:

    dependencies {
      def work_version = "2.3.4"

        // (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"
      }
    

从 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。另外还有其他有用的工作器子类型,如 WorkerRxWorkerCoroutineWorker(使用 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 ListenableWorker.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 有两种类型 WorkRequestOneTimeWorkRequestPeriodicWorkRequest

如果您当前使用的是 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(一次性或定期)

要创建 OneTimeWorkRequestPeriodicWorkRequest,您应该使用 OneTimeWorkRequest.BuilderPeriodicWorkRequest.Builder

要创建类似于上述 JobOneTimeWorkRequest,您应执行以下操作:

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();
    

调度工作

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

FirebaseJobDispatcher 定义的每个 Job 都有一个用于唯一标识 Jobtag它还为应用提供了一种方法,通过调用 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(分别使用 OneTimeWorkRequestPeriodicWorkRequest)来实现相同的结果。有关详情,请参阅 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 需要针对每个应用进行一次初始化,通常使用 ContentProviderApplication.onCreate()

WorkManager 通常使用 ContentProvider 自行初始化。不过,关于线程池的大小以及可以在给定时间调度的工作器数量的默认值存在一些细微差异。因此,您可能需要自定义 WorkManager。

通常情况下,此自定义操作是使用 WorkManager.initialize() 完成的。这样您就可以自定义用于运行 Worker 的后台 Executor 和用于构造 WorkersWorkerFactory。(WorkerFactory 在依赖项注入方面很有用)请阅读此方法的文档,以确保停止 WorkManager 的自动初始化。

有关详情,请参阅 initialize()Configuration.Builder 的文档。