Lên lịch cho chuông báo

Báo thức (dựa trên lớp AlarmManager) cho phép bạn thực hiện các thao tác dựa trên thời gian bên ngoài vòng đời của ứng dụng. Ví dụ: bạn có thể dùng chuông báo để bắt đầu một thao tác diễn ra trong thời gian dài, chẳng hạn như bắt đầu một dịch vụ mỗi ngày một lần để tải thông tin dự báo thời tiết xuống.

Chuông báo có những đặc điểm sau:

  • Chúng cho phép bạn kích hoạt Intent vào thời gian và/hoặc khoảng thời gian đã đặt.

  • Bạn có thể sử dụng chúng cùng với broadcast receiver để lên lịch job hoặc WorkRequest nhằm thực hiện các thao tác khác.

  • Các dịch vụ này hoạt động bên ngoài ứng dụng của bạn, vì vậy bạn có thể dùng chúng để kích hoạt các sự kiện hoặc thao tác ngay cả khi ứng dụng không chạy và ngay cả khi chính thiết bị đang ở chế độ ngủ.

  • Các thành phần này giúp bạn giảm thiểu các yêu cầu về tài nguyên của ứng dụng. Bạn có thể lên lịch các thao tác mà không cần dựa vào đồng hồ hẹn giờ hoặc các dịch vụ chạy liên tục.

Đặt chuông báo không chính xác

Khi một ứng dụng đặt chuông báo không chính xác, hệ thống sẽ gửi chuông báo vào một thời điểm nào đó trong tương lai. Chuông báo không chính xác đảm bảo một số điều về thời gian gửi chuông báo trong khi vẫn tuân thủ các hạn chế tiết kiệm pin, chẳng hạn như chế độ Doze.

Nhà phát triển có thể tận dụng các đảm bảo API sau đây để tuỳ chỉnh thời gian phân phối báo thức không chính xác.

Đưa ra chuông báo sau một khoảng thời gian cụ thể

Nếu ứng dụng của bạn gọi set(), setInexactRepeating() hoặc setAndAllowWhileIdle(), thì chuông báo sẽ không bao giờ kêu trước thời gian kích hoạt được cung cấp.

Trên Android 12 (API cấp 31) trở lên, hệ thống sẽ gọi báo thức trong vòng một giờ kể từ thời gian kích hoạt được cung cấp, trừ phi có bất kỳ hạn chế nào về việc tiết kiệm pin có hiệu lực, chẳng hạn như trình tiết kiệm pin hoặc Doze.

Đưa ra chuông báo trong một khoảng thời gian

Nếu ứng dụng của bạn gọi setWindow(), thì chuông báo sẽ không bao giờ đổ chuông trước thời gian kích hoạt đã cung cấp. Trừ phi có bất kỳ quy định hạn chế nào về việc tiết kiệm pin, nếu không, chuông báo sẽ được gửi trong khoảng thời gian đã chỉ định, bắt đầu từ thời gian kích hoạt đã cho.

Nếu ứng dụng của bạn nhắm đến Android 12 trở lên, thì hệ thống có thể trì hoãn việc gọi một báo thức không chính xác theo khung thời gian ít nhất 10 phút. Vì lý do này, các giá trị tham số windowLengthMillis trong 600000 sẽ bị cắt thành 600000.

Đưa ra chuông báo lặp lại theo định kỳ

Nếu ứng dụng của bạn gọi setInexactRepeating(), hệ thống sẽ gọi nhiều báo thức:

  1. Chuông báo đầu tiên sẽ đổ chuông trong khoảng thời gian đã chỉ định, bắt đầu từ thời gian kích hoạt đã cho.
  2. Các chuông báo tiếp theo thường sẽ đổ chuông sau khi khoảng thời gian đã chỉ định trôi qua. Thời gian giữa hai lần gọi liên tiếp của chuông báo có thể khác nhau.

Đặt chuông báo chính xác

