You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:08:10