Android - AlarmManager 不触发 BroadcastReceiver 以显示本地通知

标签 android kotlin broadcastreceiver alarmmanager android-notifications

我正在编写一个祈祷应用程序,该应用程序需要在 PrayerTimes 上显示本地通知。每天的祈祷时间都不同,因此我使用以下代码显示来自 BroadcastReceiver 的位置通知,并在该安排之后立即通知下一个通知。

问题是,应用程序必须每天至少打开一次,通知才能在其特定时间继续触发。

有没有办法使用警报管理器安排 BroadcastReceiver 以在不打开应用程序的情况下触发本地通知?

fun MakkahPrayer.setNotificationForPrayer(prayer: Prayer, date: Date) {
    val app = App.instance!!.applicationContext
    val preferences = PreferenceManager.getInstance(app)

    if(!preferences.isPrayerAlarmSet(prayer.name)) {
        val calendar = Calendar.getInstance()
        calendar.add(Calendar.DAY_OF_YEAR, 0)

        val dayOfYear = calendar[Calendar.DAY_OF_YEAR]

        NotificationUtils.instance.setNotification(date.time, prayer.name, dayOfYear.toString())
        preferences.setPrayerIsAlarmOn(prayer.name, true)
    }
}

NotificationUtils.kt
class NotificationUtils {
    companion object {
        val instance = NotificationUtils()
    }

    fun setNotification(timeInMilliSeconds: Long, name: String, day: String) {

        val cal = Calendar.getInstance()
        cal.time = Date()
        val millis = cal.timeInMillis

        if (timeInMilliSeconds > 0 && timeInMilliSeconds > millis) {

            val key = name + day

            val alarmManager =
                App.instance?.getSystemService(Activity.ALARM_SERVICE) as AlarmManager
            val alarmIntent = Intent(App.instance?.applicationContext, AlarmReceiver::class.java)

            alarmIntent.putExtra("prayer", name)
            alarmIntent.putExtra("timestamp", timeInMilliSeconds)
            alarmIntent.putExtra("notificationID", key)

            val calendar = Calendar.getInstance()
            calendar.timeInMillis = timeInMilliSeconds

            val pendingIntent = PendingIntent.getBroadcast(
                App.instance,
                timeInMilliSeconds.toInt(),
                alarmIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )

            alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
            } else {
                alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMilliSeconds, pendingIntent)
            }
        }
    }
}

报警接收器.kt
class AlarmReceiver : BroadcastReceiver() {
    companion object {
        private lateinit var mNotification: Notification

        const val CHANNEL_ID = "CHANNEL_ID"
        const val CHANNEL_NAME = "Prayer Notification"
    }

    override fun onReceive(context: Context, intent: Intent) {
        val manager = createChannel(context)
        showNotification(context, intent, manager)
        setNextPrayerAlarm(intent)
    }

    private fun setNextPrayerAlarm(intent: Intent) {
        if (intent.extras != null) {
            val prayerName = intent.extras!!.getString("prayer", "Prayer")
            val prayer = Prayer.valueOf(prayerName)
            MakkahPrayer.instance.removePrayerNotification(prayer)
        }

        val (nextPrayer, date) = MakkahPrayer.instance.nextPrayerWithTime()
        MakkahPrayer.instance.setNotificationForPrayer(nextPrayer, date)
    }