Hệ thống sẽ gọi một chuông báo chính xác vào một thời điểm chính xác trong tương lai.

Hầu hết các ứng dụng đều có thể lên lịch cho các tác vụ và sự kiện bằng cách sử dụng thông báo không chính xác để hoàn tất một số trường hợp sử dụng phổ biến. Nếu chức năng cốt lõi của ứng dụng phụ thuộc vào một thông báo được tính giờ chính xác (chẳng hạn như ứng dụng đồng hồ báo thức hoặc ứng dụng lịch), thì bạn có thể sử dụng thông báo chính xác.

Các trường hợp sử dụng có thể không cần đến chuông báo chính xác

Danh sách sau đây cho thấy những quy trình công việc thường gặp có thể không cần đến chuông báo chính xác:

Lên lịch cho các thao tác định thời trong suốt vòng đời của ứng dụng
Lớp Handler bao gồm một số phương thức phù hợp để xử lý các thao tác định thời gian, chẳng hạn như thực hiện một số thao tác sau mỗi n giây, trong khi ứng dụng của bạn đang hoạt động: postAtTime()postDelayed(). Lưu ý rằng các API này dựa vào thời gian hoạt động của hệ thống chứ không phải thời gian thực.
Thao tác ở chế độ nền đã lên lịch, chẳng hạn như cập nhật ứng dụng và tải nhật ký lên
WorkManager cung cấp cách lập lịch công việc định kỳ có giới hạn thời gian. Bạn có thể cung cấp một khoảng thời gian lặp lại và flexInterval (tối thiểu 15 phút) để xác định thời gian chạy chi tiết cho công việc.
Hành động do người dùng chỉ định sẽ xảy ra sau một thời gian cụ thể (ngay cả khi hệ thống ở trạng thái không hoạt động)
Sử dụng chuông báo không chính xác. Cụ thể, hãy gọi setAndAllowWhileIdle().
Hành động do người dùng chỉ định sẽ xảy ra sau một thời gian cụ thể
Sử dụng chuông báo không chính xác. Cụ thể, hãy gọi set().
Hành động do người dùng chỉ định có thể xảy ra trong một khoảng thời gian cụ thể
Sử dụng chuông báo không chính xác. Cụ thể, hãy gọi setWindow(). Xin lưu ý rằng nếu ứng dụng của bạn nhắm đến Android 12 trở lên, thì thời lượng nhỏ nhất được phép là 10 phút.

Cách đặt thông báo chính xác

Ứng dụng của bạn có thể đặt chuông báo chính xác bằng một trong các phương thức sau. Các phương thức này được sắp xếp sao cho những phương thức gần cuối danh sách sẽ thực hiện nhiều tác vụ quan trọng về thời gian hơn nhưng lại yêu cầu nhiều tài nguyên hệ thống hơn.

setExact()

Gọi một chuông báo vào một thời điểm gần như chính xác trong tương lai, miễn là các biện pháp tiết kiệm pin khác không có hiệu lực.

Sử dụng phương thức này để đặt chuông báo chính xác, trừ phi công việc của ứng dụng có tính chất khẩn cấp về thời gian đối với người dùng.

setExactAndAllowWhileIdle()

Gọi một chuông báo tại một thời điểm gần như chính xác trong tương lai, ngay cả khi các biện pháp tiết kiệm pin đang có hiệu lực.

setAlarmClock()

Bật chuông báo vào một thời điểm chính xác trong tương lai. Vì người dùng có thể thấy rõ các chuông báo này, nên hệ thống không bao giờ điều chỉnh thời gian phân phối của chúng. Hệ thống xác định những chuông báo này là quan trọng nhất và sẽ thoát khỏi chế độ nguồn điện thấp nếu cần để phát chuông báo.

Mức tiêu thụ tài nguyên hệ thống

