Kiến thức cơ bản về Kotlin trên Android 09.2: WorkManager

Lớp học lập trình này thuộc khoá học Kiến thức cơ bản về Kotlin cho Android. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tất cả lớp học lập trình của khoá học đều được liệt kê trên trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.

Giới thiệu

Hầu hết các ứng dụng trong thực tế đều cần thực hiện các tác vụ trong nền dài hạn. Ví dụ: một ứng dụng có thể tải tệp lên máy chủ, đồng bộ hoá dữ liệu từ máy chủ và lưu vào cơ sở dữ liệu Room, gửi nhật ký đến máy chủ hoặc thực thi các thao tác tốn kém trên dữ liệu. Bạn nên thực hiện những thao tác như vậy ở chế độ nền, ngoài luồng giao diện người dùng (luồng chính). Các tác vụ trong nền tiêu thụ tài nguyên có hạn của thiết bị, chẳng hạn như RAM và pin. Nếu không được xử lý đúng cách, điều này có thể dẫn đến trải nghiệm không tốt cho người dùng.

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng WorkManager để lên lịch một tác vụ ở chế độ nền theo cách tối ưu hoá và hiệu quả. Để tìm hiểu thêm về các giải pháp khác có sẵn để xử lý ở chế độ nền trong Android, hãy xem Hướng dẫn xử lý ở chế độ nền.

Kiến thức bạn cần có

  • Cách sử dụng ViewModel, LiveDataRoom trong Bộ thành phần cấu trúc Android.
  • Cách thực hiện các phép biến đổi trên một lớp LiveData.
  • Cách tạo và chạy một coroutine.
  • Cách sử dụng bộ chuyển đổi liên kết trong liên kết dữ liệu.
  • Cách tải dữ liệu được lưu vào bộ nhớ đệm bằng mẫu kho lưu trữ.

Kiến thức bạn sẽ học được

  • Cách tạo một Worker, đại diện cho một đơn vị công việc.
  • Cách tạo một WorkRequest để yêu cầu thực hiện công việc.
  • Cách thêm các quy tắc ràng buộc vào WorkRequest để xác định cách thức và thời điểm chạy một worker.
  • Cách sử dụng WorkManager để lên lịch cho các tác vụ ở chế độ nền.

Bạn sẽ thực hiện

  • Tạo một worker để thực thi một tác vụ ở chế độ nền nhằm tìm nạp trước danh sách phát video DevBytes từ mạng.
  • Lên lịch để worker chạy định kỳ.
  • Thêm các điều kiện ràng buộc vào WorkRequest.
  • Lên lịch cho một WorkRequest định kỳ được thực thi mỗi ngày một lần.

Trong lớp học lập trình này, bạn sẽ làm việc trên ứng dụng DevBytes mà bạn đã phát triển trong một lớp học lập trình trước đó. (Nếu chưa cài đặt ứng dụng này, bạn có thể tải mã khởi đầu cho bài học này xuống.)

Ứng dụng DevBytes hiển thị danh sách các video DevBytes, là các video hướng dẫn ngắn do nhóm Quan hệ với nhà phát triển Android của Google tạo ra. Các video này giới thiệu các tính năng dành cho nhà phát triển và các phương pháp hay nhất để phát triển trên Android.

Bạn nâng cao trải nghiệm người dùng trong ứng dụng bằng cách tìm nạp trước các video mỗi ngày một lần. Điều này đảm bảo người dùng nhận được nội dung mới nhất ngay khi họ mở ứng dụng.

Trong nhiệm vụ này, bạn sẽ tải và kiểm tra mã khởi đầu.

Bước 1: Tải xuống và chạy ứng dụng khởi động

Bạn có thể tiếp tục làm việc với ứng dụng DevBytes mà bạn đã tạo trong lớp học lập trình trước (nếu có). Ngoài ra, bạn có thể tải ứng dụng khởi động xuống.