    private fun showNotification(
        context: Context,
        intent: Intent,
        notificationManager: NotificationManager
    ) {
        var timestamp: Long = 0
        var prayerName = "Prayer"

        var mNotificationId = ""

        if (intent.extras != null) {
            timestamp = intent.extras!!.getLong("timestamp")
            prayerName = intent.extras!!.getString("prayer", "Prayer")
            mNotificationId = intent.extras!!.getString("notificationID", "")
        }

        if (timestamp > 0) {
            val notifyIntent = Intent(context, MainActivity::class.java)

            val title = capitalize(prayerName)
            val message = "It is $title time"

            notifyIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK

            val calendar = Calendar.getInstance()
            calendar.timeInMillis = timestamp

            val pendingIntent = PendingIntent.getActivity(
                context,
                0,
                notifyIntent,
                PendingIntent.FLAG_UPDATE_CURRENT
            )
            val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)

            mNotification = NotificationCompat.Builder(context, NotificationService.CHANNEL_ID)
                .setContentIntent(pendingIntent)
                .setSmallIcon(R.drawable.ic_alarm_black_24dp)
                .setLargeIcon(
                    BitmapFactory.decodeResource(
                        context.resources,
                        R.mipmap.ic_launcher
                    )
                )
                .setSound(uri)
                .setAutoCancel(true)
                .setContentTitle(title)
                .setStyle(
                    NotificationCompat.BigTextStyle()
                        .bigText(message)
                )
                .setColor(ContextCompat.getColor(context, R.color.colorSecondary))
                .setContentText(message).build()

            notificationManager.notify(timestamp.toInt(), mNotification)
        }
    }

    @SuppressLint("NewApi")
    private fun createChannel(context: Context): NotificationManager {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val soundUri =
                Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + App.instance?.applicationContext?.packageName + "/" + R.raw.azan)

            val audioAttributes = AudioAttributes.Builder()
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .setUsage(AudioAttributes.USAGE_NOTIFICATION)
                .build()

            val notificationManager =
                context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

            val importance = NotificationManager.IMPORTANCE_HIGH
            val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance)
            channel.enableVibration(true)
            channel.setShowBadge(true)
            channel.canShowBadge()
            channel.enableLights(true)
            channel.lightColor = context.getColor(R.color.colorSecondary)
            channel.description =
                context.getString(R.string.notification_channel_description)
            channel.setSound(soundUri, audioAttributes)
            channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            notificationManager.createNotificationChannel(channel)

            return notificationManager
        } else {
            return context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        }
    }
}

编辑:
使用以下方法后,如下面的人所述,它仍然无法正常工作,即应用程序必须在 24 小时内至少打开一次,才能产生本地通知。
我正在寻找一种解决方案,其中应用程序不应该在 4.5 天内打开,并且应用程序应该提供本地通知。
目前,它只能工作 24 小时,当第二天到来时,通知会停止触发,要求应用程序每天至少打开一次。

最佳答案

您可以创建 PrayerWorker使用 Androidx Work Manager安排通知的后台 API/设置(全部不使用打开应用程序,而是在收到通知时触发。

可以找到文档 here

您的 setNextPrayerAlarm函数将改为将逻辑移至 PrayerWorker看起来像这样:

private fun setNextPrayerAlarm(intent: Intent) {
    if (intent.extras != null) {
        val oneTimeWorkRequestBuilder = OneTimeWorkRequest.Builder(PrayerWorker::class.java)
        oneTimeWorkRequestBuilder.setInputData(`put your input data here`)
        WorkManager.getInstance(context).enqueueUniqueWork("setPrayerWorker",ExistingWorkPolicy.REPLACE, oneTimeWorkRequestBuilder.build())
    }            
}

PrayerWorker可能看起来像这样
class PrayerWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
    override fun doWork(): Result {
        //Insert logic to determine alarms to set 
        return Result.success() //for success case
    }
}

编辑 1:

嗨,我应该在方法上更清楚,对不起。有两种方法可以使它成为重复警报。

方法一:
修改OneTimeWorkRequestPeriodicWorkRequest (请参阅文档 here)。使用此方法,您可以指定希望设置的工作人员如何重复(例如,每 2 小时、每 24 小时)。最短间隔为 15 分钟。

方法二:
修改PrayerWorker还安排下一个 worker 。这将利用这样一个事实,即您可以为工作人员的触发添加延迟(请参阅文档),在这种情况下为 24 小时。下面是示例
    class PrayerWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
            override fun doWork(): Result {
                //Insert logic to determine alarms to set 
                val oneTimeWorkRequestBuilder = OneTimeWorkRequest.Builder(PrayerWorker::class.java)
                oneTimeWorkRequestBuilder.setInputData(`put your input data here`)
oneTimeWorkRequestBuilder.setInitialDelay(`initialDelay`, `timeUnit`)
              WorkManager.getInstance(context).enqueueUniqueWork("setPrayerWorker",ExistingWorkPolicy.REPLACE, oneTimeWorkRequestBuilder.build())
                return Result.success() //for success case
            }
    }

关于Android - AlarmManager 不触发 BroadcastReceiver 以显示本地通知,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61044129/

相关文章:

java - ActionBar 选项卡内容重叠

ListView 通知数据集更改后Android CAB销毁

Android Kotlin 在其他 Activity 中调用函数

javascript - 如何在 kotlin 中的 js 数组上使用 push() 或 pop()?

android - Fresco 的 prefetchToBitmapCache 的 callerContext 参数应该是什么?

android - 如何从 AsyncTask 更新 ListView?

list - 如何在Kotlin中按其值对HashMap <String,Int>顺序进行排序?

android - 如何使用广播发送数据

c# - Xamarin Android - BroadcastReceiver 问题 - 无法实例化接收器

java - 警报管理器未触发广播接收器