Android Java实现应用关闭状态下每日推送通知问题求解
Android 应用关闭状态下每日推送通知失效修复方案
现有代码存在多处兼容性、逻辑错误,会直接导致应用退到后台/被关闭后闹钟无法触发、通知无法弹出,按以下步骤修复即可。
核心问题点
- Manifest中广播接收器配置错误:配置的无效intent-filter无实际作用,未配置开机重启监听,设备重启后闹钟会直接失效。
- AlarmManager使用不符合高版本系统要求:
setRepeating在Android 4.4以上不保证精准触发,Android 6.0以上Doze模式下会被拦截,且未适配Android 12+的PendingIntent可变性规则,会直接导致PendingIntent创建失败。 - 闹钟时间逻辑缺陷:未判断设置的时间是否早于当前时间,若当前时间已过当日设定的推送时间,闹钟不会按预期触发。
- 通知渠道逻辑冗余:每次触发广播才创建通知渠道,初始化Builder时使用了未创建的默认渠道ID,会导致高版本系统下通知不显示。
- 未适配国产ROM后台限制:小米、华为、OPPO、vivo等定制ROM默认禁止应用后台自启动、后台运行,未引导用户开启对应权限的情况下,应用被杀后无法唤醒。
修复步骤
1. 修正AndroidManifest.xml配置
首先添加必要权限,修正广播接收器配置:
<!-- 基础权限 --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- Android13+ 通知权限,需要动态申请 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <application ...> <!-- 其他组件配置 --> <receiver android:name=".dailyNotification.Notification_reciever" android:exported="false"> <intent-filter> <!-- 监听开机完成事件,重启后自动恢复闹钟 --> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /> </intent-filter> </receiver> </application>
注意:国产ROM的自启动、后台无限制权限无法通过代码申请,需要在应用内做引导,让用户手动开启,否则应用被系统强制停止后,任何定时任务都无法触发。
2. 修正闹钟设置逻辑
废弃setRepeating方法,使用适配高版本的精准闹钟API,处理时间过期问题,给PendingIntent添加正确的Flag:
private static final int NOTIFICATION_REQ_CODE = 100; private void setDailyPushAlarm() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 22); calendar.set(Calendar.MINUTE, 20); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); // 当前时间已过当日推送点,顺延到次日 if (Calendar.getInstance().after(calendar)) { calendar.add(Calendar.DAY_OF_MONTH, 1); } Intent intent = new Intent(getApplicationContext(), Notification_reciever.class); int flag = PendingIntent.FLAG_UPDATE_CURRENT; // Android12+ 必须指定PendingIntent可变性 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { flag |= PendingIntent.FLAG_IMMUTABLE; } PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), NOTIFICATION_REQ_CODE, intent, flag); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); // 先取消已存在的同ID闹钟,避免重复 alarmManager.cancel(pendingIntent); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Android6.0+ 适配Doze模式,保证休眠状态下也能触发 alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android4.4-6.0 使用精准闹钟 alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent); } else { // 4.4以下版本使用重复闹钟 alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); } }
高版本Android不支持精准重复闹钟,需要在每次闹钟触发、推送完成后,重新调用该方法设置次日的闹钟。
3. 修正广播接收器实现
优化通知渠道创建逻辑,处理开机广播事件,触发推送后自动设置次日闹钟:
public class Notification_reciever extends BroadcastReceiver { public static final String CHANNEL_ID = "daily_push_channel"; private static final int NOTIFICATION_REQ_CODE = 100; @Override public void onReceive(Context context, Intent intent) { // 收到开机广播,直接恢复闹钟即可,不需要发通知 if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { setNextDayAlarm(context); return; } // 初始化通知渠道 initNotificationChannel(context); // 构造点击通知跳转的PendingIntent Intent jumpIntent = new Intent(context, MainActivity.class); jumpIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); int flag = PendingIntent.FLAG_UPDATE_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { flag |= PendingIntent.FLAG_IMMUTABLE; } PendingIntent clickPendingIntent = PendingIntent.getActivity(context, NOTIFICATION_REQ_CODE, jumpIntent, flag); // 构造通知 NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("Notification 1") .setContentText("Hello") .setContentIntent(clickPendingIntent) .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_HIGH); NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); if (manager != null) { manager.notify((int) System.currentTimeMillis(), builder.build()); } // 设置次日的闹钟 setNextDayAlarm(context); } private void initNotificationChannel(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; NotificationManager manager = context.getSystemService(NotificationManager.class); // 渠道已存在则跳过创建 if (manager.getNotificationChannel(CHANNEL_ID) != null) return; AudioAttributes audioAttributes = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ALARM) .build(); NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "每日通知", NotificationManager.IMPORTANCE_HIGH ); channel.enableLights(true); channel.setLightColor(Color.RED); channel.enableVibration(true); channel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400}); manager.createNotificationChannel(channel); } private void setNextDayAlarm(Context context) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 22); calendar.set(Calendar.MINUTE, 20); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); calendar.add(Calendar.DAY_OF_MONTH, 1); Intent intent = new Intent(context, Notification_reciever.class); int flag = PendingIntent.FLAG_UPDATE_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { flag |= PendingIntent.FLAG_IMMUTABLE; } PendingIntent pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_REQ_CODE, intent, flag); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pendingIntent); 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.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent); } } }
额外兼容说明
- Android 13(API33)及以上版本,必须在应用启动时动态申请
POST_NOTIFICATIONS权限,用户授权后通知才能正常弹出。 - 广播接收器的
onReceive方法执行时间不能超过10秒,若有复杂逻辑请启动前台服务处理,否则会被系统直接杀死。 - 如果对定时精准度要求不高,可以替换为WorkManager实现定时任务,对不同厂商ROM的兼容性更好,不需要额外处理Doze模式适配。
内容的提问来源于stack exchange,提问作者Ali Habib Haidar
相关产品推荐
相关产品推荐

