Android闹钟应用SetAlarmClock无效问题求助
Android闹钟setAlarmClock无法触发的修复方案
核心问题分析
你的闹钟无法触发主要源于以下几个关键错误:
- 触发闹钟的Intent未设置Action且未携带必要的Clock ID,导致BroadcastReceiver无法识别并处理
- 闹钟时间计算逻辑错误,无论当前时间是否已过都直接加1天,可能导致闹钟设置到错误的时间点
- PendingIntent未适配Android 12+的Flag要求,存在兼容性问题
- BroadcastReceiver的清单配置缺失对应Action,系统无法分发广播
- 重复闹钟触发后未重新设置下一次闹钟,仅能触发一次
修复后的代码实现
1. 闹钟设置类(AlarmManagement)
class AlarmManagement private constructor(private val context: Context) { companion object { private var instance: AlarmManagement? = null const val ACTION_START_ALARM = "ACTION_START_ALARM" @JvmStatic fun getInstance(context: Context): AlarmManagement { if (instance == null) { instance = AlarmManagement(context.applicationContext) } return instance!! } } private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager private 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) return false 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 // 配置Intent:设置Action并携带Clock ID val alarmIntent = Intent(context, AlarmReceiver::class.java).apply { action = ACTION_START_ALARM putExtra("clockId", clock.id) } // 适配Android 12+的PendingIntent Flag val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } val pendingIntent = PendingIntent.getBroadcast( context, clock.id!!.toInt(), alarmIntent, flags ) // 修正时间计算逻辑:如果设置时间早于当前时间,才加1天 val calendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, clock.triggeringHour) set(Calendar.MINUTE, clock.triggeringMinute) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) // 如果当前时间已过设置时间,调整到次日 if (timeInMillis < System.currentTimeMillis()) { add(Calendar.DAY_OF_YEAR, 1) } } val info = AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingIntent) alarmManager.setAlarmClock(info, pendingIntent) Camp.log("alarm", "AlarmClock was set for ${clock.id} at ${calendar.time}") } fun exactAlarmIsAllowed(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { return if (alarmManager.canScheduleExactAlarms()) { Camp.log("alarm info", "Exact alarm permission granted") true } else { Camp.log("alarm info", "Exact alarm permission missing") false } } else { Camp.log("alarm info", "Exact alarm permission not required for this SDK version") return true } } fun reloadAllActiveClocksAlarmIntents() { val clocks = database.getAllClocks() clocks.forEach { if (it.isActive) setOrUpdateAlarm(it) } } fun cancelAlarmIntent(intentRequestCode: Int) { val alarmIntent = Intent(context, AlarmReceiver::class.java).apply { action = ACTION_START_ALARM } val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } val pendingIntent = PendingIntent.getBroadcast( context, intentRequestCode, alarmIntent, flags ) pendingIntent.cancel() alarmManager.cancel(pendingIntent) } }
2. 广播接收器类(AlarmReceiver)
class AlarmReceiver : BroadcastReceiver() { private val intentProcessingCompletionMessage = "Intent processing has completed." override fun onReceive(context: Context, intent: Intent) { when (intent.action) { AlarmManagement.ACTION_START_ALARM -> handleAlarmTrigger(context, intent) Intent.ACTION_BOOT_COMPLETED -> AlarmManagement.getInstance(context).reloadAllActiveClocksAlarmIntents() } } private fun handleAlarmTrigger(context: Context, intent: Intent) { 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()) // 获取触发的Clock val clockId = intent.getLongExtra("clockId", -1L) if (clockId == -1L) { Camp.log("error alarm", "Intent clock id is invalid") return } val clock = database.getClockById(clockId) ?: run { Camp.log("error alarm", "Clock with id $clockId not found") return } if (!clock.isActive) { Camp.log("error alarm", "Inactive alarm triggered, canceling intent") AlarmManagement.getInstance(context).cancelAlarmIntent(clockId.toInt()) return } // 检查是否是触发日期 val isRightDay = when (clock.alarmRepeatingMode) { AlarmRepeatingMode.ONETIME, AlarmRepeatingMode.EVERYDAY -> true AlarmRepeatingMode.SELECTDAYS -> { clock.triggeringWeekDays?.and(1 shl (today - 1)) != 0 } else -> { Camp.log("error alarm", "Unsupported repeating mode") false } } if (!isRightDay) { Camp.log("Alarm info", "Current day does not match alarm settings") // 重新设置到下一个符合条件的日期 AlarmManagement.getInstance(context).setOrUpdateAlarm(clock) return } // 处理闹钟触发逻辑 when (clock.alarmRepeatingMode) { AlarmRepeatingMode.ONETIME -> { clock.isActive = false database.insertOrUpdateClock_IdOrResult(clock) } AlarmRepeatingMode.EVERYDAY, AlarmRepeatingMode.SELECTDAYS -> { // 重新设置下一次闹钟 AlarmManagement.getInstance(context).setOrUpdateAlarm(clock) } } Camp.log("Alarm info", "Alarm starting. LockScreenActivity started. $intentProcessingCompletionMessage") MyApplication.getInstance().startLockScreenActivity(clock) } }
3. 清单文件(AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <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"> <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> <service android:name=".AlarmService" /> <receiver android:name=".AlarmReceiver" android:exported="false"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <!-- 添加闹钟触发的Action --> <action android:name="ACTION_START_ALARM" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> <activity android:name=".MainActivity" android:exported="true" /> <activity android:name=".ClockSettingActivity" android:exported="false" /> <activity android:name=".SettingsActivity" android:exported="true" /> <activity android:name=".LockScreenActivity" android:exported="true" android:windowSoftInputMode="stateHidden"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
额外注意事项
- 对于Android 13+,除了
SCHEDULE_EXACT_ALARM权限,还需要引导用户手动开启精确闹钟权限(通过alarmManager.createScheduleExactAlarmIntent()跳转到系统设置) - 建议申请忽略电池优化权限,避免系统在后台杀死闹钟进程
- 测试时请关闭设备的省电模式,部分厂商的省电策略会拦截闹钟触发
内容的提问来源于stack exchange,提问作者Camporation
相关产品推荐
相关产品推荐

