16、通知的延时与定时:AlarmManager+Notification、WorkManager+Notification、JobScheduler+Notification

通知不是非得立刻弹出来的。很多时候,我们需要它「等一会儿再出现」。

比如早上8点的吃药提醒,或者半小时后的会议通知。这些场景下,通知的延时与定时就成了刚需。

Android 提供了三种主流方案:AlarmManagerWorkManagerJobScheduler。它们都能搭配 Notification 实现定时通知,但各有各的脾气。今天我就把这三兄弟掰开揉碎讲清楚。

核心结论先放这:

  • 精确到秒级的定时任务 → 用 AlarmManager
  • 后台可延迟、可保证执行的任务 → 用 WorkManager
  • 系统级省电、网络相关的任务 → 用 JobScheduler
定时通知三大方案对比 AlarmManager 精确到秒级 支持唤醒设备 适合闹钟/提醒 WorkManager 保证执行、可延迟 兼容省电模式 适合后台同步 JobScheduler 系统级调度 省电优先 依赖网络/充电 选型建议 • 需要精确时间点触发(如闹钟)→ AlarmManager • 需要保证任务最终执行,且可延迟 → WorkManager • 需要系统最优调度,省电优先 → JobScheduler

16.1 AlarmManager + Notification:精确到秒的定时通知

AlarmManager 是 Android 最老牌的定时工具。它能在指定时间点触发一个 PendingIntent,哪怕 App 被杀死、手机在休眠。

说白了,它就是用来做「闹钟」的。

我的经验:我在项目中做过一个「服药提醒」功能,用户设定早上8:00吃药,必须准时弹出通知。这种场景只能用 AlarmManager,因为 WorkManager 可能会因为省电策略延迟几分钟。

基本用法

// 获取 AlarmManager 实例
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

// 创建通知的 Intent
Intent intent = new Intent(this, AlarmReceiver.class);
intent.putExtra("title", "吃药提醒");
intent.putExtra("content", "该吃降压药了!");
PendingIntent pendingIntent = PendingIntent.getBroadcast(
    this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);

// 设置定时:早上8:00
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);

// 如果当前时间已过8点,则设置为明天
if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
    calendar.add(Calendar.DAY_OF_MONTH, 1);
}

// 设置精确闹钟(API 19+ 推荐使用 setExact)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(
        AlarmManager.RTC_WAKEUP,
        calendar.getTimeInMillis(),
        pendingIntent
    );
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
}

在 BroadcastReceiver 中弹出通知

public class AlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String title = intent.getStringExtra("title");
        String content = intent.getStringExtra("content");

        NotificationManager notificationManager =
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        Notification notification = new NotificationCompat.Builder(context, "alarm_channel")
            .setSmallIcon(R.drawable.ic_alarm)
            .setContentTitle(title)
            .setContentText(content)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)
            .build();

        notificationManager.notify(1001, notification);
    }
}

注意:从 Android 12 开始,AlarmManager 的精确闹钟需要申请 SCHEDULE_EXACT_ALARM 权限。用户可以在系统设置中关闭该权限,所以要做好降级处理。

我曾经遇到过一个线上问题:用户反馈闹钟不响,查了半天发现是国产手机把精确闹钟权限默认关了。后来我们加了权限检测和引导弹窗才解决。

16.2 WorkManager + Notification:可靠的后台定时通知

WorkManager 是 Google 官方推荐的「后台任务管理器」。它不追求精确时间,但保证任务「最终一定会执行」。

你想想看,如果你的 App 需要每隔6小时同步一次数据,然后弹出通知告知用户。这种场景用 AlarmManager 太浪费,用 JobScheduler 又太复杂。WorkManager 刚刚好。

基本用法

// 定义 Worker
public class NotificationWorker extends Worker {
    public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Result doWork() {
        // 弹出通知
        showNotification("数据已同步", "最新数据已更新,点击查看");
        return Result.success();
    }

