很多開發者嘗試在 Service 或 BroadcastReceiver 中直接彈窗,或期望普通進程常駐後台。Android 系統為了電量和內存,會回收無前台組件的進程,導致鬧鐘失效。
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
// 設置精確鬧鐘(API 23+ 需忽略電池優化)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent);
}
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 1. 獲取通知管理器
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// 2. Android 8.0+ 必須創建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"schedule_channel", "日程提醒", NotificationManager.IMPORTANCE_HIGH);
nm.createNotificationChannel(channel);
}
// 3. 構建通知
Intent clickIntent = new Intent(context, MainActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, clickIntent,
PendingIntent.FLAG_IMMUTABLE);
Notification notification = new NotificationCompat.Builder(context, "schedule_channel")
.setContentTitle("日程提醒")
.setContentText("您有一個日程開始了")
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build();
// 4. 發送通知(ID 唯一標識)
nm.notify((int) System.currentTimeMillis(), notification);
}
}
<receiver
android:name=".AlarmReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <!-- 開機自啟 -->
</intent-filter>
</receiver>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <!-- API 31+ -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" /> <!-- API 33+ -->
WorkManager 封裝了 AlarmManager/JobScheduler,自動適配版本,支持約束條件(網絡、充電、閒置),更適合可延遲的周期性任務:
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ReminderWorker.class)
.setInitialDelay(triggerTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
.addTag("schedule_reminder")
.build();
WorkManager.getInstance(context).enqueue(work);
| Android 版本 | 關鍵要求 |
|---|---|
| 6.0 (API 23) | 忽略電池優化白名單引導 ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS |
| 8.0 (API 26) | 通知渠道、後台執行限制、隱式廣播限制 |
| 10 (API 29) | 後台啟動 Activity 限制 |
| 12 (API 31) | SCHEDULE_EXACT_ALARM 權限(用戶可拒絕) |
| 14 (API 34) | 前台服務類型細分、精確鬧鐘更嚴格 |
應用關閉後提醒的核心是 系統級鬧鐘 + 高優先級通知。不需要 Root、不需要常駐進程。正確使用 AlarmManager.setExactAndAllowWhileIdle 配合 NotificationCompat,並處理好各版本權限適配,即可可靠實現。推薦遷移至 WorkManager 簡化開發。