+4

Cách dùng WorkManager lập lịch sau một khoảng thời gian không sử dụng app

Mở đầu

WorkManager là một thư viện Android được cung cấp bởi Google để quản lý công việc nền (background tasks) một cách có tổ chức và hiệu quả. Nó giúp lập lịch và đảm bảo rằng các tác vụ nền được thực thi đúng cách, ngay cả khi ứng dụng bị đóng hoặc thiết bị khởi động lại

image.png

Đặc điểm của WorkManager

Một số đặc điểm chính của WorkManager:

  1. Lập lịch công việc: WorkManager cho phép lập lịch các tác vụ nền để chạy vào một thời điểm nhất định hoặc sau một khoảng thời gian xác định.

  2. Hạn chế tài nguyên: WorkManager tự động điều chỉnh thời gian chạy của công việc dựa trên trạng thái của thiết bị (như pin yếu, kết nối mạng chậm) để tối ưu hóa việc sử dụng tài nguyên.

  3. Đảm bảo công việc được hoàn thành: Ngay cả khi ứng dụng bị đóng hoặc thiết bị khởi động lại, WorkManager sẽ đảm bảo công việc được hoàn thành sau khi điều kiện cho phép.

  4. Tính linh hoạt: WorkManager hỗ trợ nhiều loại công việc khác nhau như OneTimeWorkRequest (chạy một lần), PeriodicWorkRequest (chạy định kỳ), và ChainedWorkRequest (chuỗi các công việc).

  5. Tương thích ngược: WorkManager hoạt động trên tất cả các phiên bản Android từ Android 4.0 (API level 14) trở lên.

  6. Theo dõi trạng thái: Bạn có thể theo dõi trạng thái của các công việc đã lập lịch và nhận thông báo khi chúng hoàn thành hoặc gặp lỗi.

WorkManager là một thành phần quan trọng trong kiến trúc Android modern, giúp xây dựng các ứng dụng có hiệu suất tốt và đáng tin cậy hơn. Nó được khuyến nghị sử dụng thay vì các giải pháp cũ hơn như GCMNetworkManager hay SyncAdapter để quản lý công việc nền.

Cách dùng WorkManager với lập lịch sau một khoảng thời gian không dùng app

Các bước tổng quát như sau:

Để sử dụng WorkManager để lập lịch công việc sau một khoảng thời gian không sử dụng ứng dụng, bạn có thể làm như sau:

  1. Tạo một Worker:

Đầu tiên, bạn cần tạo một lớp kế thừa từ Worker hoặc một trong các lớp con của nó (ví dụ: CoroutineWorker). Lớp Worker này sẽ định nghĩa công việc bạn muốn thực hiện.

class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // Viết code để thực hiện công việc ở đây
        return Result.success()
    }
}
  1. Tạo một WorkRequest: Tiếp theo, bạn cần tạo một WorkRequest để định nghĩa khi nào và làm thế nào để thực thi Worker của bạn.
val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .setInitialDelay(30, TimeUnit.MINUTES) // Thực thi Worker sau 30 phút
    .setConstraints(
        Constraints.Builder()
            .setRequiresDeviceIdle(true) // Chỉ thực thi khi thiết bị đang nhàn rỗi
            .build()
    )
    .build()

Ở ví dụ này, setInitialDelay được sử dụng để đặt thời gian trì hoãn trước khi thực thi Worker, và setConstraints được sử dụng để đặt ràng buộc rằng Worker chỉ được thực thi khi thiết bị đang nhàn rỗi (không có hoạt động của người dùng).

  1. Enqueue WorkRequest: Cuối cùng, bạn enqueue WorkRequest vào WorkManager để lập lịch thực thi.
WorkManager.getInstance(context).enqueue(myWorkRequest)

Bạn có thể điều chỉnh thời gian trì hoãn (setInitialDelay), ràng buộc (setConstraints), và các tham số khác của WorkRequest để phù hợp với yêu cầu của ứng dụng của bạn.

