Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

Memigrasikan dari Firebase JobDispatcher ke WorkManager

WorkManager adalah library untuk menjadwalkan dan menjalankan 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 berikut ke file build.gradle aplikasi Anda:

dependencies {
  def work_version = "2.4.0"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional - GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
  }

Dari JobService ke Pekerja

FirebaseJobDispatcher menggunakan subclass dari JobService sebagai titik masuk untuk menentukan pekerjaan yang harus diselesaikan. 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 ListenableWorker.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 WorkRequests

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 seperti apa 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. Sehingga 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 menggunakan Job.Builder.setConstaints(...) untuk menyiapkan batasan pada tugas. 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 perlu diinisialisasi sekali setiap aplikasi, biasanya menggunakan ContentProvider atau Application.onCreate().

WorkManager biasanya menginisialisasi sendiri dengan menggunakan ContentProvider. Namun, ada beberapa perbedaan kecil secara default yang berkaitan dengan ukuran threadpool, dan jumlah pekerja yang dapat dijadwalkan pada waktu tertentu. Jadi, Anda mungkin perlu menyesuaikan WorkManager.

Biasanya, penyesuaian ini dilakukan menggunakan WorkManager.initialize(). Ini memungkinkan Anda menyesuaikan Executor latar belakang yang digunakan untuk menjalankan Worker, dan WorkerFactory digunakan untuk membuat Workers. (WorkerFactory berguna dalam konteks injeksi dependensi). Harap baca dokumentasi untuk metode ini guna memastikan Anda menghentikan inisialisasi otomatis WorkManager.

Untuk informasi selengkapnya, lihat dokumentasi untuk initialize() dan untuk Configuration.Builder.