Khi hệ thống kích hoạt các báo thức chính xác mà ứng dụng của bạn đặt, thiết bị sẽ tiêu tốn rất nhiều tài nguyên, chẳng hạn như thời lượng pin, đặc biệt là nếu thiết bị đang ở chế độ tiết kiệm pin. Hơn nữa, hệ thống không thể dễ dàng xử lý hàng loạt các yêu cầu này để sử dụng tài nguyên hiệu quả hơn.

Bạn nên tạo một báo thức không chính xác bất cứ khi nào có thể. Để thực hiện công việc lâu hơn, hãy lên lịch bằng cách sử dụng WorkManager hoặc JobScheduler từ BroadcastReceiver của chuông báo. Để thực hiện công việc trong khi thiết bị ở trạng thái Nghỉ, hãy tạo một chuông báo không chính xác bằng cách sử dụng setAndAllowWhileIdle() và bắt đầu một công việc từ chuông báo.

Khai báo quyền thông báo chính xác phù hợp

Nếu ứng dụng của bạn nhắm đến Android 12 trở lên, thì bạn phải có quyền truy cập đặc biệt vào ứng dụng "Báo thức và lời nhắc". Để thực hiện việc này, hãy khai báo quyền SCHEDULE_EXACT_ALARM trong tệp kê khai của ứng dụng, như minh hoạ trong đoạn mã sau:

<manifest ...>
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
    <application ...>
        ...
    </application>
</manifest>

Nếu ứng dụng của bạn nhắm đến Android 13 (API cấp 33) trở lên, bạn có thể khai báo quyền SCHEDULE_EXACT_ALARM hoặc quyền USE_EXACT_ALARM.

<manifest ...>
    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
    <application ...>
        ...
    </application>
</manifest>

Mặc dù cả quyền SCHEDULE_EXACT_ALARMUSE_EXACT_ALARM đều báo hiệu các chức năng giống nhau, nhưng chúng được cấp theo cách khác nhau và hỗ trợ các trường hợp sử dụng khác nhau. Ứng dụng của bạn chỉ nên sử dụng chuông báo chính xác và khai báo quyền SCHEDULE_EXACT_ALARM hoặc USE_EXACT_ALARM nếu một chức năng dành cho người dùng trong ứng dụng của bạn yêu cầu các hành động được tính giờ chính xác.

USE_EXACT_ALARM

SCHEDULE_EXACT_ALARM

  • Do người dùng cấp
  • Nhiều trường hợp sử dụng hơn
  • Các ứng dụng nên xác nhận rằng quyền chưa bị thu hồi

Quyền SCHEDULE_EXACT_ALARM không được cấp trước cho các lượt cài đặt mới của ứng dụng nhắm đến Android 13 (API cấp 33) trở lên. Nếu người dùng chuyển dữ liệu ứng dụng sang một thiết bị chạy Android 14 thông qua thao tác sao lưu và khôi phục, thì quyền SCHEDULE_EXACT_ALARM sẽ bị từ chối trên thiết bị mới. Tuy nhiên, nếu một ứng dụng hiện có đã có quyền này, thì ứng dụng đó sẽ được cấp quyền trước khi thiết bị nâng cấp lên Android 14.

Lưu ý: Nếu chuông báo chính xác được thiết lập bằng đối tượng OnAlarmListener, chẳng hạn như với API setExact, thì không yêu cầu quyền SCHEDULE_EXACT_ALARM.

Sử dụng quyền SCHEDULE_EXACT_ALARM

Không giống như USE_EXACT_ALARM, người dùng phải cấp quyền SCHEDULE_EXACT_ALARM. Cả người dùng và hệ thống đều có thể thu hồi quyền SCHEDULE_EXACT_ALARM.

Để kiểm tra xem ứng dụng của bạn có được cấp quyền hay không, hãy gọi canScheduleExactAlarms() trước khi thử đặt chuông báo chính xác. Khi quyền SCHEDULE_EXACT_ALARM bị thu hồi đối với ứng dụng của bạn, ứng dụng sẽ dừng và tất cả chuông báo chính xác trong tương lai đều bị huỷ. Điều này cũng có nghĩa là giá trị do canScheduleExactAlarms() trả về vẫn hợp lệ trong toàn bộ vòng đời của ứng dụng.

