Di chuyển từ Firebase JobDispatcher sang WorkManager

WorkManager là thư viện để lên lịch và thực thi thao tác cần thiết trong nền có thể trì hoãn trong Android. Đây là giải pháp thay thế đề xuất cho JobDispatcher của Firebase. Hướng dẫn sau đây sẽ hướng dẫn bạn quy trình di chuyển hoạt động triển khai JobDispatcher của Firebase sang WorkManager.

Thiết lập Gradle

Để nhập thư viện WorkManager vào dự án Android, hãy thêm các phần phụ thuộc được liệt kê trong bài viết Bắt đầu sử dụng WorkManager.

Từ JobService đến các worker

FirebaseJobDispatcher sử dụng lớp con của JobService làm điểm truy cập để xác định công việc cần hoàn thành. Bạn có thể đang dùng JobService trực tiếp hoặc dùng SimpleJobService.

JobService sẽ có dạng như sau:

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

Nếu bạn đang dùng SimpleJobService, bạn sẽ ghi đè onRunJob(). Giao thức này sẽ trả về một loại @JobResult int.

Điểm khác biệt chính là khi bạn đang dùng JobService trực tiếp, onStartJob() sẽ được gọi trên chuỗi chính và ứng dụng có trách nhiệm giảm tải công việc vào một chuỗi trong nền. Mặt khác, nếu bạn đang dùng SimpleJobService, dịch vụ đó chịu trách nhiệm thực thi công việc của bạn trên chuỗi nền.

WorkManager có các khái niệm tương tự. Đơn vị công việc cơ bản trong WorkManager là ListenableWorker. Ngoài ra, còn có các loại worker hữu ích khác như Worker, RxWorkerCoroutineWorker (khi sử dụng coroutine Kotlin).

JobService liên kết đến ListenableWorker

Nếu bạn đang dùng JobService trực tiếp, thì worker mà ứng dụng liên kết với tài khoản này là ListenableWorker. Nếu đang dùng SimpleJobService thì bạn nên dùng Worker.

Hãy sử dụng ví dụ (MyJobService) ở trên và tìm hiểu cách chúng tôi có thể chuyển đổi sang 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.
  }
}

Đơn vị công việc cơ bản trong WorkManager là ListenableWorker. Giống như JobService.onStartJob(), startWork() được gọi trên chuỗi chính. Tại đây, MyWorker triển khai ListenableWorker và trả về một bản sao của ListenableFuture, dùng để thông báo về việc hoàn thành công việc không đồng bộ. Bạn nên chọn chiến lược tạo luồng của riêng bạn tại đây.

ListenableFuture tại đây cuối cùng sẽ trả về một loại ListenableWorker.Result có thể là một trong các loại Result.success(), Result.success(Data outputData), Result.retry(), Result.failure() hoặc Result.failure(Data outputData). Để biết thêm thông tin, hãy xem trang tham khảo cho ListenableWorker.Result.

onStopped() được gọi để thông báo rằng ListenableWorker cần phải dừng vì các hạn chế đó không còn được đáp ứng (ví dụ: do mạng không còn nữa) hoặc do WorkManager.cancel…() được gọi. onStopped() cũng có thể được gọi nếu hệ điều hành quyết định ngừng hoạt động vì lý do nào đó.

SimpleJobService liên kết đến một Worker

Khi sử dụng SimpleJobService, worker ở trên sẽ trông giống như sau:

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

Tại đây, doWork() trả về một bản sao của ListenableWorker.Result để báo hiệu việc hoàn thành công việc một cách đồng bộ. Phương thức này tương tự như SimpleJobService – chế độ này lên lịch việc làm trên một chuỗi trong nền.

JobBuilder liên kết đến WorkRequest

FirebaseJobBuilder sử dụng Job.Builder để đại diện cho siêu dữ liệu Job. WorkManager sử dụng WorkRequest để thực hiện vai trò này.

WorkManager có hai loại WorkRequest: OneTimeWorkRequestPeriodicWorkRequest.

Nếu hiện đang dùng Job.Builder.setRecurring(true), bạn nên tạo PeriodicWorkRequest mới. Nếu không, bạn nên dùng OneTimeWorkRequest.

Hãy xem xét việc lập lịch cho một khu phức hợp Job với FirebaseJobDispatcher có thể trông giống như sau:

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

Để đạt được cùng mục đích với WorkManager, bạn cần:

  • Xây dựng dữ liệu đầu vào có thể được dùng làm dữ liệu đầu vào cho Worker.
  • Xây dựng một WorkRequest với dữ liệu đầu vào và các quy tắc ràng buộc tương tự như dữ liệu đầu vào đã xác định ở trên cho FirebaseJobDispatcher.
  • Thêm WorkRequest vào hàng đợi.

Thiết lập đầu vào cho Worker

FirebaseJobDispatcher sử dụng Bundle để gửi dữ liệu đầu vào đến JobService. WorkManager sử dụng Data. Vì vậy, những điều đó sẽ trở thành:

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

Thiết lập các Quy tắc ràng buộc cho Worker

FirebaseJobDispatcher sử dụng Job.Builder.setConstaints(...) để thiết lập các quy tắc ràng buộc đối với công việc. WorkManager sử dụng 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();

Tạo WorkRequest (Một lần hoặc định kỳ)

Để tạo OneTimeWorkRequestPeriodicWorkRequest, bạn nên dùng OneTimeWorkRequest.BuilderPeriodicWorkRequest.Builder.

Để tạo một OneTimeWorkRequest tương tự như Job ở trên, bạn nên làm những việc sau:

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

Điểm khác biệt chính ở đây là các công việc của WorkManager luôn tồn tại tự động khi thiết bị khởi động lại.

Nếu muốn tạo PeriodicWorkRequest, bạn sẽ làm những việc như:

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

Lên lịch cho công việc

Bây giờ, bạn đã xác định được WorkerWorkRequest, nên bạn có thể lên lịch cho công việc.

Mỗi Job được xác định bằng FirebaseJobDispatcher có một tag dùng để xác định duy nhất một Job. Phương thức này cũng cung cấp một cách để ứng dụng thông báo cho bộ lập lịch nếu bản sao của Job này nhằm thay thế một bản sao hiện có của Job bằng cách gọi 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);

Khi sử dụng WorkManager, bạn có thể đạt được kết quả tương tự bằng cách dùng API enqueueUniqueWork()enqueueUniquePeriodicWork() (khi sử dụng OneTimeWorkRequestPeriodicWorkRequest tương ứng). Để biết thêm thông tin, hãy xem các trang tham khảo dành cho WorkManager.enqueueUniqueWork()WorkManager.enqueueUniquePeriodicWork().

Thẻ sẽ có dạng như sau:

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

Huỷ công việc

Với FirebaseJobDispatcher, bạn có thể huỷ công việc bằng cách dùng:

Kotlin

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

Java

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

Khi sử dụng WorkManager, bạn có thể dùng:

Kotlin

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

Java

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

Khởi chạy WorkManager

WorkManager thường tự khởi chạy bằng cách sử dụng ContentProvider. Nếu cần có thêm quyền kiểm soát về cách WorkManager tổ chức và lên lịch hoạt động, thì bạn có thể tuỳ chỉnh cấu hình và quy trình khởi chạy của WorkManager.