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,LiveDatavàRoomtrong 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.
- 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.
- Giải nén mã rồi mở dự án trong Android Studio.
- 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 đó.
- 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. 
- Trong Android Studio, hãy mở rộng tất cả các gói.
- 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ằngRoom.
- Khám phá gói repository. Gói này chứa lớpVideosRepositorygiúp tách lớp dữ liệu khỏi phần còn lại của ứng dụng.
- 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 AlarmManager và BroadcastReceiver.
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.
- Mở tệp build.gradle (Module:app)và thêm phần phụ thuộcWorkManagervà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"- Đồ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- Workertrong 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- WorkRequesttrong một nhiệm vụ sau.
- WorkManager
 Lớp này lên lịch và chạy- WorkRequestcủa bạn.- WorkManagerlê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- WorkManagertrong 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.
- Trong gói devbyteviewer, hãy tạo một gói mới có tên làwork.
- Trong gói work, hãy tạo một lớp Kotlin mới có tên làRefreshDataWorker.
- Mở rộng lớp RefreshDataWorkertừ lớpCoroutineWorker. TruyềncontextvàWorkerParametersdưới dạng tham số hàm khởi tạo.
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}- Để giải quyết lỗi lớp trừu tượng, hãy ghi đè phương thức doWork()bên trong lớpRefreshDataWorker.
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.
- Trong lớp RefreshDataWorker, bên trongdoWork(), hãy tạo và khởi tạo một đối tượngVideosDatabasevà một đối tượngVideosRepository.
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)
   return Result.success()
}- Trong lớp RefreshDataWorker, bên trongdoWork(), phía trên câu lệnhreturn, hãy gọi phương thứcrefreshVideos()bên trong khốitry. 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.
- Sau đây là lớp RefreshDataWorkerhoà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 OneTimeWorkRequestdà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 PeriodicWorkRequestdà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.
- 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() {
}- 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ứcPeriodicWorkRequestBuilder(). Truyền vào lớpRefreshDataWorkermà bạn đã tạo trong nhiệm vụ trước. Truyền vào khoảng thời gian lặp lại là1vớ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.
- 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"
}- Trong lớp DevByteApplication, ở cuối phương thứcsetupRecurringWork(), hãy lên lịch cho công việc bằng phương thứcenqueueUniquePeriodicWork(). Truyền enumKEEPcho ExistingPeriodicWorkPolicy. TruyềnrepeatingRequestdướ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.
- Ở đầu lớp DevByteApplication, hãy tạo một đối tượngCoroutineScope. TruyềnDispatchers.Defaultdưới dạng tham số hàm khởi tạo.
private val applicationScope = CoroutineScope(Dispatchers.Default)- 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 {
   }
}- Bên trong phương thức delayedInit(), hãy gọisetupRecurringWork().
- Di chuyển quá trình khởi chạy Timber từ phương thức onCreate()sang phương thứcdelayedInit().
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}- Trong lớp DevByteApplication, ở cuối phương thứconCreate(), hãy thêm một lệnh gọi đến phương thứcdelayedInit().
override fun onCreate() {
   super.onCreate()
   delayedInit()
}- Mở ngăn Logcat ở cuối cửa sổ Android Studio. Lọc trên RefreshDataWorker.
- Chạy ứng dụng. WorkManagersẽ 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.
- Trong lớp DevByteApplication, bên trong phương thứcsetupRecurringWork(), hãy đánh dấu ghi chú vào định nghĩarepeatingRequesthiệ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à15phút.
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()- 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) . .
- Chạy ứng dụng và WorkManagersẽ 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.)
- Trong lớp DevByteApplication, ở đầusetupRecurringWork(), hãy xác định mộtvalthuộc loạiConstraints. Sử dụng phương thứcConstraints.Builder().
val constraints = Constraints.Builder()Để giải quyết lỗi này, hãy nhập androidx.work.Constraints.
- 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ượngconstraints. Sử dụng enumUNMETEREDđể 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)- 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.
- Trong lớp DevByteApplication, bên trong phương thứcsetupRecurringWork(), hãy đặt đối tượngConstraintsthà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ứcsetConstraints()phía trên lệnh gọi phương thứcbuild().
       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ỳ.
- 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 đó.
- 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 ở bên trái. Lọc trênwork.
- 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.
- Chạy ứng dụng và chú ý đến ngăn Logcat. WorkManagersẽ 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
- 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.
- Trong lớp DevByteApplication, bên trong phương thứcsetupRecurringWork(), 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ứcbuild()và sử dụng phương thứcsetRequiresBatteryNotLow().
.setRequiresBatteryNotLow(true)- 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ứcsetRequiresCharging().
.setRequiresCharging(true)- 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ứcsetRequiresDeviceIdle(). 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 SDKMtrở 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()- 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)
   }- Để 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.
- Chạy ứng dụng và WorkManagersẽ 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.
- 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 WorkManagergiú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 WorkManagerlàWorker,WorkRequestvàWorkManager.
- Lớp Workerbiể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ớpWorkervà ghi đè phương thứcdoWork().
- Lớp WorkRequestbiểu thị một yêu cầu thực hiện một đơn vị công việc.WorkRequestlà lớp cơ sở để chỉ định các tham số cho công việc mà bạn lên lịch trongWorkManager.
- Có 2 cách triển khai cụ thể của lớp WorkRequest:OneTimeWorkRequestcho các tác vụ một lần vàPeriodicWorkRequestcho các yêu cầu công việc định kỳ.
- Khi xác định WorkRequest, bạn có thể chỉ địnhConstraintscho biết thời điểmWorkersẽ 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ằngWorkRequestkhông được chạy nếu pin thiết bị yếu, hãy sử dụng phương thức đặtsetRequiresBatteryNotLow().
- 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ứcWorkManagerenqueue.
- 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 trongWorkRequest, đồ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:
- Xác định WorkRequest
- WorkManager
- Bắt đầu sử dụng WorkManager
- Công việc định kỳ
- Hướng dẫn về xử lý ở chế độ nền
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
▢ OneTimeWorkRequest và PeriodicWorkRequest
▢ OneTimeWorkRequest và RecurringWorkRequest
▢ OneTimeOffWorkRequest và RecurringWorkRequest
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
▢ BroadcastReceiver và AlarmManager
▢ AlarmManager và JobScheduler
▢ Scheduler và BroadcastReceiver
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: 
Để 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.