Trong nhiệm vụ này, bạn sẽ tải và chạy ứng dụng khởi đầu, đồng thời kiểm tra mã khởi đầu.

  1. Nếu bạn chưa có ứng dụng DevBytes, hãy tải mã khởi đầu DevBytes cho lớp học lập trình này xuống từ dự án DevBytesRepository trên GitHub.
  2. Giải nén mã rồi mở dự án trong Android Studio.
  3. Kết nối thiết bị kiểm thử hoặc trình mô phỏng với Internet (nếu chưa kết nối). Tạo và chạy ứng dụng. Ứng dụng sẽ tìm nạp danh sách video DevByte từ mạng và hiển thị danh sách đó.
  4. Trong ứng dụng, hãy nhấn vào một video bất kỳ để mở video đó trong ứng dụng YouTube.

Bước 2: Khám phá mã

Ứng dụng khởi đầu có nhiều mã được giới thiệu trong lớp học lập trình trước. Mã khởi đầu cho lớp học lập trình này có các mô-đun mạng, giao diện người dùng, bộ nhớ đệm khi không có kết nối mạng và kho lưu trữ. Bạn có thể tập trung vào việc lập lịch cho tác vụ ở chế độ nền bằng cách sử dụng WorkManager.

  1. Trong Android Studio, hãy mở rộng tất cả các gói.
  2. Khám phá gói database. Gói này chứa các thực thể cơ sở dữ liệu và cơ sở dữ liệu cục bộ, được triển khai bằng Room.
  3. Khám phá gói repository. Gói này chứa lớp VideosRepository giúp tách lớp dữ liệu khỏi phần còn lại của ứng dụng.
  4. Hãy tự mình khám phá phần còn lại của mã khởi đầu, với sự trợ giúp của lớp học lập trình trước đó.

WorkManager là một trong những Bộ thành phần cấu trúc Android và là một phần của Android Jetpack. WorkManager dành cho công việc trong nền có thể trì hoãn và yêu cầu đảm bảo thực thi:

  • Có thể trì hoãn có nghĩa là công việc không bắt buộc phải chạy ngay lập tức. Ví dụ: gửi dữ liệu phân tích đến máy chủ hoặc đồng bộ hoá cơ sở dữ liệu ở chế độ nền là những công việc có thể trì hoãn.
  • Đảm bảo thực thi có nghĩa là tác vụ sẽ chạy ngay cả khi ứng dụng thoát hoặc thiết bị khởi động lại.

Trong khi WorkManager chạy các hoạt động ở chế độ nền, ứng dụng này sẽ xử lý các vấn đề về khả năng tương thích và áp dụng các phương pháp hay nhất để quản lý tình trạng pin và hệ thống. WorkManager cung cấp khả năng tương thích ngược đến API cấp 14. WorkManager chọn một cách phù hợp để lên lịch cho một tác vụ trong nền, tuỳ thuộc vào cấp độ API của thiết bị. Có thể sử dụng JobScheduler (trên API 23 trở lên) hoặc kết hợp AlarmManagerBroadcastReceiver.

WorkManager cũng cho phép bạn đặt tiêu chí về thời điểm chạy tác vụ ở chế độ nền. Ví dụ: bạn có thể muốn tác vụ chỉ chạy khi trạng thái pin, trạng thái mạng hoặc trạng thái sạc đáp ứng một số tiêu chí nhất định. Bạn sẽ tìm hiểu cách đặt các ràng buộc sau trong lớp học lập trình này.

Trong lớp học lập trình này, bạn sẽ lên lịch cho một tác vụ tìm nạp trước danh sách phát video DevBytes từ mạng mỗi ngày một lần. Để lên lịch cho tác vụ này, bạn sử dụng thư viện WorkManager.

  1. Mở tệp build.gradle (Module:app) và thêm phần phụ thuộc WorkManager vào dự án.

    Nếu bạn sử dụng phiên bản mới nhất của thư viện, thì ứng dụng giải pháp sẽ biên dịch như dự kiến. Nếu không, hãy thử giải quyết vấn đề hoặc quay lại phiên bản thư viện được trình bày bên dưới.
