Bài 32: AlarmManager trong Android - Lập trình Android cơ bản

Đăng bởi: Admin | Lượt xem: 4682 | Chuyên mục: Android


Giới thiệu

AlarmManager trong Android là một cầu nối giữa ứng dụng và alarm service của hệ thống Android. Nó có thể gửi một bản tin broadcast tới ứng dụng của bạn ở thời điểm đã được lên lịch trước đó. Sau đó ứng dụng của bạn có thể thực hiện bất kỳ tác vụ nào cần thiết ở thời điểm đó.
Để dễ hình khi nói tới AlarmManager, chúng ta nghĩ ngay tới ứng dụng báo thức. Đây chính là ví dụ điển hình, bạn đặt một lịch đến đúng 8h sáng thì chuông reo để còn đi làm. Dù ứng dụng của bạn đã bị bạn tắt, kill các kiểu nhưng đúng 8h sáng là nó lại “chồi lên” kêu ầm ĩ 🙂
Cùng với sự trợ giúp của PendingIntent và  Intent, một bundle đóng gói các thông tin cần thiết sẽ được gửi cùng broadcast hệ thống tới ứng dụng của bạn khi thời gian đặt lịch đến.
Tuy nhiên, từ phiên bản Android M thì Google đã có sự thay đổi với AlarmManager để hạn chế việc tiêu thụ pin quá mức. Alarm sẽ không còn được ưu tiên bật chính xác vào thời điểm đã đặt, mà tùy thuộc vào tình hình tài nguyên máy thời điểm đó mà thời gian bật alarm sẽ có sai lệch chút ít.
Có một vấn đề nhức đầu phổ biến của các nhà phát triển ứng dụng, đó là AlarmManager không hề lưu lại các lịch đã đặt sau khi thiết bị khởi động lại. Do vậy, việc quản lý các schedule sẽ cần phải làm thủ công bởi chính ứng dụng của bạn.
Chúng ta sẽ cùng nhau tìm hiểu kĩ hơn về AlarmManager Android trong bài viết này nhé.

1. Thiết lập AlarmManager Android trong ứng dụng

Có 2 bước để khởi tạo AlarmManager trong Android, đó là:
  1. Tạo một BroadcastReceiver để nhận các bản tin broadcast từ hệ thống, tất nhiên bạn cần phải đăng ký trong xml
  2. Đăng ký alarm với Alarm hệ thống ALARM_SERVICE thông qua PendingIntent kèm thời gian đặt trước.
Mình sẽ hướng dẫn các bạn thực hiện từng bước một.
Bước 1: Tạo BroadcastReceiver để nhận broadcast từ hệ thống
Đầu tiên, bạn tạo một class kế thừa từ BroadcastReceiver
public class AlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
       // For our recurring task, we'll just display a message
        Toast.makeText(context, "I'm running", Toast.LENGTH_SHORT).show();
    }
}
Sau đó đăng ký receiver trong AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest>
  <application>
    <receiver
        android:name=".AlarmReceiver"
        android:enabled="true"
        android:exported="true">
    </receiver>
  </application>
</manifest>
BroadcastReceiver là class để nhận và xử lý các bản tin broadcast được gửi tới ứng dụng của bạn. Có một hàm quan trọng nhất mà bạn phải overridden là onReceive(). Đây chính là hàm sẽ được gọi khi có broadcast gửi tới . Bạn có thể bóc tách dữ liệu từ bundle bằng các hàm getXXXExtra()
Ngoài ra, bạn cũng nên đặc biệt chú ý kiểm tra trường action trong intent, để đảm bảo nhận đúng broadcast mà bạn đã assign lúc lên lịch.
Bước 2: Tạo alarm với thời gian đặt trước
Dưới đây là đoạn code ví dụ để thiết lập trình schedule sẽ alarm sau 10s:
// Get AlarmManager instance
 alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
// Intent part
 intent = Intent(this, AlarmReceiver::class.java)
intent.action = "FOO_ACTION"
intent.putExtra("KEY_FOO_STRING", "Medium AlarmManager Demo")
 pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
// Alarm time
 ALARM_DELAY_IN_SECOND = 10
 alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000L
// Set with system Alarm Service
// Other possible functions: setExact() / setRepeating() / setWindow(), etc
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, pendingIntent)
Trong đoạn code trên chắc cũng không có gì khó hiểu lắm. Việc tạo alarm về cơ bản chỉ có vậy. Mình sẽ giải thích kỹ hơn về PendingIntent, thành phần quan trọng trong việc tạo Alarm.

2. Tìm hiểu PendingIntent Android

Như trong đoạn code trên, chúng ta có sử dụng đến PendingIntent:
pendingIntentRequestCode = 0
 flag = 0
 pendingIntent = PendingIntent.getBroadcast(this, pendingIntentRequestCode, intent, flag)
Tuy nhiên, với alarm chúng ta còn dùng đến PendingIntent nữa. Đây là loại Intent đặc biệt, thay vì tác vụ đích cần thực hiện ngay Intent khi vừa gửi thì với PendingIntent, tác vụ sẽ thực hiện sau một khoảng thời gian nào đó. Bạn có thể hiểu nôm na như vậy.
Có tổng cộng 4 cách để tạo một PendingIntent, nhưng thường ứng dụng bạn chỉ sử dụng cách thứ 1.
  1. PendingIntent.getBroadcast() — Applicable to AlarmManager
  2. PendingIntent.getActivity()
  3. PendingIntent.getActivities()
  4. PendingIntent.getService()