Khi quyền SCHEDULE_EXACT_ALARMS được cấp cho ứng dụng của bạn, hệ thống sẽ gửi cho ứng dụng đó thông báo truyền tin ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED. Ứng dụng của bạn phải triển khai một broadcast receiver thực hiện những việc sau:

  1. Xác nhận rằng ứng dụng của bạn vẫn có quyền truy cập đặc biệt vào ứng dụng. Để thực hiện việc này, hãy gọi canScheduleExactAlarms(). Quy trình kiểm tra này giúp bảo vệ ứng dụng của bạn trong trường hợp người dùng cấp quyền cho ứng dụng, sau đó thu hồi quyền đó gần như ngay lập tức.
  2. Lên lịch lại mọi chuông báo chính xác mà ứng dụng của bạn cần, dựa trên trạng thái hiện tại của ứng dụng. Logic này phải tương tự như những gì ứng dụng của bạn làm khi nhận được thông báo truyền tin ACTION_BOOT_COMPLETED.

Yêu cầu người dùng cấp quyền SCHEDULE_EXACT_ALARM

Tuỳ chọn này có tên là &quot;Cho phép đặt chuông báo và lời nhắc&quot;
Hình 1. Trang "Chuông báo và lời nhắc" trong phần quyền truy cập đặc biệt của ứng dụng trong phần cài đặt hệ thống, nơi người dùng có thể cho phép ứng dụng của bạn đặt chuông báo chính xác.

Nếu cần, bạn có thể chuyển người dùng đến màn hình Báo thức và lời nhắc trong phần cài đặt hệ thống, như minh hoạ trong Hình 1. Để thực hiện điều này, vui lòng hoàn thành các bước sau:

  1. Trong giao diện người dùng của ứng dụng, hãy giải thích cho người dùng biết lý do ứng dụng của bạn cần lên lịch báo thức chính xác.
  2. Gọi một ý định bao gồm thao tác theo ý định ACTION_REQUEST_SCHEDULE_EXACT_ALARM.

Đặt chuông báo lặp lại

Báo thức lặp lại cho phép hệ thống thông báo cho ứng dụng của bạn theo lịch định kỳ.

Một chuông báo được thiết kế kém có thể gây hao pin và tạo ra tải trọng đáng kể cho các máy chủ. Vì lý do này, trên Android 4.4 (API cấp 19) trở lên, tất cả các báo thức lặp lại đều là báo thức không chính xác.

Báo thức lặp lại có các đặc điểm sau:

  • Một loại chuông báo. Để thảo luận thêm, hãy xem phần Chọn loại chuông báo.

  • Thời gian kích hoạt. Nếu thời gian kích hoạt mà bạn chỉ định là thời gian trong quá khứ, thì chuông báo sẽ kích hoạt ngay lập tức.

  • Khoảng thời gian giữa các lần đổ chuông. Ví dụ: mỗi ngày một lần, mỗi giờ hoặc mỗi 5 phút.

  • Một ý định đang chờ xử lý sẽ kích hoạt khi chuông báo được kích hoạt. Khi bạn đặt một báo thức thứ hai sử dụng cùng một ý định đang chờ xử lý, báo thức này sẽ thay thế báo thức ban đầu.

Để huỷ một PendingIntent(), hãy truyền FLAG_NO_CREATE đến PendingIntent.getService() để lấy một phiên bản của ý định (nếu có), sau đó truyền ý định đó đến AlarmManager.cancel()

Kotlin

val alarmManager =
    context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
val pendingIntent =
    PendingIntent.getService(context, requestId, intent,
                                PendingIntent.FLAG_NO_CREATE)