// WorkManager dependency
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
  1. Đồng bộ hoá dự án và đảm bảo không có lỗi biên dịch.

Trước khi thêm mã vào dự án, hãy làm quen với các lớp sau trong thư viện WorkManager:

  • Worker
    Đây là lớp mà bạn xác định công việc thực tế (tác vụ) cần chạy ở chế độ nền. Bạn mở rộng lớp này và ghi đè phương thức doWork(). Phương thức doWork() là nơi bạn đặt mã sẽ được thực hiện ở chế độ nền, chẳng hạn như đồng bộ hoá dữ liệu với máy chủ hoặc xử lý hình ảnh. Bạn triển khai Worker trong nhiệm vụ này.
  • WorkRequest
    Lớp này thể hiện yêu cầu chạy worker ở chế độ nền. Sử dụng WorkRequest để định cấu hình cách thức và thời điểm chạy tác vụ worker, với sự trợ giúp của Constraints, chẳng hạn như thiết bị được cắm nguồn hoặc kết nối Wi-Fi. Bạn sẽ triển khai WorkRequest trong một nhiệm vụ sau.
  • WorkManager
    Lớp này lên lịch và chạy WorkRequest của bạn. WorkManager lên lịch cho các yêu cầu công việc theo cách phân bổ tải cho các tài nguyên hệ thống trong khi vẫn tuân thủ các quy tắc ràng buộc mà bạn chỉ định. Bạn sẽ triển khai WorkManager trong một nhiệm vụ sau.

Bước 1: Tạo một worker

Trong nhiệm vụ này, bạn sẽ thêm một Worker để tìm nạp trước danh sách phát video DevBytes ở chế độ nền.

  1. Trong gói devbyteviewer, hãy tạo một gói mới có tên là work.
  2. Trong gói work, hãy tạo một lớp Kotlin mới có tên là RefreshDataWorker.
  3. Mở rộng lớp RefreshDataWorker từ lớp CoroutineWorker. Truyền contextWorkerParameters dưới dạng tham số hàm khởi tạo.
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}
  1. Để giải quyết lỗi lớp trừu tượng, hãy ghi đè phương thức doWork() bên trong lớp RefreshDataWorker.
override suspend fun doWork(): Result {
  return Result.success()
}

Hàm tạm ngưng là hàm có thể tạm dừng và tiếp tục sau đó. Một hàm tạm ngưng có thể thực thi một thao tác chạy trong thời gian dài và chờ thao tác đó hoàn tất mà không chặn luồng chính.

Bước 2: Triển khai doWork()

Phương thức doWork() bên trong lớp Worker được gọi trên một luồng nền. Phương thức này thực hiện công việc một cách đồng bộ và phải trả về một đối tượng ListenableWorker.Result. Hệ thống Android cho phép Worker có tối đa 10 phút để hoàn tất quá trình thực thi và trả về một đối tượng ListenableWorker.Result. Sau khi hết thời gian này, hệ thống sẽ buộc dừng Worker.

Để tạo một đối tượng ListenableWorker.Result, hãy gọi một trong các phương thức tĩnh sau để cho biết trạng thái hoàn thành của hoạt động ở chế độ nền:

  • Result.success() – công việc đã hoàn tất thành công.
  • Result.failure() – công việc hoàn tất nhưng gặp lỗi vĩnh viễn.
  • Result.retry() – công việc gặp phải lỗi tạm thời và cần được thử lại.

