我正在 Android 上制作一个闹钟应用程序。看来我一切都是按照文档做的,但闹钟却从来没有响过。我不想使用 SetExact 方法来设置闹钟,因为根据文档,在我的情况下使用 SetAlarmClock 是合乎逻辑的。我需要一个相关且适用的用例。
在这里,为了以防万一,我将给出我编写的代码示例 - 意图已安装,但不起作用。
设置信号的类:
class AlarmManagement private constructor(private val context: Context) {
companion object {
private var instance: AlarmManagement? = null
@JvmStatic
fun getInstance(context: Context): AlarmManagement {
if (instance == null) {
instance = AlarmManagement(context.applicationContext)
}
return instance!!
}
}
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val database = Database.getInstance(MyApplication.getAppContext())
fun setOrUpdateAlarm(clock: Clock) {
fun clockValuesAreCorrectForAlarmSetting(clock: Clock): Boolean {
if(clock == null) {
Camp.log("error alarm", "Clock equal to zero was passed to setOrUpdateAlarm()")
return false
}
if (clock.isActive == false) return false//this is acceptable, just end the method
if (clock.id == null) {
Camp.log("error alarm", "A clock with id = null was passed to set the alarm")
return false
}
return true
}
if (!clockValuesAreCorrectForAlarmSetting(clock)) return
if (!exactAlarmIsAllowed()) return
val alarmIntent = Intent(context, AlarmReceiver::class.java).apply {
}
val pendingIntent = PendingIntent.getBroadcast(
context
clock.id!!.toInt(),//ALARM_REQUEST_CODE
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
//To use not millisseconds to set the time, but time in a convenient format, use Calendar
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, clock.triggeringHour)
set(Calendar.MINUTE, clock.triggeringMinute)
if (clock.alarmRepeatingMode == AlarmRepeatingMode.EVERYDAY || clock.alarmRepeatingMode == AlarmRepeatingMode.SELECTDAYS) {
add(Calendar.DAY_OF_YEAR, 1) // Set the interval for the day
}
}
val info = AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingIntent)
//Warning: on android below 5.0 you need to use ExactAlarm instead of setAlarmClock (I’m making an application for newer versions)
alarmManager.setAlarmClock(info, pendingIntent)
Camp.log("alarm", "AlarmClock was set for $clock")
}
//Used at application startup to check permission to set fine Alarms (AlarmClock and ExactAlarm)
fun exactAlarmIsAllowed(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms() == true) {
return true
} else {
Camp.log(
"alarm info",
"Check result: permission to run alarms is missing (exact alarms)"
)
return false
}
} else {
Camp.log(
"alarm info",
"Check result: there is permission to run alarms (exact alarms)"
)
return true
}
}
//Restarts the intents of all active alarms
fun reloadAllActiveClocksAlarmIntents() {
val clocks = database.getAllClocks()
clocks.forEach {
setOrUpdateAlarm(it)
}
}
/** Cancel AlarmIntent by intentRequestCode
I am using clock.id as requestCode at the same time*/
fun cancelAlarmIntent(intentRequestCode: Int) {
//Create a new empty intent with the same requestCode as the one you want to cancel
//In this case, a replacement occurs (the previous one is deleted)
val alarmIntent = Intent(context, AlarmReceiver::class.java).apply {}
val pendingIntent = PendingIntent.getBroadcast(
context
intentRequestCode,
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
//Cancel empty intent
pendingIntent.cancel()
}
}
Receiver 类,在触发时接收它们(SetAlarmClock() 的意图甚至不被接受,尽管 SetExact 被接受)。可能没有必要在这里包含它的代码,因为意图没有达到它,但仍然:
class AlarmReceiver : BroadcastReceiver() {
/* val alarmsManager = AlarmManagement.getInstance(*//*MyApplication.getAppContext()*//*)*/
lateinit var triggeringClock: Clock
val intentProcessingCompletionMessage = "Intent processing has completed."
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == "ACTION_START_ALARM") {
Camp.log("alarm","AlarmReceiverreceived alarm intent")
val calendar = Calendar.getInstance()
val today = calendar.get(Calendar.DAY_OF_WEEK)
val database = Database.getInstance(MyApplication.getAppContext())
fun intentProcessing() {
fun gettingClock(): Clock? {
val intentClockId = intent.getSerializableExtra("clockId") as Long
if (intentClockId == null) {
Camp.log("error alarm", "Intent clock id == null")
return null
}
val dbClock = database.getClockById(intentClockId)
if (dbClock == null) {
Camp.log("error alarm", "Clock with the same id as intentClock was not found")
return null
}
return dbClock
}
val clock = gettingClock()
if (clock == null) {
Camp.log(
"error alarm",
"An error occurred while retrieving the alarm object. $intentProcessingCompletionMessage"
)
return
}
if(!clock.isActive){
Camp.log(
"error alarm",
"For an unexpected reason, an inactive alarm was triggered. The intent for this alarm has been canceled and will no longer fire. $intentProcessingCompletionMessage"
)
return
}
fun todayIsRightDayForAlarm(): Boolean {
if (clock.alarmRepeatingMode == AlarmRepeatingMode.ONETIME || clock.alarmRepeatingMode == AlarmRepeatingMode.EVERYDAY) {
return true
}
if (clock.alarmRepeatingMode == AlarmRepeatingMode.SELECTDAYS) {
//Calendar.DAY_OF_WEEK week starts on Sun (Sun == 1, Mon == 2, etc.), so we convert it to (1 shl (today - 1))
return (clock.triggeringWeekDays?.and((1 shl (today - 1)))) != 0 //check for the presence of today in the triggeringWeekDays combination using bitwise multiplication
}
Camp.log(
"error alarm",
"Checking for the day of the week to trigger this type of alarm was not provided"
)
return false
}
if (!todayIsRightDayForAlarm()) {
Camp.log(
"Alarm info"
"The current day of the week does not match the days of the received alarm. $intentProcessingCompletionMessage"
)
return
}
fun activateAlarm(triggeringClock: Clock) {
when (triggeringClock.alarmRepeatingMode) {
AlarmRepeatingMode.ONETIME -> {
triggeringClock.isActive = false
database.insertOrUpdateClock_IdOrResult(triggeringClock)
}
AlarmRepeatingMode.EVERYDAY, AlarmRepeatingMode.SELECTDAYS -> {
}
}
Camp.log("Alarm info", "Alarm starting. LockScreenActivity started. $intentProcessingCompletionMessage")
MyApplication.getInstance().startLockScreenActivity(triggeringClock)
}
activateAlarm(clock)
return
}
intentProcessing()
}
//Triggered when the device is rebooted
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
AlarmManagement.getInstance(MyApplication.getAppContext()).reloadAllActiveClocksAlarmIntents()
}
}
}
清单:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!--To use ExactAlarms and SetAlarmClock-->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!--AlarmClock needs to continue working even after the device is rebooted-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--Permission to display pop-up windows (activity)qa when the application is running in the background-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Base.Theme.PasswordAlarmClock"
tools:targetApi="31">
<!-- Declare the Worker class for the WorkManager -->
<service
android:name=".MyWorker"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE">
<intent-filter>
<action android:name="androidx.work.Worker" />
</intent-filter>
</service>
<!-- ... -->
<!--Declare an unkillable service for precise notifications-->
<!--android:foregroundServiceType=""//I don’t know what type should be used and whether it is necessary-->
<service
android:name=".AlarmService" />
<!-- <receiver
android:name=".AlarmReceiver"
android:enabled="true"
android:exported="true" />-->
<receiver android:name=".AlarmReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
<activity
android:name=".ClockSettingActivity"
android:exported="false">
</activity>
<activity
android:name=".SettingsActivity"
android:exported="true">
</activity>
<!-- android:windowSoftInputMode="stateHidden" is used to prevent the keyboard from appearing automatically (due to the use of an invisible zone for the input field)-->
<activity
android:name=".LockScreenActivity"
android:exported="true"
android:windowSoftInputMode="stateHidden">
<!--This block makes the activity start-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
在没有服务的情况下尝试时。在模拟器上一切正常,但在小米上不行。 可以使用 FLAG_UPDATE_CURRENT 进行更新,然后在启动新意图时无需取消。
报警管理
class AlarmManagement private constructor(/*private val context: Context*/) {
companion object {
private var instance: AlarmManagement? = null
@JvmStatic
fun getInstance(): AlarmManagement {
if (instance == null) {
instance = AlarmManagement()
}
return instance!!
}
}
val alarmManager = MyApplication.getAppContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
val database = Database.getInstance(MyApplication.getAppContext())
val exactAlarmSettingStrategy: ExactAlarmSettingStrategy = SetAlarmClock()
fun setOrUpdateAlarm(clock: Clock) {
fun clockValuesAreCorrectForAlarmSetting(): Boolean {
if (!clock.isActive) return false
if (clock.id == null) {
Camp.log("error alarm", "A clock with id = null was passed for setting the alarm")
return false
}
return true
}
if (!clockValuesAreCorrectForAlarmSetting()) return
val requestCode = clock.id!!.toInt()
cancelAlarmIntent(requestCode)
val alarmIntent = Intent(MyApplication.getAppContext(), AlarmReceiver::class.java).apply {
action = "ALARM"
putExtra("clockId", clock.id)
}
if (!exactAlarmIsAllowed()) return
val pendingIntent = PendingIntent.getBroadcast(
MyApplication.getAppContext(),
requestCode,
alarmIntent,
PendingIntent.FLAG_IMMUTABLE
)
val calendar = java.util.Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, clock.triggeringHour)
set(Calendar.MINUTE, clock.triggeringMinute)
}
if (calendar.timeInMillis <= System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_YEAR, 1)
}
exactAlarmSettingStrategy.setExactAlarm(alarmManager, pendingIntent, calendar)
Camp.log("alarm", "AlarmClock was set for $clock")
}
fun exactAlarmIsAllowed(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return if (alarmManager.canScheduleExactAlarms()) {
true
} else {
Camp.log(
"alarm info",
"Check result: permission for scheduling exact alarms is missing"
)
false
}
} else {
Camp.log(
"alarm info",
"Check result: permission for scheduling exact alarms is present"
)
return true
}
}
fun activateInactiveAlarmIntentsWhoseClocksAreActive() {
val clocks = database.getAllClocks()
clocks.forEach {
setOrUpdateAlarm(it)
}
}
fun cancelAlarmIntent(intentRequestCode: Int) {
val intent = Intent(MyApplication.getAppContext(), AlarmReceiver::class.java)
val pendingIntent =
PendingIntent.getService(MyApplication.getAppContext(), intentRequestCode, intent,
PendingIntent.FLAG_NO_CREATE)
if (pendingIntent != null) {
alarmManager.cancel(pendingIntent)
}
}
}
接收器
package com.camporation.passwordalarmclock
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import java.util.Calendar
class AlarmReceiver : BroadcastReceiver() {
lateinit var triggeringClock: Clock
val intentProcessingCompletionMessage = "Intent processing is complete. "
override fun onReceive(context: Context, intent: Intent) {
Camp.log("AlarmReceiver received intent")
if (intent.action == "ALARM") {
Camp.log("alarm","AlarmReceiver received alarm intent")
val calendar = Calendar.getInstance()
val today = calendar.get(Calendar.DAY_OF_WEEK)
val database = Database.getInstance(MyApplication.getAppContext())
fun intentProcessing() {
fun gettingClock(): Clock? {
val intentClockId = intent.getSerializableExtra("clockId") as Long?
if (intentClockId == null) {
Camp.log("error alarm", "Intent clock id == null")
return null
}
val dbClock = database.getClockById(intentClockId)
if (dbClock == null) {
Camp.log("error alarm", "Clock with the same id as intentClock was not found")
return null
}
return dbClock
}
val clock = gettingClock()
if (clock == null) {
Camp.log(
"error alarm",
"An error occurred while obtaining the alarm object. $intentProcessingCompletionMessage"
)
return
}
if(!clock.isActive){
Camp.log(
"error alarm",
"For an unforeseen reason, an inactive alarm went off. The intent of this alarm is canceled and will not trigger anymore. $intentProcessingCompletionMessage"
)
return
}
fun todayIsRightDayForAlarm(): Boolean {
if (clock.alarmRepeatingMode == AlarmRepeatingMode.ONETIME || clock.alarmRepeatingMode == AlarmRepeatingMode.EVERYDAY) {
return true
}
if (clock.alarmRepeatingMode == AlarmRepeatingMode.SELECTDAYS) {
return (clock.triggeringWeekDays?.and((1 shl (today - 1)))) != 0
}
Camp.log(
"error alarm",
"Checking the day of the week for triggering this type of alarm was not provided"
)
return false
}
if (!todayIsRightDayForAlarm()) {
Camp.log(
"Alarm info",
"The current day of the week does not match the triggering days of the accepted alarm. $intentProcessingCompletionMessage"
)
return
}
fun updateClockInDatabase(){
when (clock.alarmRepeatingMode) {
AlarmRepeatingMode.ONETIME -> {
clock.isActive = false
val idOrResult = database.insertOrUpdateClock_IdOrResult(clock)
Camp.log("One-time alarm modified: isActive = false. It is added to the database with id/result: $idOrResult")
}
AlarmRepeatingMode.EVERYDAY, AlarmRepeatingMode.SELECTDAYS -> {
// currently not required for these types
}
}
}
updateClockInDatabase()
MainActivity.currentActivity?.onDatabaseUpdatingInReceiver()
AlarmManagement.getInstance().activateInactiveAlarmIntentsWhoseClocksAreActive()
Camp.log("alarm allAlarms receiver","The Receiver activated all inactive intents with active alarms")
fun activateAlarm() {
Camp.log("Alarm info", "Triggering the alarm. Starting the LockScreenActivity. $intentProcessingCompletionMessage")
MyApplication.getInstance().startLockScreenActivity(clock)
}
activateAlarm()
return
}
intentProcessing()
}
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
AlarmManagement.getInstance().activateInactiveAlarmIntentsWhoseClocksAreActive()
}
}
}
准确的警报设置
interface ExactAlarmSettingStrategy {
fun setExactAlarm(alarmManager: AlarmManager, pendingIntent: PendingIntent, calendar: Calendar)
}
class SetAlarmClock : ExactAlarmSettingStrategy {
override fun setExactAlarm(
alarmManager: AlarmManager,
pendingIntent: PendingIntent,
calendar: Calendar
) {
val info = AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingIntent)
alarmManager.setAlarmClock(info, pendingIntent)
}
}
class SetExactAndAllowWhileIdle : ExactAlarmSettingStrategy {
override fun setExactAlarm(
alarmManager: AlarmManager,
pendingIntent: PendingIntent,
calendar: Calendar
) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
}
}
清单
xml
<!-- For using ExactAlarms and SetAlarmClock -->
<!-- https://developer.android.com/develop/background-work/services/alarms/schedule#exact-permission-declare -->
<!-- Requires either SCHEDULE_EXACT_ALARM or USE_EXACT_ALARM -->
<!-- Check permission existence using canScheduleExactAlarms() -->
<!-- Provided manually for Android 12 and above -->
<!-- May be revoked by the user -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<!-- Provided automatically, available from Android 13 and above -->
<!-- Cannot be revoked by the user -->
<!-- Cannot use (not critical), as working with earlier Android versions -->
<!-- <uses-permission android:name="android.permission.USE_EXACT_ALARM"/> -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<receiver android:name=".AlarmReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
```