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 .