Android - AlarmManager не запускает BroadcastReceiver для отображения локальных уведомлений - PullRequest
4 голосов
/ 05 апреля 2020

Я пишу молитвенное приложение, которое требует, чтобы приложение отображало Локальные уведомления в PrayerTimes. Время молитвы и разное для каждого дня, поэтому я использую следующий фрагмент кода, чтобы показать уведомление о местоположении от BroadcastReceiver и сразу после этого расписания следующее уведомление.

Проблема в том, что приложение должно открываться не реже одного раза в день, чтобы уведомления продолжали работать с указанными c временами.

Есть ли способ запланировать BroadcastReceiver с помощью Alarm Диспетчер для запуска локальных уведомлений без открытия приложения?

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)
            }
        }
    }
}

AlarmReceiver.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 часа, когда наступает следующий день, уведомления прекращают работу, требуя, чтобы приложение было открыто по крайней мере один раз в день.

Ответы [ 3 ]

1 голос
/ 08 апреля 2020

Вы можете создать PrayerWorker с помощью Androidx Work Manager, чтобы запланировать фоновый API / настройку уведомлений (все без использования открывающего приложения и вместо этого запускаются при получении уведомления.

Документация может быть найдена здесь

Ваша функция setNextPrayerAlarm вместо этого переместит логи c на 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:

Привет, я должен был быть более понятным в методе, извините. Есть два способа сделать этот повторяющийся сигнал тревоги.

Метод 1: Измените OneTimeWorkRequest на PeriodicWorkRequest (см. Документацию здесь ). Используя этот метод, вы можете указать, как вы хотите, чтобы работник, который устанавливает повторение (например, каждый 2 часа, каждые 24 часа.) Минимальный интервал составляет 15 минут.

Метод 2: Измените PrayerWorker, чтобы также запланировать следующий рабочий. Это будет использовать тот факт, что вы можете добавить задержку к запуску рабочий (см. документацию), который ич в этом случае будет 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
            }
    }
0 голосов
/ 14 апреля 2020

Я не знаю, на какой android уровень Sdk нацеливается ваше приложение, но Google изменил его API s, начиная с O. Объявление неявного широковещательного получателя из манифеста не будет работать.

As part of the Android 8.0 (API level 26) Background Execution Limits, apps that target the API level 26 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest. However, several broadcasts are currently exempted from these limitations. Apps can continue to register listeners for the following broadcasts, no matter what API level the apps target.

подробнее об этом здесь: https://developer.android.com/guide/components/broadcast-exceptions

0 голосов
/ 10 апреля 2020

Попробуйте выполнить следующие шаги

1. В NotificationUtils.kt добавьте флаг намерения FLAG_RECEIVER_FOREGROUND

, как показано ниже, который будет работать для Вы

        val alarmIntent = Intent(App.instance?.applicationContext, AlarmReceiver::class.java)
        alarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        alarmIntent.putExtra("prayer", name)
        ....
        ...

2. Также убедитесь, что вы зарегистрировали AlarmReceiver в манифесте

, как показано ниже

<receiver android:name="com.myapp.receiver.AlarmReceiver">
    </receiver>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...