if (pendingIntent != null && alarmManager != null) {
  alarmManager.cancel(pendingIntent)
}

Java

AlarmManager alarmManager =
    (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent =
    PendingIntent.getService(context, requestId, intent,
                                PendingIntent.FLAG_NO_CREATE);
if (pendingIntent != null && alarmManager != null) {
  alarmManager.cancel(pendingIntent);
}

Chọn loại chuông báo

Một trong những điều đầu tiên cần cân nhắc khi sử dụng chuông báo lặp lại là loại chuông báo.

Có 2 loại đồng hồ chung cho báo thức: "thời gian thực đã trôi qua" và "đồng hồ thời gian thực" (RTC). Thời gian thực đã trôi qua sử dụng "thời gian kể từ khi hệ thống khởi động" làm thông tin tham chiếu và đồng hồ thời gian thực sử dụng thời gian UTC (đồng hồ báo). Điều này có nghĩa là thời gian thực đã trôi qua phù hợp với việc đặt báo thức dựa trên thời gian đã trôi qua (ví dụ: báo thức kích hoạt sau mỗi 30 giây) vì thời gian này không bị ảnh hưởng bởi múi giờ hoặc ngôn ngữ. Loại đồng hồ thời gian thực phù hợp hơn cho những báo thức phụ thuộc vào ngôn ngữ hiện tại.

Cả hai loại này đều có phiên bản "đánh thức", tức là đánh thức CPU của thiết bị nếu màn hình đang tắt. Điều này đảm bảo chuông báo sẽ đổ chuông vào thời gian đã lên lịch. Điều này hữu ích nếu ứng dụng của bạn có một phần phụ thuộc về thời gian. Ví dụ: nếu có một khoảng thời gian giới hạn để thực hiện một thao tác cụ thể. Nếu bạn không sử dụng phiên bản đánh thức của loại chuông báo, thì tất cả chuông báo lặp lại sẽ kêu khi thiết bị của bạn thức.

Nếu bạn chỉ cần chuông báo kích hoạt theo một khoảng thời gian cụ thể (ví dụ: sau mỗi nửa giờ), hãy sử dụng một trong các loại thời gian thực đã trôi qua. Nhìn chung, đây là lựa chọn tốt hơn.

Nếu bạn cần chuông báo đổ chuông vào một thời điểm cụ thể trong ngày, hãy chọn một trong các loại đồng hồ thời gian thực dựa trên đồng hồ. Tuy nhiên, xin lưu ý rằng phương pháp này có thể có một số nhược điểm. Ứng dụng có thể không dịch tốt sang các ngôn ngữ khác và nếu người dùng thay đổi chế độ cài đặt thời gian của thiết bị, thì điều này có thể gây ra hành vi không mong muốn trong ứng dụng của bạn. Việc sử dụng loại báo thức đồng hồ thời gian thực cũng không mở rộng tốt, như đã thảo luận ở trên. Bạn nên sử dụng chuông báo "thời gian thực đã trôi qua" nếu có thể.

Dưới đây là danh sách các loại:

  • ELAPSED_REALTIME: Kích hoạt ý định đang chờ xử lý dựa trên khoảng thời gian kể từ khi thiết bị khởi động, nhưng không đánh thức thiết bị. Thời gian đã trôi qua bao gồm cả thời gian thiết bị ở chế độ ngủ.

  • ELAPSED_REALTIME_WAKEUP: Đánh thức thiết bị và kích hoạt ý định đang chờ xử lý sau khi khoảng thời gian đã chỉ định trôi qua kể từ khi thiết bị khởi động.

  • RTC: Kích hoạt ý định đang chờ xử lý tại thời điểm được chỉ định nhưng không đánh thức thiết bị.

  • RTC_WAKEUP: Đánh thức thiết bị để kích hoạt ý định đang chờ xử lý vào thời gian đã chỉ định.

Ví dụ về chuông báo thời gian thực đã trôi qua

Sau đây là một số ví dụ về cách sử dụng ELAPSED_REALTIME_WAKEUP

Đánh thức thiết bị để kích hoạt chuông báo sau 30 phút nữa và sau đó cứ 30 phút một lần:

Kotlin

// Hopefully your alarm will have a lower frequency than this!
alarmMgr?.setInexactRepeating(
        AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
        AlarmManager.INTERVAL_HALF_HOUR,
        alarmIntent
)

Java

// Hopefully your alarm will have a lower frequency than this!
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
        AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);

