Миграция с Firebase JobDispatcher на WorkManager

WorkManager — это библиотека для планирования и выполнения отложенной фоновой работы в Android. Это рекомендуемая замена Firebase JobDispatcher. Следующее руководство проведет вас через процесс миграции реализации Firebase JobDispatcher в WorkManager.

Настройка Gradle

Чтобы импортировать библиотеку WorkManager в проект Android, добавьте зависимости, перечисленные в разделе «Начало работы с WorkManager» .

От JobService к работникам

FirebaseJobDispatcher использует подкласс JobService в качестве точки входа для определения работы, которую необходимо выполнить. Возможно, вы используете JobService напрямую или SimpleJobService .

JobService будет выглядеть примерно так:

Котлин

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?"
    }
}

Ява

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 .

Котлин

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.
    }
}

Ява

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 вышеуказанный рабочий будет выглядеть так:

Котлин

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")
    }
}

Ява

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 :

Котлин

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)

Ява

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 . Итак, это становится:

Котлин

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

Ява

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

Настройка ограничений для работника

FirebaseJobDispatcher использует Job.Builder.setConstaints(...) для установки ограничений для заданий. Вместо этого WorkManager использует Constraints .

Котлин

import androidx.work.*

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

Ява

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 .

Чтобы создать OneTimeWorkRequest , аналогичный приведенному выше Job вам необходимо сделать следующее:

Котлин

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

Ява

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 , вам следует сделать что-то вроде:

Котлин

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

Ява

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 .

Котлин

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

Ява

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

Это будет выглядеть примерно так:

Котлин

import androidx.work.*

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

Ява

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 вы можете отменить работу, используя:

Котлин

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

Ява

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

При использовании WorkManager вы можете использовать:

Котлин

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

Ява

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

Инициализация WorkManager

WorkManager обычно инициализируется с помощью ContentProvider . Если вам требуется больший контроль над тем, как WorkManager организует и планирует работу, вы можете настроить конфигурацию и инициализацию WorkManager .