Android Alarm Manager 每两年定时通知失效问题求助
搞定Android每两年09:00精准推送通知的问题
嘿,我来帮你排查下为啥你的Alarm Manager没精准生效,再给你一套靠谱的实现方案!
你大概率踩了这些坑
- Doze模式搞事情:Android 6.0之后的Doze模式会把非必要的闹钟给压下去,普通的重复闹钟API根本扛不住,必须用精确闹钟才行。
- 闰年坑:要是你直接算两年的毫秒数(比如
2*365*24*60*60*1000),遇到闰年就会差一天,时间久了完全不准。 - 闹钟类型选错了:要是用了
ELAPSED_REALTIME类型,那是从设备开机算时间,和实际日期半毛钱关系没有,肯定没法按9点触发。 - 没处理重启:默认闹钟在设备重启后会直接失效,得监听开机广播重新设置才行。
精准实现的步骤
1. 先把权限搞定
在AndroidManifest.xml里加上这两个权限:
<!-- Android 12+ 需要的精确闹钟权限 --> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <!-- 开机自启,重启后重新设闹钟用 --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
注意Android 12+得动态申请SCHEDULE_EXACT_ALARM权限,用户同意了才能用精确闹钟。
2. 精准计算下一次触发时间
用Calendar来算,避开闰年的坑:
private Calendar getNextTriggerTime() { Calendar calendar = Calendar.getInstance(); // 先把时间调到当天的09:00:00.000 calendar.set(Calendar.HOUR_OF_DAY, 9); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); // 如果现在已经过了今天的9点,就直接跳到后年的同一天9点;没到的话就用今天的9点 if (System.currentTimeMillis() > calendar.getTimeInMillis()) { calendar.add(Calendar.YEAR, 2); } return calendar; }
3. 设置单次精确闹钟(别用重复闹钟!)
重复闹钟的固定间隔会有闰年问题,所以最好设单次闹钟,每次触发完再设下一次的:
private void scheduleTwoYearAlarm(Context context) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmReceiver.class); // 注意FLAG_IMMUTABLE,Android 12+必须加这个 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); Calendar nextTrigger = getNextTriggerTime(); long triggerTime = nextTrigger.getTimeInMillis(); // 根据Android版本选合适的API,确保Doze模式下也能触发 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (alarmManager.canScheduleExactAlarms()) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } else { // 引导用户去开权限 Intent permissionIntent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); permissionIntent.setData(Uri.parse("package:" + context.getPackageName())); context.startActivity(permissionIntent); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } else { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent); } }
4. 写个广播接收器,触发后推通知+设下一次闹钟
public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 先判断是不是开机广播,是的话直接重设闹钟 if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { scheduleTwoYearAlarm(context); return; } // 推送通知给用户 sendNotification(context); // 设置下一次两年后的闹钟 scheduleTwoYearAlarm(context); } private void sendNotification(Context context) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); String channelId = "two_year_reminder_channel"; // Android 8.0+必须建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(channelId, "两年一次提醒", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification) // 换成你自己的图标 .setContentTitle("两年之约提醒") .setContentText("这是你设置的每两年一次的通知哦") .setPriority(NotificationCompat.PRIORITY_DEFAULT); notificationManager.notify(1, builder.build()); } // 把之前的getNextTriggerTime和scheduleTwoYearAlarm方法移到这里,或者封装成工具类 }
5. 注册广播接收器
在AndroidManifest.xml里加上:
<receiver android:name=".AlarmReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
测试小技巧
要是不想等两年测试,可以把getNextTriggerTime里的calendar.add(Calendar.YEAR, 2)改成calendar.add(Calendar.MINUTE, 2),等两分钟看能不能触发,没问题再改回两年。
额外提醒
要是用户把你的应用强制停止了,闹钟会失效,这种情况没法避免,只能引导用户别强制停应用;另外如果需要固定时区(比如UTC),记得给Calendar设置时区:calendar.setTimeZone(TimeZone.getTimeZone("UTC"))。
内容的提问来源于stack exchange,提问作者اندرولند androland
相关产品推荐
相关产品推荐