Ở trên mình đã demo qua cách làm tổng quan. Bây giờ mình sẽ đi vào với demo lập lịch thông báo nhé. Mọi người có gì không hiểu hay cần góp ý hãy comment nhé.

Bước 1: Tạo một hàm gửi thông báo

private fun sendNotification() {
        val notificationManager =
            applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "notify_channel",
                "Notification Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }
        val intent =
            applicationContext.packageManager.getLaunchIntentForPackage(applicationContext.packageName)
        intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(
            applicationContext,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(applicationContext, "notify_channel")
            .setContentTitle("We miss you!")
            .setContentText("You haven't used the app for a while. Come back and check out new features!")
            .setSmallIcon(R.drawable.ic_explore_black)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .build()

        notificationManager.notify(1, notification)
    }

Giải thích: Đoạn mã này định nghĩa một hàm sendNotification() để gửi thông báo trong ứng dụng Android. Dưới đây là giải thích ngắn gọn về từng phần của đoạn mã:

  1. Lấy NotificationManager:

    val notificationManager =
        applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    

    Lấy đối tượng NotificationManager từ hệ thống để quản lý thông báo.

  2. Tạo NotificationChannel (với Android 8.0 trở lên):

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            "notify_channel",
            "Notification Channel",
            NotificationManager.IMPORTANCE_DEFAULT
        )
        notificationManager.createNotificationChannel(channel)
    }
    

