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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:10:48