Đánh thức thiết bị để kích hoạt chuông báo một lần (không lặp lại) sau một phút:

Kotlin

private var alarmMgr: AlarmManager? = null
private lateinit var alarmIntent: PendingIntent
...
alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
    PendingIntent.getBroadcast(context, 0, intent, 0)
}

alarmMgr?.set(
        AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() + 60 * 1000,
        alarmIntent
)

Java

private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() +
        60 * 1000, alarmIntent);

Ví dụ về chuông báo của đồng hồ thời gian thực

Sau đây là một số ví dụ về cách sử dụng RTC_WAKEUP.

Đánh thức thiết bị để kích hoạt chuông báo vào khoảng 2 giờ chiều và lặp lại một lần mỗi ngày vào cùng thời điểm:

Kotlin

// Set the alarm to start at approximately 2:00 p.m.
val calendar: Calendar = Calendar.getInstance().apply {
    timeInMillis = System.currentTimeMillis()
    set(Calendar.HOUR_OF_DAY, 14)
}

// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
alarmMgr?.setInexactRepeating(
        AlarmManager.RTC_WAKEUP,
        calendar.timeInMillis,
        AlarmManager.INTERVAL_DAY,
        alarmIntent
)

Java

// Set the alarm to start at approximately 2:00 p.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 14);

// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        AlarmManager.INTERVAL_DAY, alarmIntent);

Đánh thức thiết bị để đổ chuông báo chính xác lúc 8 giờ 30 phút sáng và cứ sau 20 phút:

Kotlin

private var alarmMgr: AlarmManager? = null
private lateinit var alarmIntent: PendingIntent
...
alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
    PendingIntent.getBroadcast(context, 0, intent, 0)
}

// Set the alarm to start at 8:30 a.m.
val calendar: Calendar = Calendar.getInstance().apply {
    timeInMillis = System.currentTimeMillis()
    set(Calendar.HOUR_OF_DAY, 8)
    set(Calendar.MINUTE, 30)
}

// setRepeating() lets you specify a precise custom interval--in this case,
// 20 minutes.
alarmMgr?.setRepeating(
        AlarmManager.RTC_WAKEUP,
        calendar.timeInMillis,
        1000 * 60 * 20,
        alarmIntent
)

Java

private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

// Set the alarm to start at 8:30 a.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);

// setRepeating() lets you specify a precise custom interval--in this case,
// 20 minutes.
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        1000 * 60 * 20, alarmIntent);

Quyết định độ chính xác cần thiết cho chuông báo

Như đã mô tả trước đó, việc chọn loại chuông báo thường là bước đầu tiên trong quá trình tạo chuông báo. Một điểm khác biệt nữa là độ chính xác mà bạn cần đối với thông báo. Đối với hầu hết các ứng dụng, setInexactRepeating() là lựa chọn phù hợp. Khi bạn sử dụng phương thức này, Android sẽ đồng bộ hoá nhiều báo thức lặp lại không chính xác và kích hoạt chúng cùng một lúc. Điều này giúp giảm mức tiêu hao pin.

Tránh sử dụng chuông báo chính xác nếu có thể. Tuy nhiên, đối với những ứng dụng hiếm gặp có yêu cầu nghiêm ngặt về thời gian, bạn có thể đặt một chuông báo chính xác bằng cách gọi setRepeating().

