Переход с 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 сопоставляется с 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 есть два типа WorkRequest: OneTimeWorkRequest и PeriodicWorkRequest.

Если вы используете Job.Builder.setRecurring(true), создайте новый PeriodicWorkRequest. В противном случае используйте OneTimeWorkRequest.

Давайте посмотрим, как может выглядеть планирование сложного Job с помощью FirebaseJobDispatcher:

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 (OneTime или Periodic)

Чтобы создать OneTimeWorkRequest и PeriodicWorkRequest, используйте OneTimeWorkRequest.Builder и PeriodicWorkRequest.Builder.

Чтобы создать объект OneTimeWorkRequest, аналогичный объекту Job, выполните следующие действия:

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, вы готовы составить график работы.

Каждый объект Job, определенный с помощью FirebaseJobDispatcher, имел свойство tag, которое использовалось для уникальной идентификации объекта Job. Кроме того, приложение могло сообщить планировщику, что этот экземпляр Job должен заменить существующую копию Job, вызвав setReplaceCurrent.

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 вы можете достичь того же результата с помощью API enqueueUniqueWork() и enqueueUniquePeriodicWork() (при использовании 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.