You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android开发:如何阻止应用打开时重复显示定时推送通知

解决Android每日定时通知重复显示问题

首先得给你梳理下当前代码里的核心问题:

  1. 每次启动应用都重复设置了闹钟,导致可能多次触发通知
  2. 没有标记当天是否已经推送过通知,所以当天内可能重复弹出
  3. 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记录当天的年份天数,确保一天只推一次
  • 精准闹钟APIsetExactAndAllowWhileIdlesetInexactRepeating更精准,且能在Doze模式下正常触发
  • PendingIntent Flag:Android 12+必须添加FLAG_IMMUTABLE,避免安全风险
  • 前台取消通知:应用打开时主动取消通知,避免重复显示

内容的提问来源于stack exchange,提问作者Muhammad Iqbal Alhadad

火山引擎 最新活动