Như đã đề cập ở trên, AlarmManager sẽ gửi một broadcast tới BroadcastReceiver (trong ví dụ bài viết này thì là class AlarmReceiver). Theo như tài liệu chính thức của Google có mô tả thì chỉ có hàm getBroadcast() là khả dụng cho AlarmManager. Tham khảo t ại đây: Google official documentation — PendingIntent.getBroadcast()
Tham số requestCode được coi là một unique ID để phân biệt các PendingIntent trong cùng một Intent.
Tham số cuối cùng là flag, hiểu nôm na là kiểu của PendingIntent.
  1. FLAG_UPDATE_CURRENT
  2. FLAG_CANCEL_CURRENT
  3. FLAG_IMMUTABLE

3. Xử lý sự kiện Alarm

AlarmManager cung cấp 2 cách để lắng nghe một alarm broadcast. Đó là:
  1. Local listener (AlarmManager.OnAlarmListener)
  2. BroadcastReceiver được quy định tại Intent bọc bên trong một PendingIntent
Việc triển khai AlarmManager.OnAlarmListener() tương tự như cách sử dụng PendingIntent. Đơn giản là yêu cầu một callback xử lý sự kiện alarm trong đó.
alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManagerv
 alarmTime = System.currentTimeMillis() + 4000
 tagStr = "TAG"
 handler = null // `null` means the callback will be run at the Main thread
alarmManager.setExact(AlarmManager.RTC_WAKEUP, alarmTime, tagStr, object : AlarmManager.OnAlarmListener {
  override fun onAlarm() {
    Toast.makeText(this, "AlarmManager.OnAlarmListener", Toast.LENGTH_LONG).show()
  }
}, null)
Có một hạn chế với cách sử dụng AlarmManager.OnAlarmListener() đó là nó không thể hoạt động nếu Activity/Fragment tương ứng bị destroy. Ngoài ra, dùng PendingIntent còn có ưu điểm là ứng dụng có thể nhận broadcast alarm ngay cả khi ứng dụng bị kill bởi người dùng trước đó.

4. Những cách thiết lập thời gian schedule cho alarm manager Android

Như mình đã nói ở trên, do Google muốn hạn chế việc tiêu thụ pin quá đà nên việc thiết lập thời gian schedule bởi Alarm sẽ không chính xác.
Ví dụ: bạn thiết lập đúng 8h00 AM thì ứng dụng tự động bật một bài hát. Nhưng thực tế thì có thể 8h02 AM ứng dụng mới nhận broadcast alarm và mới bật được nhạc. Vẫn có những sai số về thời gian.
Bình thường bạn hay dùng tới 2 api:
  1. set() – lên lịch thời gian nhưng cho phép Android linh động thời điểm kích hoạt
  2. setExact()- yêu cầu Android kích hoạt chính xác thời điểm
Tuy nhiên, từ Android M, Google có thêm khái niệm Doze và App Standby. Hai chế độ này sẽ hủy các alarm được lên lịch trước đó khi thiết bị không được cắm sạc.
Khi người dùng tắt màn hình hoặc không cắm sạc, thiết bị sẽ chuyển sang chế độ Doze. Trong chế độ Doze, Android sẽ hạn chế thiết bị truy cập vào mạng và dịch vụ sử dụng nhiều CPU. Do đó, các alarm cũng sẽ bị hủy hoặc bị hoãn cho tới khi thiết bị sẵn sàng.
Android sẽ định kỳ thoát và vào lại chế độ Doze, gọi là khoảng thời gian bảo trì. Trong thời gian bảo trì này, tất cả các hạn chế trước đó sẽ được giải phóng. Tức là các yêu cầu truy cập mạng sẽ được phép, các alarm được kích hoạt (đó chính là lý do tại sao mình nói ở trên là thời gian kích hoạt alarm sẽ bị sai lệch).
Chế độ App Standby cũng tương tự chế độ Doze, chỉ khác là màn hình không bắt buộc phải tắt.
Để khắc phục việc app vào chế độ Doze, bạn có thể sử dụng 2 api bên dưới để tạo alarm.
setAndAllowWhileIdle(...)
setExactAndAllowWhileIdle(...)

5. Cách hủy một AlarmManager trong Android

Để hủy một Alarm, đơn giản là bạn gọi hàm cancel()
onAlarmListener = object : AlarmManager.OnAlarmListener {
  override fun onAlarm() {
     toast("Alarm goes off.")
  }
}
alarmManager.cancel(onAlarmListener)
Tuy nhiên, nếu bạn chỉ muốn hủy chính xác một alarm thì cần phải truyền chính xác requestCode mà bạn đã tạo trước đó.

6. Tổng kết

Android cung cấp một alarm service để thông báo cho ứng dụng Android ở một thời điểm được thiết lập trước. Cùng với sự phát triển của Android, nó đã được thay đổi để điều khiển alarm service tiết kiệm pin hơn.
setExactAndAllowWhileIdle() chỉ nên được sử dụng khi ứng dụng của bạn cần alarm chính xác nhất.
Ngoài ra, bạn cũng cần phải quản lý các AlarmManager trong android một cách thủ công, để có thể đăng ký lại các alarm khi thiết bị khởi động lại.
Mình hi vọng qua bài viết này bạn sẽ hiểu rõ hơn và biết cách sử dụng Alarm trong Android. 
Bài tiếp theo: Permission trong Android >>
vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!