一、問題描述

Android 14 的使用者反映應用程式閃退,經查測後發現是設定指定時間提醒功能時出現閃退。

1
Fatal Exception: java.lang.SecurityException: Caller com.tms.qpass needs to hold android.permission.SCHEDULE_EXACT_ALARM or android.permission.USE_EXACT_ALARM to set exact alarms.

從錯誤訊息可以清楚看到,問題發生當下是拋出 SecurityException

二、錯誤原因說明

其實從錯誤訊息中就可略知一二,一切都跟 SCHEDULE_EXACT_ALARM 還有 USE_EXACT_ALARM 有關。

✍ 如果是日曆或鬧鐘應用程式,應該要宣告的是 USE_EXACT_ALARM 精確鬧鐘權限。

USE_EXACT_ALARM 是一項即將推出的新權限,作用是針對以 Android 13 (API 級別 33) 以上版本為目標的應用程式,授予精確鬧鐘功能的存取權。

USE_EXACT_ALARM 是一項受限制權限,只有當應用程式的核心功能支援精確鬧鐘需求,應用程式才能宣告這項權限。要求這項受限制權限的應用程式必須接受審查,若是應用程式不符合使用限制條件,就禁止在 Google Play 發布 核心功能: - 應用程式是鬧鐘或計時器應用程式。 - 應用程式是會顯示活動通知的日曆應用程式。 上述以外的用途,則應評估能否選擇使用 SCHEDULE_EXACT_ALARM 做為替代方案。

以上整理自 Google Play Policy

SCHEDULE_EXACT_ALARM 權限

💡 適用的精準鬧鐘 API

  1. setExact()
  2. setExactAndAllowWhileIdle()
  3. setAlarmClock()

Android 12 以前

可直接使用 API。

Android 12 後

從 Android 12 開始,所有 Target SDK 為 Android 12 的 App ,如果要使用 AlarmManager 的精準鬧鐘 API,都必須要在 AndroidManifest.xml 中宣告SCHEDULE_EXACT_ALARM 權限。

因為此權限系統預設允許,所以不用特別引導使用者開啟設定。

Android 14 後

在以下幾種情況時,SCHEDULE_EXACT_ALARM 權限預設關閉。

  1. App Target SDK 為 Android 13 或以上。
    1. 包含使用者使用備份軟體將 App 還原到較新版本的裝置上。
    2. 原本就有安裝且允許權限,然後系統自動更新到新系統者不在此限。
  2. AndroidManifest.xml 中有宣告 SCHEDULE_EXACT_ALARM
  3. App 不在豁免條件或預先允許的情境內。
  4. 非日曆或鬧鐘應用程式。

三、最佳實踐

1. 在 AndroidManifest.xml 宣告使用的權限

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

2. 設定 Alarm 前動態檢查,否則會出 SecurityExeption

使用 AlarmManager.canScheduleExactAlarm() 檢查是否取得權限。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/**
 * [ALARMMANAGER] Android 12 以上 精確鬧鐘權限
 *
 * @return
 */
private fun isSetExactAlarmAllowed(): Boolean =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        val alarmManager =
            requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
        alarmManager.canScheduleExactAlarms()
    } else {
        true
    }

3. 若無法取得權限,可以跟使用者說明後,使用 Intent 帶往系統設定。

1
2
3
4
5
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return

val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
intent.data = Uri.parse("package:${context?.packageName}")
startActivity(intent)

4. 官方建議在 onResume() 再次檢查是否有取得權限。

1
2
3
4
5
6
7
8
9
override fun onResume() {   
	if (alarmManager.canScheduleExactAlarms()) {
		// Set exact alarms.
		alarmManager.setExact(...)
	} else { 
		// Permission not yet approved. Display user notice and revert to a fallback approach.
		alarmManager.setWindow(...)
	}
}

🚨 監聽權限允許結果

SCHEDULE_EXACT_ALARMS 的權限允許時,系統會發送帶有 ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED的 Broadcast,App 可以實作 BroadcastReceiver來監聽此事件。 在監聽事件的回呼中,官方也建議再用 canSetExactAlarm() 檢查是否真的有拿到權限。

我個人的作法,會另外將是否曾請求過權限記錄在本地端,避免因為單一功能重複向使用者請求權限。

四、參考資料