Android AlarmManager新增闹钟无法设置或触发问题排查求助
问题根因与修复方案
核心问题点
- PendingIntent 未指定可变性标识(适配安卓12+):API 31及以上系统要求PendingIntent必须显式指定
FLAG_IMMUTABLE或FLAG_MUTABLE,你当前传入的0属于非法参数,系统会直接丢弃该PendingIntent,导致闹钟设置失效,同时isReminderSet方法查询时也会返回null。 - 缺失必要权限:
- API 31及以上设置精确闹钟需要
android.permission.SCHEDULE_EXACT_ALARM权限,未声明该权限会导致闹钟设置直接失败 - 接收开机广播需要声明
android.permission.RECEIVE_BOOT_COMPLETED权限,你当前Manifest中未配置该权限,设备重启后闹钟无法重新注册
- API 31及以上设置精确闹钟需要
- 闹钟调用逻辑重复:API 31的设备会先后执行
setAndAllowWhileIdle和setExactAndAllowWhileIdle两次调用,后者会覆盖前者的配置,且你不需要额外调用setAndAllowWhileIdle,该方法用于不精确提醒场景 - 次日调度逻辑不合理:当前逻辑只要设定时间与当前时间差小于30分钟就直接调度到次日,会导致用户在接近提醒时间开启功能时,当天无法收到提醒,误以为功能失效
- 时区同步问题:
LocalTime使用设备当前时区,但未处理用户时区变更的场景,会导致闹钟时间偏移
修复方案
1. 补充Manifest权限与配置
<!-- 开机广播权限 --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- 安卓12-13精确闹钟权限 --> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" android:maxSdkVersion="33" /> <!-- 安卓14+精确闹钟权限 --> <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> <receiver android:name="com.lostpixels.biblept.notifications.AlarmReceiver" android:enabled="true"/> <receiver android:name="com.lostpixels.biblept.notifications.BootReceiver" android:enabled="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.TIMEZONE_CHANGED"/> <action android:name="android.intent.action.TIME_SET"/> </intent-filter> </receiver>
2. 修复PendingIntent Flag与闹钟设置逻辑
fun scheduleReminder(context: Context, reschedule: Boolean = false) { val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager // 移除lazy,每次创建PendingIntent,指定正确的Flag val intent = Intent(context, AlarmReceiver::class.java) val alarmPendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } else { PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } val time = getReminderTime(context = context) val now = LocalTime.now() // 修复次日判断逻辑:只有当前时间已经过了设定时间,才设到次日 val shouldSetForTomorrow = time < now || reschedule val calendar = GregorianCalendar.getInstance().apply { if (shouldSetForTomorrow) { add(Calendar.DAY_OF_MONTH, 1) } set(Calendar.HOUR_OF_DAY, time.hour) set(Calendar.MINUTE, time.minute) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) } // 修正版本判断逻辑,避免重复调用 when { Build.VERSION.SDK_INT >= 23 -> { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, alarmPendingIntent) } else -> { alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, alarmPendingIntent) } } }
3. 修复isReminderSet方法的Flag
fun isReminderSet(context: Context): Boolean { val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_NO_CREATE } return PendingIntent.getBroadcast( context, 0, Intent(context, AlarmReceiver::class.java), flag ) != null }
4. 新增时区/时间变更监听
在BootReceiver中新增对时间、时区变更的监听,收到广播后重新设置闹钟,避免时间变更导致的提醒偏移:
class BootReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == Intent.ACTION_BOOT_COMPLETED || intent.action == Intent.ACTION_TIMEZONE_CHANGED || intent.action == Intent.ACTION_TIME_SET) { if (userWantsReminder(context)) { scheduleReminder(context = context) } } } }
内容的提问来源于stack exchange,提问作者Mackan
相关产品推荐
相关产品推荐

