Giới thiệu về WorkManager (Worker, WorkRequest, WorkManager) - Phần 2

I. Mở đầu

WorkManager is a library for managing deferrable and guaranteed background work.

Trong bài viết trước mình đã trình bày chi tiết về mô hình bộ nhớ Android, các tính năng tối ưu hóa pin, giải pháp xử lý background ở thời điểm hiện tại và những ưu điểm chính của WorkManager và nơi nên sử dụng. Bạn có thể xem tại đây.

Trong bài viết này, mình sẽ trình bày các thành phần chính của thư viện WorkManager:

  • Worker
  • WorkRequest
  • Constraints
  • Input/Output Data

Để sử dụng WorkManager, trước hết chúng ta nên khai báo dependencies:

  • Trong build.gradle (Project:projectName)
allprojects {
   repositories {
       google()
       jcenter()
   }
}
  • Trong build.gradle (Module:app)
dependencies {
   def work_version = "2.1.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 - Test helpers
   androidTestImplementation "androidx.work:work-testing:$work_version"
 }

Trước hết chúng ta cần tạo một background task bằng cách kế thừa lớp Worker. Sau đó, chúng ta xác định cách thức và thời điểm chạy các tác vụ bằng cách triển khai WorkRequest và cuối cùng chúng ta bàn giao task cho hệ thống thực hiện.

Q: Điều gì xảy ra ở phía sau? Cơ chế nội bộ (Internal mechanism) được sử dụng bởi WorkManager là gì?

A: Thực tế, WorkManager giống như 1 wrapper cho các tiến trình background, trì hoãn và đảm bảo các công việc được thực hiện.

II. Nội dung

1. Worker

1 task được xác định bởi Worker class. Phương thức doWork() thực hiện đồng bộ công việc trên worker thread.

doWork() sẽ trả về Result - cho biết kết quả thành công hay thất bại.

class SyncWorker(context: Context, wp: WorkerParameters):Worker(c, wp) {
override fun doWork(): Result {
      loadData()
      return Result.success()
  }
}

2. WorkRequest

Nhiệm vụ chính của WorkRequest là xác định lớp Worker nào sẽ thực hiện task. Mỗi WorkRequest có một ID duy nhất. Thông qua ID này, bạn có thể cancel task hoặc lấy trạng thái của task.

Tương tự như Worker, WorkRequest là một abstract class nên bạn sẽ sử dụng một trong các lớp con của nó là OneTimeWorkRequest hoặc PeriodicWorkRequest.

Khi một task được xác định, chúng ta cần xác định nó là task được thực hiện 1 lần duy nhất hay thực hiện định kỳ. Nếu chỉ thực hiện 1 lần thì chúng ta sẽ sử dụng OneTimeWorkRequestPeriodicWorkRequest class nếu task đó được thực hiện định kỳ.

  • OneTimeWorkRequest

    • Dành cho công việc không được lặp lại
    • Có thể có thiết lập độ trễ
    • Có thể là 1 phần của 1 chuỗi các công việc
  • PeriodicWorkRequest

    • Được sử dụng cho các công việc cần thực hiện định kỳ cho đến khi bị hủy.
    • Khoảng thời gian lặp lại tối thiểu có thể được xác định là 15 phút (giống như API của JobScheduler) và không có độ trễ ban đầu
    • Không thể là 1 phần của 1 chuỗi các công việc
    • Trước v2.1-alpha02, bạn không thể tạo một periodicWorkRequest với độ trễ ban đầu.
    • Việc thực thi có thể bị trì hoãn vì WorkManager tuân theo tối ưu hóa pin của hệ điều hành, chẳng hạn như chế độ ngủ gật (doze mode)

val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncWorker>().build()

val syncPeriodically = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS).build()

val periodicRefreshRequest = PeriodicWorkRequest.Builder(
  SyncWorker::class.java, // the worker class
  30, // repeating interval
  TimeUnit.Minutes,
  15, // flex interval - worker will run somehow within this period of time, but at the end of repeating interval
  TimeUnit.MINUTES
)

Happy and Unhappy paths for One Time Work and Periodic Work

Các task nên được thực thi khi thỏa mãn một số điều kiện, có thể chúng ta cần kết nối wi-fi và thiết bị sẽ không hoạt động. Điều này có thể thực hiện được trong WorkManager bằng cách sử dụng lớp Constraints và có 5 hạn chế có thể được sử dụng:

  • network type limitations: setRequiredNetworkType(requiredNetworkType: NetworkType)
  • battery level limitations: setRequiresBatteryNotLow(requiresBatteryNotLow: Boolean)
  • charging limitations: setRequiresCharging(requiresCharging: Boolean)
  • status of the device limitations: setRequiresDeviceIdle(requiresDeviceIdle: Boolean)
  • storage level limitations: setRequiresStorageNotLow(requiresStorageNotLow: Boolean)

Với NetworkType, chúng ta có 5 giá trị enum:

  • CONNECTED — Any working network connection
  • METERED — A metered network connection
  • NOT_REQUIRED — A network is not required for this work.
  • NOT_ROAMING — A non-roaming network connection
  • UNMETERED — An unmetered network connection

3. Constraints

Chúng ta có thể áp dụng Constraints như sau:

val constraints = Constraints.Builder()
         .setRequiresCharging(true)
         .setRequiresStorageNotLow(true)
         .setRequiredNetworkType(NetworkType.CONNECTED)
         .build()
         
val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncDataWorker>()
  .setConstraints(constraints)
  .build()

4. Input and output Data

  • Lớp hỗ trợ Data.Builder cho phép chúng ta cung data cho Worker
  • Lưu ý data không được vượt quá 10KB. Nếu không thì chúng ta sẽ nhận được IllegalStateException nếu data vượt quá giới hạn cho phép.
  • Data được lưu trữ dưới dạng Key-Value

val userIdData = Data.Builder()
   .putInt(DATA_USER_ID, userId)
   .build()
val syncOnlyOnce = OneTimeWorkRequestBuilder<SyncWorker>()
   .setInputData(userIdData)
   .build()
val userIdInput = inputData.getInt(Constants.DATA_USER_ID, 0)
// ktx
val outputData = workDataOf(Constants.DATA_SENT to isDataSent)

5. WorkManager

Sau khi các công việc được xác định bởi lớp Worker và chúng ta cũng biết loại nhiệm vụ (định kỳ hay không), bây giờ chúng ta có thể lên lịch với WorkManager bằng phương thức enqueue(). Và nếu chúng ta quyết định hủy bỏ tất cả công việc thì chúng ta có thể dùng phương thức cancelALlWork()

// running work
WorkManager.getInstance(application).enqueue(syncOnlyOnce)

// cancelling work
WorkManager.getInstance(application).cancelAllWork()

III. Kết luận

Ngoài nội dung trong bài viết này, bạn có thể tham khảo ví dụ về cách sử dụng WorkManager tại đây.

Trong bài viết tiếp theo, chúng ta sẽ xem xét kỹ hơn về BackoffPolicy, cách xác định một task, làm thế nào để có được trạng thái của một task, cách kết hợp các task và biểu đồ của các task (xâu chuỗi công việc) và cách hợp nhất các đầu vào và đầu ra. Hãy theo dõi!