Android:如何创建精准重复闹钟并实现重启后持续运行?
问题描述
应用基本开发完成,仅通知功能存在问题:通知可正常触发,但无法同时满足精准性与重复性,已调试两天未解决。
需求
- 实现每日精准重复的闹钟通知,直到被新设置覆盖
- 确保设备重启后闹钟仍能正常运行
当前代码
MainActivity.java(scheduleNotification()由MaterialTimePicker调用)
public void scheduleNotification(int hour, int minute) { Intent alarmIntent = new Intent(this, AlarmReceiverNotification.class); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); if (calendar.before(Calendar.getInstance())) { calendar.add(Calendar.DATE, 1); } triggerTime = calendar.getTimeInMillis(); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_IMMUTABLE); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, 24*3600*1000, pendingIntent); Log.v("Alarm", "Alarm set for " + hour + ":" + minute); logTimeUntilNotification(); }
AlarmReceiverNotification.java
public class AlarmReceiverNotification extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { showNotification(context); } private void showNotification(Context context) { NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.cancelAll(); new NotificationCompat.Builder(context, "1") .setSmallIcon(R.drawable.ic_pooper_notif) .setContentTitle(context.getResources().getString(R.string.notificationTitle)) .setContentText(context.getResources().getString(R.string.notificationContent)) .setDefaults(NotificationCompat.DEFAULT_ALL) .setAutoCancel(true); } }
AndroidManifest.xml 权限配置
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
效果对比
- 预期:每日在设定的精准时间触发通知,直到被新设置覆盖
- 实际:通知触发时间比设定时间晚1个半小时以上
- 半正常:使用
setExact可精准触发,但无法重复
目标API:33,最低API:27
解决方案
1. 实现精准重复闹钟
Android 12+ 对setRepeating的精准性做了限制,系统会为省电延迟触发。需采用单次精准闹钟+触发后重新设置下一次闹钟的方案:
修改MainActivity.java的scheduleNotification()方法
public void scheduleNotification(int hour, int minute) { Intent alarmIntent = new Intent(this, AlarmReceiverNotification.class); // 携带时间参数,方便接收器计算下一次触发时间 alarmIntent.putExtra("HOUR", hour); alarmIntent.putExtra("MINUTE", minute); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); if (calendar.before(Calendar.getInstance())) { calendar.add(Calendar.DATE, 1); } long triggerTime = calendar.getTimeInMillis(); // 用FLAG_UPDATE_CURRENT确保重复设置时覆盖原有PendingIntent PendingIntent pendingIntent = PendingIntent.getBroadcast( this, 0, alarmIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT ); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); if (alarmManager != null) { // 根据API版本选择精准闹钟API if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+ 需检查SCHEDULE_EXACT_ALARM权限 if (alarmManager.canScheduleExactAlarms()) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent ); } else { // 引导用户开启精准闹钟权限 Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); startActivity(intent); } } else { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent ); } } // 保存闹钟时间到SharedPreferences,用于重启恢复 SharedPreferences prefs = getSharedPreferences("AlarmPrefs", MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt("ALARM_HOUR", hour); editor.putInt("ALARM_MINUTE", minute); editor.apply(); Log.v("Alarm", "Alarm set for " + hour + ":" + minute); logTimeUntilNotification(); }
修改AlarmReceiverNotification.java(文件位置:与MainActivity同包)
触发通知后立即设置下一天的闹钟:
public class AlarmReceiverNotification extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { showNotification(context); // 重新设置下一次闹钟 rescheduleNextAlarm(context, intent); } private void showNotification(Context context) { NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.cancelAll(); // 修复原代码:需调用build()和notify()才能显示通知 Notification notification = new NotificationCompat.Builder(context, "1") .setSmallIcon(R.drawable.ic_pooper_notif) .setContentTitle(context.getResources().getString(R.string.notificationTitle)) .setContentText(context.getResources().getString(R.string.notificationContent)) .setDefaults(NotificationCompat.DEFAULT_ALL) .setAutoCancel(true) .build(); mNotificationManager.notify(1, notification); } private void rescheduleNextAlarm(Context context, Intent intent) { int hour = intent.getIntExtra("HOUR", 0); int minute = intent.getIntExtra("MINUTE", 0); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); // 直接加1天,确保是下一次触发时间 calendar.add(Calendar.DATE, 1); long nextTriggerTime = calendar.getTimeInMillis(); Intent alarmIntent = new Intent(context, AlarmReceiverNotification.class); alarmIntent.putExtra("HOUR", hour); alarmIntent.putExtra("MINUTE", minute); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, 0, alarmIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT ); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (alarmManager.canScheduleExactAlarms()) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } } else { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } } } }
2. 实现设备重启后闹钟恢复
添加开机广播接收器,重启后读取保存的闹钟时间并重新设置:
步骤1:创建BootCompletedReceiver.java(文件位置:与其他接收器同包)
public class BootCompletedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { // 读取之前保存的闹钟时间 SharedPreferences prefs = context.getSharedPreferences("AlarmPrefs", Context.MODE_PRIVATE); int savedHour = prefs.getInt("ALARM_HOUR", 0); int savedMinute = prefs.getInt("ALARM_MINUTE", 0); // 启动MainActivity重置闹钟 Intent alarmIntent = new Intent(context, MainActivity.class); alarmIntent.putExtra("RESET_ALARM", true); alarmIntent.putExtra("HOUR", savedHour); alarmIntent.putExtra("MINUTE", savedMinute); alarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(alarmIntent); } } }
步骤2:修改MainActivity.java的onCreate方法
处理重启后的重置请求:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = getIntent(); if (intent.getBooleanExtra("RESET_ALARM", false)) { int hour = intent.getIntExtra("HOUR", 0); int minute = intent.getIntExtra("MINUTE", 0); scheduleNotification(hour, minute); } // 其他初始化逻辑... }
步骤3:在AndroidManifest.xml中添加权限与接收器注册
<!-- 添加开机权限 --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- 注册AlarmReceiverNotification --> <receiver android:name=".AlarmReceiverNotification" android:exported="false" /> <!-- 注册开机广播接收器 --> <receiver android:name=".BootCompletedReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> </intent-filter> </receiver>
关键注意事项
- 原代码
showNotification()遗漏build()和notify()调用,导致通知无法显示,已修复 - Android 12+ 需动态申请
SCHEDULE_EXACT_ALARM权限,代码中已添加检查与引导逻辑 - 必须用SharedPreferences持久化闹钟时间,否则重启后无法恢复
- 使用
PendingIntent.FLAG_UPDATE_CURRENT避免重复创建PendingIntent导致多个闹钟并存
内容的提问来源于stack exchange,提问作者Everdro1d
相关产品推荐
相关产品推荐