Trong nhiệm vụ này, bạn triển khai phương thức doWork() để tìm nạp danh sách phát video DevBytes từ mạng. Bạn có thể sử dụng lại các phương thức hiện có trong lớp VideosRepository để truy xuất dữ liệu từ mạng.

  1. Trong lớp RefreshDataWorker, bên trong doWork(), hãy tạo và khởi tạo một đối tượng VideosDatabase và một đối tượng VideosRepository.
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)

   return Result.success()
}
  1. Trong lớp RefreshDataWorker, bên trong doWork(), phía trên câu lệnh return, hãy gọi phương thức refreshVideos() bên trong khối try. Thêm nhật ký để theo dõi thời điểm chạy worker.
try {
   repository.refreshVideos( )
   Timber.d("Work request for sync is run")
   } catch (e: HttpException) {
   return Result.retry()
}

Để giải quyết lỗi "Tham chiếu chưa được giải quyết", hãy nhập retrofit2.HttpException.

  1. Sau đây là lớp RefreshDataWorker hoàn chỉnh để bạn tham khảo:
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {
       val database = getDatabase(applicationContext)
       val repository = VideosRepository(database)
       try {
           repository.refreshVideos()
       } catch (e: HttpException) {
           return Result.retry()
       }
       return Result.success()
   }
}

Worker xác định một đơn vị công việc và WorkRequest xác định cách thức và thời điểm chạy công việc. Có 2 cách triển khai cụ thể của lớp WorkRequest:

  • Lớp OneTimeWorkRequest dành cho các tác vụ một lần. (Việc cần làm một lần chỉ xảy ra một lần.)
  • Lớp PeriodicWorkRequest dành cho các công việc định kỳ, công việc lặp đi lặp lại theo các khoảng thời gian.

Công việc có thể là công việc một lần hoặc công việc định kỳ, vì vậy, hãy chọn lớp học cho phù hợp. Để biết thêm thông tin về cách lên lịch cho công việc định kỳ, hãy xem tài liệu về công việc định kỳ.

Trong nhiệm vụ này, bạn sẽ xác định và lên lịch cho WorkRequest để chạy worker mà bạn đã tạo trong nhiệm vụ trước.

Bước 1: Thiết lập công việc định kỳ

Trong một ứng dụng Android, lớp Application là lớp cơ sở chứa tất cả các thành phần khác, chẳng hạn như hoạt động và dịch vụ. Khi quy trình cho ứng dụng hoặc gói của bạn được tạo, lớp Application (hoặc bất kỳ lớp con nào của Application) sẽ được tạo thực thể trước bất kỳ lớp nào khác.

Trong ứng dụng mẫu này, lớp DevByteApplication là một lớp con của lớp Application. Lớp DevByteApplication là nơi phù hợp để lên lịch cho WorkManager.

  1. Trong lớp DevByteApplication, hãy tạo một phương thức tên là setupRecurringWork() để thiết lập công việc định kỳ ở chế độ nền.
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
  1. Bên trong phương thức setupRecurringWork(), hãy tạo và khởi động một yêu cầu định kỳ để chạy một lần mỗi ngày bằng phương thức PeriodicWorkRequestBuilder(). Truyền vào lớp RefreshDataWorker mà bạn đã tạo trong nhiệm vụ trước. Truyền vào khoảng thời gian lặp lại là 1 với đơn vị thời gian là TimeUnit.DAYS.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

Để giải quyết lỗi này, hãy nhập java.util.concurrent.TimeUnit.

Bước 2: Lên lịch WorkRequest bằng WorkManager

Sau khi xác định WorkRequest, bạn có thể lên lịch cho WorkManager bằng phương thức enqueueUniquePeriodicWork(). Phương thức này cho phép bạn thêm một PeriodicWorkRequest có tên riêng biệt vào hàng đợi, trong đó chỉ có một PeriodicWorkRequest có tên cụ thể có thể hoạt động tại một thời điểm.

Ví dụ: bạn có thể chỉ muốn một thao tác đồng bộ hoá hoạt động. Nếu có một thao tác đồng bộ hoá đang chờ xử lý, bạn có thể chọn để thao tác đó chạy hoặc thay thế bằng thao tác mới của mình bằng cách sử dụng ExistingPeriodicWorkPolicy.

