Android开发:如何阻止应用打开时重复显示定时推送通知
解决Android每日定时通知重复显示问题
首先得给你梳理下当前代码里的核心问题:
- 每次启动应用都重复设置了闹钟,导致可能多次触发通知
- 没有标记当天是否已经推送过通知,所以当天内可能重复弹出
- PendingIntent的Flag没有适配Android 12+的要求,可能导致Intent行为异常
下面是针对性的解决方案,一步步帮你实现「每日指定时间仅推送一次,应用打开时不再重复显示」的需求:
一、核心优化思路
- 用SharedPreferences记录当天是否已推送,避免重复触发
- 仅在闹钟未设置时才初始化,防止重复注册
- 使用精准闹钟API,保证指定时间触发,同时适配Doze模式
- 应用前台时取消当前通知,避免显示冗余内容
二、完整优化代码
1. 修改广播接收器(AlarmBroadcastReceiver2.kt)
package com.test.android import android.app.* import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import android.graphics.Color import android.os.Build import androidx.core.app.NotificationCompat import java.util.* class AlarmBroadcastReceiver2: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val sharedPref = context.getSharedPreferences("NotificationPrefs", Context.MODE_PRIVATE) val today = Calendar.getInstance().get(Calendar.DAY_OF_YEAR) val lastPushDay = sharedPref.getInt("last_push_day", -1) // 当天已经推送过,直接设置第二天闹钟后返回 if (today == lastPushDay) { setupNextDayAlarm(context) return } // 显示通知 showNotification(context) // 更新当天推送标记 sharedPref.edit().putInt("last_push_day", today).apply() // 设置第二天的闹钟 setupNextDayAlarm(context) } // 设置第二天的16点闹钟 private fun setupNextDayAlarm(context: Context) { val nextCalendar = Calendar.getInstance() nextCalendar.set(Calendar.HOUR_OF_DAY, 16) nextCalendar.set(Calendar.MINUTE, 0) nextCalendar.set(Calendar.SECOND, 0) nextCalendar.add(Calendar.DAY_OF_YEAR, 1) val nextIntent = Intent(context, AlarmBroadcastReceiver2::class.java) val nextPendingIntent = PendingIntent.getBroadcast(context, 0, nextIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } ) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 适配Doze模式,保证精准触发 alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextCalendar.timeInMillis, nextPendingIntent) } else { alarmManager.setExact(AlarmManager.RTC_WAKEUP, nextCalendar.timeInMillis, nextPendingIntent) } } internal fun showNotification(context: Context) { val CHANNEL_ID = "daily_absence_notification" val name = context.resources.getString(R.string.app_name) val notificationIntent = Intent(context, MainActivity::class.java) // 避免重复创建Activity实例 notificationIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP val contentIntent = PendingIntent.getActivity( context, 1, notificationIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } ) val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Android 8.0+ 创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val mChannel = NotificationChannel( CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH ).apply { enableLights(true) lightColor = Color.RED enableVibration(true) vibrationPattern = longArrayOf(1000, 1000, 1000, 1000, 1000) } mNotificationManager.createNotificationChannel(mChannel) } val mBuilder = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.mipmap.ic_launcher_round) .setLights(Color.RED, 300, 300) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setDefaults(Notification.DEFAULT_ALL) .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000)) .setAutoCancel(true) .setStyle( NotificationCompat.BigPictureStyle() .bigPicture(BitmapFactory.decodeResource(context.resources, R.drawable.absence2_2)) .bigLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher_round)) .setBigContentTitle("Selamat Sore Jangan Lupa Untuk Absensi ") .setSummaryText("Mohon melakukan absensi sore ini,Terima kasih") ) .setContentIntent(contentIntent) mNotificationManager.notify(1, mBuilder.build()) } }
2. 初始化闹钟(建议放在Application类或MainActivity的onCreate中)
private fun setupDailyAlarm() { val _intent2 = Intent(this, AlarmBroadcastReceiver2::class.java) // 检查是否已经存在相同的PendingIntent,避免重复设置 val existingPendingIntent = PendingIntent.getBroadcast(this, 0, _intent2, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_NO_CREATE } ) // 仅当闹钟未设置时才初始化 if (existingPendingIntent == null) { val pendingIntent2 = PendingIntent.getBroadcast(this, 0, _intent2, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE } else { PendingIntent.FLAG_UPDATE_CURRENT } ) val calendar2 = Calendar.getInstance() calendar2.set(Calendar.HOUR_OF_DAY, 16) calendar2.set(Calendar.MINUTE, 0) calendar2.set(Calendar.SECOND, 0) // 如果当前时间已经过了16点,直接设置为第二天的16点 if (calendar2.timeInMillis < System.currentTimeMillis()) { calendar2.add(Calendar.DAY_OF_YEAR, 1) } val alarmManager2 = getSystemService(Context.ALARM_SERVICE) as AlarmManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager2.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar2.timeInMillis, pendingIntent2) } else { alarmManager2.setExact(AlarmManager.RTC_WAKEUP, calendar2.timeInMillis, pendingIntent2) } } }
3. 应用打开时取消通知(MainActivity的onResume方法)
override fun onResume() { super.onResume() // 应用回到前台时,取消当前显示的通知 val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(1) // 1是通知的ID,要和notify时的ID一致 }
三、关键说明
- 每日推送标记:用SharedPreferences记录当天的年份天数,确保一天只推一次
- 精准闹钟API:
setExactAndAllowWhileIdle比setInexactRepeating更精准,且能在Doze模式下正常触发 - PendingIntent Flag:Android 12+必须添加
FLAG_IMMUTABLE,避免安全风险 - 前台取消通知:应用打开时主动取消通知,避免重复显示
内容的提问来源于stack exchange,提问作者Muhammad Iqbal Alhadad




