Melakukan migrasi dari Firebase JobDispatcher ke WorkManager

WorkManager adalah library untuk menjadwalkan dan mengeksekusi pekerjaan latar belakang yang dapat ditangguhkan di Android. Library ini merupakan pengganti yang direkomendasikan untuk Firebase JobDispatcher. Panduan berikut akan menjelaskan proses memigrasikan penerapan Firebase JobDispatcher ke WorkManager.

Penyiapan Gradle

Untuk mengimpor library WorkManager ke project Android, tambahkan dependensi yang tercantum di Mulai Menggunakan WorkManager.

Dari JobService ke pekerja

FirebaseJobDispatcher menggunakan subclass JobService sebagai titik masuk untuk mendefinisikan pekerjaan yang perlu dilakukan. Anda mungkin langsung menggunakan JobService, atau menggunakan SimpleJobService.

JobService akan terlihat seperti ini:

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

Jika Anda menggunakan SimpleJobService, Anda harus mengganti onRunJob(), yang menampilkan jenis @JobResult int.

Perbedaan utama adalah saat Anda menggunakan JobService secara langsung, onStartJob() akan dipanggil pada thread utama, dan merupakan tanggung jawab aplikasi untuk mengalihkan pekerjaan ke thread latar belakang. Di sisi lain, jika Anda menggunakan SimpleJobService, layanan tersebut bertanggung jawab untuk menjalankan pekerjaan Anda di thread latar belakang.

WorkManager memiliki konsep yang serupa. Unit dasar pekerjaan dalam WorkManager adalah ListenableWorker. Ada juga subjenis lainnya dari pekerja seperti Worker, RxWorker, dan CoroutineWorker (jika menggunakan coroutine Kotlin).

JobService dipetakan ke ListenableWorker

Jika Anda menggunakan JobService secara langsung, maka pekerja yang dipetakannya adalah ListenableWorker. Jika Anda menggunakan SimpleJobService, maka Anda harus menggunakan Worker sebagai gantinya.

Saatnya menggunakan contoh di atas (MyJobService) dan lihat cara kami mengonversinya menjadi 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.
  }
}

Unit dasar pekerjaan dalam WorkManager adalah ListenableWorker. Sama seperti JobService.onStartJob(), startWork() dipanggil di thread utama. Di sini MyWorker mengimplementasikan ListenableWorker dan menampilkan instance dari ListenableFuture, yang digunakan untuk menandakan penyelesaian pekerjaan secara asinkron. Anda harus memilih strategi threading Anda sendiri di sini.

Di sini ListenableFuture pada akhirnya menampilkan jenis ListenableWorker.Result yang dapat berupa salah satu Result.success(), Result.success(Data outputData), Result.retry(), Result.failure(), atau Result.failure(Data outputData). Untuk informasi selengkapnya, lihat halaman referensi untuk ListenableWorker.Result.

onStopped() dipanggil untuk menandai bahwa ListenableWorker perlu berhenti, baik karena batasan tidak lagi terpenuhi (misalnya, karena jaringan tidak lagi tersedia), atau karena metode WorkManager.cancel…() dipanggil. onStopped() juga dapat dipanggil jika OS memutuskan untuk menonaktifkan pekerjaan Anda karena beberapa alasan.

SimpleJobService dipetakan ke Pekerja

Saat menggunakan SimpleJobService, pekerja di atas akan terlihat seperti:

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

Di sini doWork() menampilkan instance dari ListenableWorker.Result untuk menandakan penyelesaian pekerjaan secara sinkron. Ini mirip dengan SimpleJobService, yang menjadwalkan tugas di thread latar belakang.

JobBuilder dipetakan ke WorkRequest

FirebaseJobBuilder menggunakan Job.Builder untuk merepresentasikan metadata Job. WorkManager menggunakan WorkRequest untuk mengisi peran ini.

WorkManager memiliki dua jenis WorkRequest: OneTimeWorkRequest dan PeriodicWorkRequest.

Jika Anda sedang menggunakan Job.Builder.setRecurring(true), maka Anda harus membuat PeriodicWorkRequest baru. Jika tidak, Anda harus menggunakan OneTimeWorkRequest.

Mari kita lihat tampilan penjadwalan Job dengan FirebaseJobDispatcher yang kompleks:

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

Untuk mencapai hal yang sama dengan WorkManager, Anda harus:

  • Membuat data input yang dapat digunakan sebagai input untuk Worker.
  • Membuat WorkRequest dengan data input dan batasan yang serupa dengan yang ditentukan di atas untuk FirebaseJobDispatcher.
  • Mengantrekan WorkRequest.

Menyiapkan input untuk Pekerja

FirebaseJobDispatcher menggunakan Bundle untuk mengirimkan data input ke JobService. WorkManager menggunakan Data sebagai gantinya. Ini akan menjadi:

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

Menyiapkan Batasan untuk Pekerja

FirebaseJobDispatcher penggunaan Job.Builder.setConstaints(...) untuk mengatur batasan pada pekerjaan. WorkManager menggunakan Constraints sebagai gantinya.

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

Membuat WorkRequest (OneTime atau Periodic)

Untuk membuat OneTimeWorkRequest dan PeriodicWorkRequest, Anda harus menggunakan OneTimeWorkRequest.Builder dan PeriodicWorkRequest.Builder.

Untuk membuat OneTimeWorkRequest yang serupa dengan Job di atas, Anda harus melakukan hal berikut:

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

Perbedaan utamanya adalah bahwa tugas WorkManager akan otomatis dipertahankan saat reboot perangkat.

Jika Anda ingin membuat PeriodicWorkRequest, maka Anda harus melakukan hal berikut:

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

Menjadwalkan pekerjaan

Setelah menentukan Worker dan WorkRequest, Anda siap untuk menjadwalkan pekerjaan.

Setiap Job yang ditentukan dengan FirebaseJobDispatcher memiliki tag yang digunakan untuk mengidentifikasi secara unik Job. Ini juga menyediakan cara bagi aplikasi untuk memberi tahu scheduler jika instance dari Job mengganti salinan Job yang ada dengan memanggil 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);

Saat menggunakan WorkManager, Anda dapat meraih hasil yang sama dengan menggunakan API enqueueUniqueWork() dan enqueueUniquePeriodicWork() (jika menggunakan OneTimeWorkRequest dan PeriodicWorkRequest). Untuk informasi selengkapnya, lihat halaman referensi untuk WorkManager.enqueueUniqueWork() dan WorkManager.enqueueUniquePeriodicWork().

Ini akan terlihat seperti:

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

Membatalkan pekerjaan

Dengan FirebaseJobDispatcher Anda dapat membatalkan pekerjaan menggunakan:

Kotlin

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

Java

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

Saat menggunakan WorkManager, Anda dapat menggunakan:

Kotlin

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

Java

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

Menginisialisasi WorkManager

WorkManager biasanya menginisialisasi sendiri dengan menggunakan ContentProvider. Jika Anda memerlukan kontrol lebih terhadap cara WorkManager mengatur dan menjadwalkan pekerjaan, Anda dapat menyesuaikan konfigurasi dan inisialisasi WorkManager.