Để tìm hiểu thêm về các cách lên lịch cho WorkRequest, hãy xem tài liệu về WorkManager.

  1. Trong lớp RefreshDataWorker, ở đầu lớp, hãy thêm một đối tượng đồng hành. Xác định tên worker để xác định riêng worker này.
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. Trong lớp DevByteApplication, ở cuối phương thức setupRecurringWork(), hãy lên lịch cho công việc bằng phương thức enqueueUniquePeriodicWork(). Truyền enum KEEP cho ExistingPeriodicWorkPolicy. Truyền repeatingRequest dưới dạng tham số PeriodicWorkRequest.
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

Nếu có tác vụ đang chờ xử lý (chưa hoàn thành) có cùng tên, thì tham số ExistingPeriodicWorkPolicy.KEEP sẽ khiến WorkManager giữ lại tác vụ định kỳ trước đó và loại bỏ yêu cầu tác vụ mới.

  1. Ở đầu lớp DevByteApplication, hãy tạo một đối tượng CoroutineScope. Truyền Dispatchers.Default dưới dạng tham số hàm khởi tạo.
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. Trong lớp DevByteApplication, hãy thêm một phương thức mới có tên là delayedInit() để bắt đầu một coroutine.
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. Bên trong phương thức delayedInit(), hãy gọi setupRecurringWork().
  2. Di chuyển quá trình khởi chạy Timber từ phương thức onCreate() sang phương thức delayedInit().
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. Trong lớp DevByteApplication, ở cuối phương thức onCreate(), hãy thêm một lệnh gọi đến phương thức delayedInit().
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. Mở ngăn Logcat ở cuối cửa sổ Android Studio. Lọc trên RefreshDataWorker.
  2. Chạy ứng dụng. WorkManager sẽ lên lịch cho công việc định kỳ của bạn ngay lập tức.

    Trong ngăn Logcat, hãy lưu ý các câu lệnh nhật ký cho biết rằng yêu cầu công việc được lên lịch, sau đó chạy thành công.
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

Nhật ký WM-WorkerWrapper được hiển thị từ thư viện WorkManager, nên bạn không thể thay đổi thông báo nhật ký này.

Bước 3: (Không bắt buộc) Lên lịch WorkRequest cho khoảng thời gian tối thiểu

Trong bước này, bạn giảm khoảng thời gian từ 1 ngày xuống 15 phút. Bạn làm việc này để có thể xem nhật ký cho một yêu cầu công việc định kỳ đang hoạt động.

  1. Trong lớp DevByteApplication, bên trong phương thức setupRecurringWork(), hãy đánh dấu ghi chú vào định nghĩa repeatingRequest hiện tại. Thêm một yêu cầu công việc mới với khoảng thời gian lặp lại định kỳ là 15 phút.
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. Mở ngăn Logcat trong Android Studio và lọc theo RefreshDataWorker. Để xoá các nhật ký trước đó, hãy nhấp vào biểu tượng Clear logcat (Xoá logcat) .
  2. Chạy ứng dụng và WorkManager sẽ lên lịch cho công việc định kỳ của bạn ngay lập tức. Trong ngăn Logcat, hãy chú ý đến nhật ký – yêu cầu công việc được chạy 15 phút một lần. Đợi 15 phút để xem một bộ nhật ký yêu cầu công việc khác. Bạn có thể để ứng dụng chạy hoặc đóng ứng dụng; trình quản lý công việc vẫn sẽ chạy.

    Xin lưu ý rằng đôi khi khoảng thời gian này ít hơn 15 phút và đôi khi nhiều hơn 15 phút. (Thời gian chính xác có thể thay đổi tuỳ theo chế độ tối ưu hoá pin của hệ điều hành.)
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work
 