Với setInexactRepeating(), bạn không thể chỉ định khoảng thời gian tuỳ chỉnh như khi dùng setRepeating(). Bạn phải sử dụng một trong các hằng số khoảng thời gian, chẳng hạn như INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY, v.v. Hãy xem AlarmManager để biết danh sách đầy đủ.

Hủy chuông báo

Tuỳ thuộc vào ứng dụng của mình, bạn có thể muốn thêm khả năng huỷ chuông báo. Để huỷ một chuông báo, hãy gọi cancel() trên Trình quản lý chuông báo, truyền vào PendingIntent mà bạn không muốn kích hoạt nữa. Ví dụ:

Kotlin

// If the alarm has been set, cancel it.
alarmMgr?.cancel(alarmIntent)

Java

// If the alarm has been set, cancel it.
if (alarmMgr!= null) {
    alarmMgr.cancel(alarmIntent);
}

Bắt đầu báo thức khi thiết bị khởi động lại

Theo mặc định, tất cả chuông báo sẽ bị huỷ khi thiết bị tắt. Để ngăn điều này xảy ra, bạn có thể thiết kế ứng dụng của mình để tự động khởi động lại chuông báo lặp lại nếu người dùng khởi động lại thiết bị. Điều này đảm bảo rằng AlarmManager sẽ tiếp tục thực hiện nhiệm vụ mà không cần người dùng khởi động lại báo thức theo cách thủ công.

Sau đây là các bước:

  1. Đặt quyền RECEIVE_BOOT_COMPLETED trong tệp kê khai của ứng dụng. Thao tác này cho phép ứng dụng của bạn nhận ACTION_BOOT_COMPLETED được truyền phát sau khi hệ thống khởi động xong (thao tác này chỉ hoạt động nếu người dùng đã chạy ứng dụng ít nhất một lần):

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. Triển khai BroadcastReceiver để nhận thông báo truyền tin:

    Kotlin

    class SampleBootReceiver : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == "android.intent.action.BOOT_COMPLETED") {
                // Set the alarm here.
            }
        }
    }

    Java

    public class SampleBootReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
                // Set the alarm here.
            }
        }
    }
  3. Thêm receiver vào tệp kê khai của ứng dụng bằng một bộ lọc ý định lọc theo thao tác ACTION_BOOT_COMPLETED:

    <receiver android:name=".SampleBootReceiver"
            android:enabled="false">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"></action>
        </intent-filter>
    </receiver>

    Lưu ý rằng trong tệp kê khai, trình nhận khởi động được đặt thành android:enabled="false". Điều này có nghĩa là receiver sẽ không được gọi trừ phi ứng dụng bật receiver một cách rõ ràng. Điều này ngăn việc gọi trình nhận tín hiệu khởi động một cách không cần thiết. Bạn có thể bật một receiver (ví dụ: nếu người dùng đặt chuông báo) như sau:

    Kotlin

    val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
    context.packageManager.setComponentEnabledSetting(
            receiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP
    )

    Java

    ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
    PackageManager pm = context.getPackageManager();
    
    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
            PackageManager.DONT_KILL_APP);

    Sau khi bạn bật bộ nhận theo cách này, bộ nhận sẽ vẫn bật, ngay cả khi người dùng khởi động lại thiết bị. Nói cách khác, việc bật receiver theo phương thức lập trình sẽ ghi đè chế độ cài đặt tệp kê khai, ngay cả khi khởi động lại. Receiver sẽ vẫn bật cho đến khi ứng dụng của bạn tắt receiver. Bạn có thể tắt một receiver (ví dụ: nếu người dùng huỷ báo thức) như sau:

    Kotlin

    val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
    context.packageManager.setComponentEnabledSetting(
            receiver,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP
    )

    Java

    ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
    PackageManager pm = context.getPackageManager();
    
    pm.setComponentEnabledSetting(receiver,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP);

Gọi báo thức trong khi thiết bị ở chế độ Nghỉ