    private void showNotification(String title, String content) {
        NotificationManager notificationManager =
            (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);

        Notification notification = new NotificationCompat.Builder(getApplicationContext(), "sync_channel")
            .setSmallIcon(R.drawable.ic_sync)
            .setContentTitle(title)
            .setContentText(content)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setAutoCancel(true)
            .build();

        notificationManager.notify(2001, notification);
    }
}

// 创建定时任务(每6小时执行一次)
PeriodicWorkRequest syncWorkRequest =
    new PeriodicWorkRequest.Builder(NotificationWorker.class, 6, TimeUnit.HOURS)
        .setConstraints(new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED) // 需要网络
            .build())
        .build();

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "sync_notification",
    ExistingPeriodicWorkPolicy.KEEP,
    syncWorkRequest
);

我的建议:WorkManager 的延迟时间最小是15分钟(实际可能更长)。如果你需要「半小时后弹出通知」,可以用 OneTimeWorkRequest 配合 setInitialDelay

// 延迟30分钟后执行一次
OneTimeWorkRequest delayedWork =
    new OneTimeWorkRequest.Builder(NotificationWorker.class)
        .setInitialDelay(30, TimeUnit.MINUTES)
        .build();

WorkManager.getInstance(context).enqueue(delayedWork);

16.3 JobScheduler + Notification:系统级省电调度

JobScheduler 是 Android 5.0 引入的系统级任务调度器。它会把多个 App 的任务合并执行,从而节省电量。

说白了,它更适合「不着急、但需要省电」的场景。比如:只在 Wi-Fi 环境下同步数据,然后弹出通知告诉用户「更新完成」。

基本用法

// 定义 JobService
public class NotificationJobService extends JobService {
    @Override
    public boolean onStartJob(JobParameters params) {
        // 在后台线程执行任务
        new Thread(() -> {
            showNotification("Wi-Fi 更新", "已通过 Wi-Fi 完成数据更新");
            jobFinished(params, false);
        }).start();
        return true; // 表示任务正在执行
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false; // 不需要重试
    }

    private void showNotification(String title, String content) {
        NotificationManager notificationManager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        Notification notification = new NotificationCompat.Builder(this, "job_channel")
            .setSmallIcon(R.drawable.ic_job)
            .setContentTitle(title)
            .setContentText(content)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setAutoCancel(true)
            .build();

        notificationManager.notify(3001, notification);
    }
}

// 调度任务
ComponentName serviceComponent = new ComponentName(this, NotificationJobService.class);
JobInfo jobInfo = new JobInfo.Builder(1001, serviceComponent)
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 仅 Wi-Fi
    .setRequiresCharging(true) // 需要充电
    .setPeriodic(60 * 60 * 1000) // 每小时检查一次
    .build();

JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);

注意:JobScheduler 的最小周期是15分钟(实际可能更长)。而且从 Android 8.0 开始,后台执行限制更严格,JobScheduler 的触发频率会进一步降低。

我曾经在适配 Android 8.0 时踩过坑:JobScheduler 设置的1小时周期,实际可能2-3小时才触发一次。后来改用 WorkManager 才解决。

16.4 三方案对比总结

特性 AlarmManager WorkManager JobScheduler
精确度 秒级 分钟级(15分钟+) 分钟级(15分钟+)
省电模式兼容 需特殊处理 原生支持 原生支持
App 被杀死后 仍可触发 仍可触发 仍可触发
权限要求 SCHEDULE_EXACT_ALARM 无特殊权限 无特殊权限
适用场景 闹钟、精确提醒 后台同步、定期通知 系统级省电调度

我的最终建议:

  • 如果你需要「明天早上8点准时弹出通知」→ 用 AlarmManager
  • 如果你需要「每6小时同步一次数据并通知用户」→ 用 WorkManager
  • 如果你需要「只在 Wi-Fi 和充电时执行任务」→ 用 JobScheduler

嗯,其实大部分场景下,WorkManager 是最稳妥的选择。它兼容性好、省电、保证执行。除非你有精确到秒的需求,否则优先考虑 WorkManager。

公众号:蓝海资料掘金营,微信deep3321