Xin chúc mừng! Bạn đã tạo một worker và lên lịch cho yêu cầu công việc bằng WorkManager. Nhưng có một vấn đề: bạn chưa chỉ định bất kỳ ràng buộc nào. WorkManager sẽ lên lịch cho công việc mỗi ngày một lần, ngay cả khi thiết bị sắp hết pin, đang ở chế độ ngủ hoặc không có kết nối mạng. Điều này sẽ ảnh hưởng đến pin và hiệu suất của thiết bị, đồng thời có thể khiến người dùng có trải nghiệm không tốt.

Trong nhiệm vụ tiếp theo, bạn sẽ giải quyết vấn đề này bằng cách thêm các ràng buộc.

Trong nhiệm vụ trước, bạn đã dùng WorkManager để lên lịch cho một yêu cầu thực hiện công việc. Trong nhiệm vụ này, bạn sẽ thêm tiêu chí về thời điểm thực thi công việc.

Khi xác định WorkRequest, bạn có thể chỉ định các quy tắc ràng buộc về thời điểm Worker sẽ chạy. Ví dụ: bạn có thể muốn chỉ định rằng tác vụ sẽ chỉ chạy khi thiết bị ở trạng thái rảnh, hoặc chỉ khi thiết bị được cắm nguồn và kết nối Wi-Fi. Bạn cũng có thể chỉ định chính sách đợi để thử lại công việc. Các ràng buộc được hỗ trợ là các phương thức được đặt trong Constraints.Builder. Để tìm hiểu thêm, hãy xem phần Xác định yêu cầu công việc.

Bước 1: Thêm một đối tượng Constraints và đặt một điều kiện ràng buộc

Trong bước này, bạn sẽ tạo một đối tượng Constraints và đặt một điều kiện ràng buộc cho đối tượng đó, đó là điều kiện ràng buộc về loại mạng. (Bạn sẽ dễ dàng nhận thấy các nhật ký chỉ có một ràng buộc. Trong bước sau, bạn sẽ thêm các ràng buộc khác.)

  1. Trong lớp DevByteApplication, ở đầu setupRecurringWork(), hãy xác định một val thuộc loại Constraints. Sử dụng phương thức Constraints.Builder().
val constraints = Constraints.Builder()

Để giải quyết lỗi này, hãy nhập androidx.work.Constraints.

  1. Sử dụng phương thức setRequiredNetworkType() để thêm một ràng buộc về loại mạng vào đối tượng constraints. Sử dụng enum UNMETERED để yêu cầu công việc chỉ chạy khi thiết bị đang ở trên mạng không đo lượng dữ liệu.
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. Sử dụng phương thức build() để tạo các quy tắc ràng buộc từ trình tạo.
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

Bây giờ, bạn cần đặt đối tượng Constraints mới tạo thành yêu cầu công việc.

  1. Trong lớp DevByteApplication, bên trong phương thức setupRecurringWork(), hãy đặt đối tượng Constraints thành yêu cầu công việc định kỳ, repeatingRequest. Để đặt các điều kiện ràng buộc, hãy thêm phương thức setConstraints() phía trên lệnh gọi phương thức build().
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

Bước 2: Chạy ứng dụng và xem nhật ký

