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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 21:35:05