从 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。工作者的其他一些有用的子类型包括 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 映射到 Worker

使用 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 有两种类型的 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 的输入。
  • 使用输入数据和约束构建 WorkRequest,类似于上面为 FirebaseJobDispatcher 定义的约束。
  • WorkRequest 加入队列。

设置 Worker 的输入

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

设置 Worker 的约束

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

要创建一个与上述 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();

调度工作

现在您已经定义了 WorkerWorkRequest,就可以准备调度工作了。

每个使用 FirebaseJobDispatcher 定义的 Job 都有一个 tag,用于唯一标识 Job。它还提供了一种方法,允许应用程序通过调用 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 通常通过使用 ContentProvider 来初始化自身。如果您需要更多地控制 WorkManager 如何组织和调度工作,您可以 自定义 WorkManager 配置和初始化