Ở bước này, bạn chạy ứng dụng và nhận thấy yêu cầu công việc bị hạn chế đang chạy ở chế độ nền theo định kỳ.

  1. Gỡ cài đặt ứng dụng khỏi thiết bị hoặc trình mô phỏng để huỷ mọi tác vụ đã lên lịch trước đó.
  2. Mở ngăn Logcat trong Android Studio. Trong ngăn Logcat, hãy xoá nhật ký trước đó bằng cách nhấp vào biểu tượng Xoá logcat ở bên trái. Lọc trên work.
  3. Tắt Wi-Fi trong thiết bị hoặc trình mô phỏng để xem cách hoạt động của các điều kiện ràng buộc. Mã hiện tại chỉ đặt một điều kiện ràng buộc, cho biết rằng yêu cầu chỉ được chạy trên một mạng không giới hạn. Vì Wi-Fi đang tắt nên thiết bị không kết nối với mạng, dù là mạng đo lượng hay không đo lượng. Do đó, điều kiện ràng buộc này sẽ không được đáp ứng.
  4. Chạy ứng dụng và chú ý đến ngăn Logcat. WorkManager sẽ lên lịch cho tác vụ ở chế độ nền ngay lập tức. Vì không đáp ứng được hạn chế về mạng, nên tác vụ không chạy.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. Bật Wi-Fi trong thiết bị hoặc trình mô phỏng và theo dõi ngăn Logcat. Giờ đây, tác vụ ở chế độ nền theo lịch sẽ chạy khoảng 15 phút một lần, miễn là đáp ứng được điều kiện ràng buộc về mạng.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

Bước 3: Thêm các ràng buộc khác

Trong bước này, bạn sẽ thêm các điều kiện ràng buộc sau vào PeriodicWorkRequest:

  • Pin không yếu.
  • Thiết bị đang sạc.
  • Thiết bị ở trạng thái chờ; chỉ có ở API cấp 23 (Android M) trở lên.

Triển khai những nội dung sau trong lớp DevByteApplication.

  1. Trong lớp DevByteApplication, bên trong phương thức setupRecurringWork(), hãy cho biết rằng yêu cầu công việc chỉ chạy nếu pin không yếu. Thêm điều kiện ràng buộc trước lệnh gọi phương thức build() và sử dụng phương thức setRequiresBatteryNotLow().
.setRequiresBatteryNotLow(true)
  1. Cập nhật yêu cầu công việc để yêu cầu này chỉ chạy khi thiết bị đang sạc. Thêm điều kiện ràng buộc trước lệnh gọi phương thức build() và sử dụng phương thức setRequiresCharging().
.setRequiresCharging(true)
  1. Cập nhật yêu cầu công việc để yêu cầu này chỉ chạy khi thiết bị ở trạng thái rảnh. Thêm điều kiện ràng buộc trước lệnh gọi phương thức build() và sử dụng phương thức setRequiresDeviceIdle(). Quy tắc ràng buộc này chỉ chạy yêu cầu công việc khi người dùng không chủ động sử dụng thiết bị. Tính năng này chỉ có trong Android 6.0 (Marshmallow) trở lên, vì vậy, hãy thêm một điều kiện cho phiên bản SDK M trở lên.
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

Sau đây là định nghĩa đầy đủ về đối tượng constraints.

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. Bên trong phương thức setupRecurringWork(), hãy thay đổi khoảng thời gian yêu cầu trở lại một lần mỗi ngày.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

Sau đây là cách triển khai hoàn chỉnh phương thức setupRecurringWork(), có nhật ký để bạn có thể theo dõi thời điểm yêu cầu công việc định kỳ được lên lịch.

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. Để xoá yêu cầu công việc đã lên lịch trước đó, hãy gỡ cài đặt ứng dụng DevBytes khỏi thiết bị hoặc trình mô phỏng của bạn.
  2. Chạy ứng dụng và WorkManager sẽ lên lịch ngay cho yêu cầu công việc. Yêu cầu công việc chạy một lần mỗi ngày, khi đáp ứng tất cả các điều kiện ràng buộc.
  3. Yêu cầu này sẽ chạy ở chế độ nền miễn là ứng dụng được cài đặt, ngay cả khi ứng dụng không chạy. Vì lý do đó, bạn nên gỡ cài đặt ứng dụng khỏi điện thoại.

Tuyệt vời! Bạn đã triển khai và lên lịch một yêu cầu tác vụ tiết kiệm pin để tìm nạp trước video hằng ngày trong ứng dụng DevBytes. WorkManager sẽ lên lịch và chạy tác vụ, đồng thời tối ưu hoá tài nguyên hệ thống. Người dùng và pin của họ sẽ rất hài lòng.