Các thiết bị chạy Android 6.0 (API cấp 23) có hỗ trợ chế độ Doze. Chế độ này giúp kéo dài thời lượng pin của thiết bị. Chuông báo sẽ không kêu khi thiết bị ở Chế độ nghỉ. Mọi chuông báo đã lên lịch sẽ bị hoãn lại cho đến khi thiết bị thoát khỏi Chế độ nghỉ. Nếu cần hoàn thành công việc ngay cả khi thiết bị ở trạng thái không hoạt động, bạn có thể dùng một số lựa chọn sau:

  • Đặt chuông báo chính xác.

  • Sử dụng WorkManager API được tạo để thực hiện công việc trong nền. Bạn có thể cho biết rằng hệ thống nên đẩy nhanh công việc của bạn để công việc hoàn thành nhanh nhất có thể. Để biết thêm thông tin, hãy xem phần Lên lịch các tác vụ bằng WorkManager

Các phương pháp hay nhất

Mọi lựa chọn bạn đưa ra khi thiết kế chuông báo lặp lại đều có thể gây ra hậu quả trong cách ứng dụng của bạn sử dụng (hoặc sử dụng sai) tài nguyên hệ thống. Ví dụ: hãy tưởng tượng một ứng dụng phổ biến đồng bộ hoá với máy chủ. Nếu thao tác đồng bộ hoá dựa trên thời gian thực và mọi phiên bản của ứng dụng đều đồng bộ hoá lúc 11:00 tối, thì tải trên máy chủ có thể dẫn đến độ trễ cao hoặc thậm chí là "từ chối dịch vụ". Hãy làm theo các phương pháp hay nhất sau đây khi sử dụng báo thức:

  • Thêm tính ngẫu nhiên (độ trễ) vào mọi yêu cầu mạng kích hoạt do kết quả của một báo thức lặp lại:

    • Thực hiện mọi thao tác cục bộ khi chuông báo kích hoạt. "Công việc cục bộ" có nghĩa là mọi thứ không truy cập vào máy chủ hoặc yêu cầu dữ liệu từ máy chủ.

    • Đồng thời, hãy lên lịch cho chuông báo chứa các yêu cầu mạng sẽ kích hoạt vào một khoảng thời gian ngẫu nhiên.

  • Giảm tần suất báo thức xuống mức tối thiểu.

  • Không đánh thức thiết bị khi không cần thiết (hành vi này được xác định theo loại chuông báo, như mô tả trong phần Chọn loại chuông báo).

  • Không đặt thời gian kích hoạt chuông báo chính xác hơn mức cần thiết.

    Sử dụng setInexactRepeating() thay vì setRepeating(). Khi bạn sử dụng setInexactRepeating(), Android sẽ đồng bộ hoá các báo thức lặp lại từ nhiều ứng dụng và kích hoạt chúng cùng một lúc. Điều này làm giảm tổng số lần hệ thống phải đánh thức thiết bị, do đó giảm mức tiêu hao pin. Kể từ Android 4.4 (API cấp 19), tất cả các báo thức lặp lại đều là báo thức không chính xác. Xin lưu ý rằng mặc dù setInexactRepeating() là một điểm cải tiến so với setRepeating(), nhưng nó vẫn có thể làm quá tải máy chủ nếu mọi phiên bản của một ứng dụng đều truy cập vào máy chủ cùng một lúc. Do đó, đối với các yêu cầu mạng, hãy thêm một số yếu tố ngẫu nhiên vào báo thức của bạn, như đã thảo luận trước đó.

  • Tránh đặt chuông báo dựa trên thời gian trên đồng hồ nếu có thể.

    Chuông báo lặp lại dựa trên thời gian kích hoạt chính xác không có khả năng mở rộng tốt. Dùng ELAPSED_REALTIME nếu có thể. Các loại chuông báo khác nhau được mô tả chi tiết hơn trong phần sau.