AlarmManager设置同一时间时onReceive仅触发一次的问题排查
解决相同时间AlarmManager仅触发一次的问题
嗨,我完全懂你遇到的这个坑——明明给每个闹钟用了不同的requestCode创建PendingIntent,相同时间的闹钟却只触发一次,这确实让人困惑!
问题根源
你用的setInexactRepeating()是Android为了省电设计的API,它的核心特性就是会把时间接近的多个inexact闹钟合并触发,哪怕你的PendingIntent是不同的,系统也会为了优化电池使用,把它们归为同一批处理,只触发一次。这就是为什么两个相同时间的闹钟只收到一次通知的原因。
解决方案
根据你的需求,有两种可行的解决方式:
方案1:改用精确闹钟+手动重复(推荐)
放弃系统的setInexactRepeating(),改用精确闹钟API,然后在每次触发通知后,手动设置下一天的闹钟。这样每个闹钟都会被独立触发,不会被合并。
修改设置闹钟的代码:
for(int i = 0; i < dosageDtoList.size();i++) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); DosageDto dosageDto = dosageDtoList.get(i); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); String time = dosageDto.getReminderTime(); String dateTime = startDateStr + " " + time; Date formattedDate = simpleDateFormat.parse(dateTime); Calendar cal = Calendar.getInstance(); cal.setTime(formattedDate); // 避免设置过去的闹钟,如果当前时间已过,直接设置第二天的 if (cal.getTimeInMillis() < System.currentTimeMillis()) { cal.add(Calendar.DAY_OF_YEAR, 1); } Intent notificationIntent = new Intent("android.media.action.DISPLAY_NOTIFICATION"); notificationIntent.addCategory("android.intent.category.DEFAULT"); notificationIntent.putExtra(Constants.POS,i); notificationIntent.putExtra(Constants.END_DATE, endDateStr); notificationIntent.putExtra(Constants.TITLE, userDrugDto.getUserName()); notificationIntent.putExtra(Constants.SUB_TITLE, userDrugDto.getDrugName()); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); // 适配API31+的PendingIntent flags,优先用IMMUTABLE保证安全 int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; PendingIntent broadcast = PendingIntent.getBroadcast(context, i, notificationIntent, flags); if(alarmManager != null) { // 根据API版本选择合适的精确闹钟API if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 允许在设备休眠时触发,适合提醒类场景 alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), broadcast); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), broadcast); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), broadcast); } } }
然后在AlarmReceiver中,触发通知后手动设置下一天的闹钟:
public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 1. 生成并显示你的通知(这里保留你原来的代码) // 2. 获取当前闹钟的参数,准备设置下一次闹钟 int pos = intent.getIntExtra(Constants.POS, -1); String endDateStr = intent.getStringExtra(Constants.END_DATE); String title = intent.getStringExtra(Constants.TITLE); String subTitle = intent.getStringExtra(Constants.SUB_TITLE); // 3. 从存储中获取对应pos的DosageDto(比如用SharedPreferences或Room数据库) // 这里假设你已经实现了数据存储逻辑,能拿到对应的dosageDto DosageDto dosageDto = getDosageDtoByPos(pos); String time = dosageDto.getReminderTime(); try { // 计算下一天的触发时间 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); Calendar nextCal = Calendar.getInstance(); nextCal.add(Calendar.DAY_OF_YEAR, 1); String nextDateStr = dateFormat.format(nextCal.getTime()); String nextDateTime = nextDateStr + " " + time; Date nextTriggerDate = dateTimeFormat.parse(nextDateTime); Calendar triggerCal = Calendar.getInstance(); triggerCal.setTime(nextTriggerDate); // 检查是否超过结束日期,超过则停止重复 Date endDate = dateFormat.parse(endDateStr); if (triggerCal.getTime().after(endDate)) { return; } // 4. 创建下一次的Intent和PendingIntent Intent nextIntent = new Intent("android.media.action.DISPLAY_NOTIFICATION"); nextIntent.addCategory("android.intent.category.DEFAULT"); nextIntent.putExtra(Constants.POS, pos); nextIntent.putExtra(Constants.END_DATE, endDateStr); nextIntent.putExtra(Constants.TITLE, title); nextIntent.putExtra(Constants.SUB_TITLE, subTitle); nextIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; PendingIntent nextBroadcast = PendingIntent.getBroadcast(context, pos, nextIntent, flags); // 5. 设置下一次的精确闹钟 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerCal.getTimeInMillis(), nextBroadcast); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerCal.getTimeInMillis(), nextBroadcast); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, triggerCal.getTimeInMillis(), nextBroadcast); } } } catch (ParseException e) { e.printStackTrace(); } } // 这里需要你实现的方法:根据pos从存储中获取对应的DosageDto private DosageDto getDosageDtoByPos(int pos) { // 示例:从SharedPreferences或数据库读取数据 return ...; } }
方案2:给相同时间的闹钟加微小时间偏移
如果你坚持要用setInexactRepeating(),可以给每个相同时间的闹钟添加几秒钟的偏移,让系统认为它们是不同时间的闹钟,避免被合并。比如在设置cal的时候加上:
// 给每个闹钟加i*1000毫秒的偏移,确保时间不重叠 cal.add(Calendar.MILLISECOND, i * 1000);
这种方式简单,但缺点是闹钟的触发时间会有微小偏差,适合对时间精度要求不高的场景。
额外注意事项
- API版本适配:不同Android版本的AlarmManager API有差异,一定要根据targetSdk版本选择合适的方法,避免兼容性问题。
- PendingIntent Flags:API 31+建议使用
FLAG_IMMUTABLE,避免潜在的安全风险;如果需要修改Intent的extra,再用FLAG_UPDATE_CURRENT。 - 数据持久化:方案1中需要把闹钟的配置信息(比如DosageDto、结束日期等)保存下来,这样在Receiver里才能重新设置下一次的闹钟,推荐用Room数据库或者SharedPreferences存储。
内容的提问来源于stack exchange,提问作者Thankless
相关产品推荐
相关产品推荐