Nếu phiên bản Android là 8.0 (API 26) trở lên, tạo một NotificationChannel mới với ID "notify_channel", tên "Notification Channel", và mức độ quan trọng mặc định. Kênh này sau đó được đăng ký với NotificationManager.

  1. Tạo Intent và PendingIntent:

    val intent =
        applicationContext.packageManager.getLaunchIntentForPackage(applicationContext.packageName)
    intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    val pendingIntent = PendingIntent.getActivity(
        applicationContext,
        0,
        intent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
    

    Tạo một Intent để mở lại ứng dụng khi người dùng nhấp vào thông báo. PendingIntent là một PendingIntent để đợi thực thi Intent này.

  2. Tạo và cấu hình Notification:

    val notification = NotificationCompat.Builder(applicationContext, "notify_channel")
        .setContentTitle("We miss you!")
        .setContentText("You haven't used the app for a while. Come back and check out new features!")
        .setSmallIcon(R.drawable.ic_explore_black)
        .setContentIntent(pendingIntent)
        .setAutoCancel(true)
        .build()
    

    Sử dụng NotificationCompat.Builder để tạo một thông báo mới với tiêu đề, nội dung, biểu tượng nhỏ, và PendingIntent. setAutoCancel(true) để tự động hủy thông báo khi người dùng nhấp vào nó.

  3. Gửi Notification:

    notificationManager.notify(1, notification)
    

    Sử dụng NotificationManager để gửi thông báo với ID là 1.

Bước 2: Tạo một công việc( Worker)

  override fun doWork(): Result {
        sendNotification();
        return Result.success()
    }
    ```
    
    Đoạn mã này định nghĩa một phương thức `doWork()` được override từ một lớp cha (có thể là `Worker` hoặc `CoroutineWorker`) trong thư viện WorkManager của Android. Mục đích của phương thức này là thực hiện công việc ngầm (background work). Dưới đây là giải thích sơ qua về đoạn mã:

```kotlin
override fun doWork(): Result {
    sendNotification()
    return Result.success()
}

Giải thích:

  1. Override phương thức doWork():

    override fun doWork(): Result
    

    Phương thức doWork() được override từ lớp cha. Đây là nơi bạn định nghĩa công việc cần thực hiện trong background. doWork() là một phương thức đồng bộ, nên nó sẽ chạy trên một luồng (thread) riêng biệt không phải là luồng giao diện chính (main thread).

  2. Gọi hàm sendNotification():

    sendNotification()
    

    Bên trong doWork(), phương thức sendNotification() được gọi. Phương thức này sẽ tạo và gửi một thông báo tới người dùng như đã giải thích ở câu trả lời trước.

  3. Trả về kết quả công việc:

    return Result.success()
    

    Sau khi hoàn thành công việc (gửi thông báo), phương thức trả về Result.success(), chỉ ra rằng công việc đã hoàn thành thành công. WorkManager sử dụng kết quả này để xác định trạng thái của công việc và có thể lên kế hoạch cho các công việc tiếp theo nếu cần thiết.

Bước 3: Gọi Worker vào trong MainActivity

Đầu tiên tạo một hàm để hiển thị thông báo sau một khoảng thời gian 10s, 20s, 40s:

    private fun scheduleNotification() {
        val tenSecondDelay = OneTimeWorkRequestBuilder<OneDayWorker>()
            .setInitialDelay(10, TimeUnit.SECONDS)
            .build()

        val twentySecondDelay = OneTimeWorkRequestBuilder<OneDayWorker>()
            .setInitialDelay(20, TimeUnit.SECONDS)
            .build()

        val fortySecondDelay = OneTimeWorkRequestBuilder<OneDayWorker>()
            .setInitialDelay(40, TimeUnit.SECONDS)
            .build()
        WorkManager.getInstance(this)
            .beginWith(tenSecondDelay)
            .then(twentySecondDelay)
            .then(fortySecondDelay)
            .enqueue()
    }

Giải thích

Đoạn mã này định nghĩa một hàm scheduleNotification() để lên lịch gửi thông báo trong ứng dụng Android bằng cách sử dụng WorkManager. Dưới đây là giải thích chi tiết về từng phần của đoạn mã:

  1. Tạo các yêu cầu công việc với thời gian trễ khác nhau:

    val tenSecondDelay = OneTimeWorkRequestBuilder<OneDayWorker>()
        .setInitialDelay(10, TimeUnit.SECONDS)
        .build()
    

    Tạo một OneTimeWorkRequest để lên lịch công việc OneDayWorker chạy sau 10 giây. OneTimeWorkRequestBuilder được sử dụng để xây dựng yêu cầu công việc một lần với thời gian trễ ban đầu là 10 giây.

    val twentySecondDelay = OneTimeWorkRequestBuilder<OneDayWorker>()
        .setInitialDelay(20, TimeUnit.SECONDS)
        .build()
    

    Tạo một OneTimeWorkRequest thứ hai để lên lịch công việc OneDayWorker chạy sau 20 giây.

    val fortySecondDelay = OneTimeWorkRequestBuilder<OneDayWorker>()
        .setInitialDelay(40, TimeUnit.SECONDS)
        .build()
    

    Tạo một OneTimeWorkRequest thứ ba để lên lịch công việc OneDayWorker chạy sau 40 giây.

  2. Chuỗi các công việc theo thứ tự:

    WorkManager.getInstance(this)
        .beginWith(tenSecondDelay)
        .then(twentySecondDelay)
        .then(fortySecondDelay)
        .enqueue()
    

    Sử dụng WorkManager để chuỗi các công việc theo thứ tự:

    • beginWith(tenSecondDelay) bắt đầu chuỗi với công việc đầu tiên (tenSecondDelay).
    • then(twentySecondDelay) chỉ định rằng công việc twentySecondDelay sẽ chạy sau khi công việc đầu tiên hoàn thành.
    • then(fortySecondDelay) chỉ định rằng công việc fortySecondDelay sẽ chạy sau khi công việc thứ hai hoàn thành.

    Cuối cùng, enqueue() được gọi để thêm chuỗi công việc này vào hàng đợi của WorkManager và bắt đầu quá trình thực thi.

Hàm scheduleNotification() lên lịch ba công việc OneDayWorker với các thời gian trễ khác nhau (10 giây, 20 giây, và 40 giây). Các công việc này được thực hiện tuần tự, tức là mỗi công việc sẽ chỉ bắt đầu sau khi công việc trước đó hoàn thành. WorkManager quản lý việc xếp hàng và thực thi các công việc này.

Bước 4: Check permisson và gửi thông báo

Check permission đối với các version Android cao hơn như 13, 14 thì cần phải check quyền hiển thị thông báo.

private fun checkPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            requestPermissions(
                arrayOf(Manifest.permission.POST_NOTIFICATIONS),
                PERMISSION_REQUEST_CODE
            )
        } else {
            if (ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                showPermissionBottomSheet("Allow the app to access notifications on your device?") { dialog, which ->
                    this.openSettings()
                }
            } else {
                scheduleNotification()
            }
        }
    }