Dự án Android Studio: DevBytesWorkManager.

  • API WorkManager giúp bạn dễ dàng lên lịch các tác vụ không đồng bộ, có thể trì hoãn và phải được chạy một cách đáng tin cậy.
  • Hầu hết các ứng dụng trong thực tế đều cần thực hiện các tác vụ trong nền dài hạn. Để lên lịch một tác vụ ở chế độ nền theo cách tối ưu hoá và hiệu quả, hãy sử dụng WorkManager.
  • Các lớp chính trong thư viện WorkManagerWorker, WorkRequestWorkManager.
  • Lớp Worker biểu thị một đơn vị công việc. Để triển khai tác vụ trong nền, hãy mở rộng lớp Worker và ghi đè phương thức doWork().
  • Lớp WorkRequest biểu thị một yêu cầu thực hiện một đơn vị công việc. WorkRequest là lớp cơ sở để chỉ định các tham số cho công việc mà bạn lên lịch trong WorkManager.
  • Có 2 cách triển khai cụ thể của lớp WorkRequest: OneTimeWorkRequest cho các tác vụ một lần và PeriodicWorkRequest cho các yêu cầu công việc định kỳ.
  • Khi xác định WorkRequest, bạn có thể chỉ định Constraints cho biết thời điểm Worker sẽ chạy. Các điều kiện ràng buộc bao gồm những yếu tố như thiết bị có được cắm nguồn hay không, thiết bị có ở trạng thái rảnh hay không hoặc thiết bị có kết nối Wi-Fi hay không.
  • Để thêm các điều kiện ràng buộc vào WorkRequest, hãy sử dụng các phương thức đặt được liệt kê trong tài liệu về Constraints.Builder. Ví dụ: để cho biết rằng WorkRequest không được chạy nếu pin thiết bị yếu, hãy sử dụng phương thức đặt setRequiresBatteryNotLow().
  • Sau khi bạn xác định WorkRequest, hãy chuyển giao tác vụ cho hệ thống Android. Để thực hiện việc này, hãy lên lịch cho tác vụ bằng một trong các phương thức WorkManager enqueue.
  • Thời gian chính xác mà Worker được thực thi phụ thuộc vào các điều kiện ràng buộc được dùng trong WorkRequest, đồng thời phụ thuộc vào các hoạt động tối ưu hoá của hệ thống. WorkManager được thiết kế để mang lại hành vi tốt nhất có thể trong các hạn chế này.

Khoá học của Udacity:

Tài liệu dành cho nhà phát triển Android:

Khác:

Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:

  • Giao bài tập về nhà nếu cần.
  • Trao đổi với học viên về cách nộp bài tập về nhà.
  • Chấm điểm bài tập về nhà.

Người hướng dẫn có thể sử dụng các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.

Nếu bạn đang tự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.

Câu hỏi 1

Đâu là cách triển khai cụ thể của lớp WorkRequest?

OneTimeWorkPeriodicRequest

OneTimeWorkRequestPeriodicWorkRequest

OneTimeWorkRequestRecurringWorkRequest

OneTimeOffWorkRequestRecurringWorkRequest

Câu hỏi 2

WorkManager dùng lớp nào sau đây để lên lịch tác vụ trong nền trên API cấp độ 23 trở lên?

▢ Chỉ JobScheduler

BroadcastReceiverAlarmManager

AlarmManagerJobScheduler

SchedulerBroadcastReceiver

Câu hỏi 3

Bạn sử dụng API nào để thêm các điều kiện ràng buộc vào WorkRequest?

setConstraints()

addConstraints()

setConstraint()

addConstraintsToWorkRequest()

Chuyển sang bài học tiếp theo: 10.1 Kiểu và giao diện

Để biết đường liên kết đến các lớp học lập trình khác trong khoá học này, hãy xem trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.