Giải thích

Hàm checkPermission() kiểm tra và yêu cầu quyền gửi thông báo trên thiết bị Android:

  • Nếu phiên bản Android là 13 trở lên, trực tiếp yêu cầu quyền POST_NOTIFICATIONS.
  • Nếu phiên bản Android thấp hơn, kiểm tra xem quyền đã được cấp chưa.
  • Nếu chưa, hiển thị một thông báo yêu cầu người dùng cấp quyền.
  • Nếu đã được cấp, tiến hành lên lịch gửi thông báo.
  • Đoạn mã này đảm bảo rằng ứng dụng có quyền cần thiết để gửi thông báo trước khi thực hiện hành động này.

Tiếp theo cần gọi 2 hàm là onResum và onPause;

 override fun onResume() {
        super.onResume()
        WorkManager.getInstance(this).cancelAllWork()
    }

    override fun onPause() {
        super.onPause()
        scheduleNotification()
    }

Giải thích:

Đoạn mã này định nghĩa hai phương thức onResume()onPause() trong một Activity hoặc Fragment của ứng dụng Android. Dưới đây là giải thích chi tiết về từng phương thức:

  1. Phương thức onResume():

    override fun onResume() {
        super.onResume()
        WorkManager.getInstance(this).cancelAllWork()
    }
    
    • override fun onResume(): Đây là phương thức được gọi khi Activity hoặc Fragment trở lại trạng thái hoạt động (active), tức là khi nó bắt đầu tương tác với người dùng.
    • super.onResume(): Gọi phương thức onResume() của lớp cha để đảm bảo rằng bất kỳ hành động nào được định nghĩa trong lớp cha cũng được thực hiện.
    • WorkManager.getInstance(this).cancelAllWork(): Hủy tất cả các công việc đã được lên lịch với WorkManager. Điều này đảm bảo rằng bất kỳ công việc nền nào đã lên lịch sẽ không tiếp tục khi Activity hoặc Fragment đang hoạt động.
  2. Phương thức onPause():

    override fun onPause() {
        super.onPause()
        scheduleNotification()
    }
    

Giải thích

  • onResume(): Khi Activity hoặc Fragment trở lại trạng thái hoạt động, tất cả các công việc đã được lên lịch với WorkManager sẽ bị hủy. Điều này ngăn chặn việc gửi thông báo khi người dùng đang sử dụng ứng dụng.
  • onPause(): Khi Activity hoặc Fragment sắp chuyển sang trạng thái không hoạt động, các công việc gửi thông báo sẽ được lên lịch. Điều này đảm bảo rằng người dùng sẽ nhận được thông báo khi họ không sử dụng ứng dụng.

Mục đích của đoạn mã này là quản lý việc gửi thông báo dựa trên trạng thái hoạt động của Activity hoặc Fragment, để tránh việc gửi thông báo không cần thiết khi ứng dụng đang được sử dụng.

Ảnh kết quả sau 10s khi app không hoạt động:

image.png

Kết

Với WorkManager, các nhà phát triển không cần phải lo lắng quá nhiều về việc quản lý chu kỳ hoạt động của ứng dụng hoặc tối ưu hóa pin, mà có thể tập trung vào logic nghiệp vụ chính. WorkManager giúp giảm thiểu rủi ro về hiệu năng và pin cho ứng dụng.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.