Создание повторяющихся уведомлений под Android с помощью Alarm Manager

Создание повторяющихся уведомлений под Android с помощью Alarm Manager

Я недавно столкнулся с небольшой проблемой создания локальных уведомлений при разработке приложения под android. В конце концов проблема была успешно решена и здесь я опишу этот случай.

В демонстрационном приложении я буду использовать Alarm Manager для создания повторяющихся локальных уведомлений. Это приложение будет создавать уведомление каждое утро в 8 часов. Будут рассмотрены ситуации, когда приложение будет закрыто или даже убито системой во время появления уведомления. При клике на уведомление будет открываться главная активность приложения.

Обязательно предоставляйте пользователю возможность включать/отключать уведомления

Это очень важно! Возможно, вы захотите сделать это в Settings Activity. При включении уведомлений пользователем, AlarmManager начнёт посылать уведомления каждое утро. Время уведомлений может быть настроено или оставаться по-умолчанию. В этом приложении мы будем запускать их в 8 часов утра.

Планирование уведомлений с Alarm Manager

Есть два типа Сигналов (Alarms):

  • Абсолютное время (как на часах) — RTC
  • Пройденное время (с момента загрузки) — Elapsed

Более подробно об этом можно прочитать в документации (англ.).

Пример RTC:

public static void scheduleRepeatingRTCNotification(Context context, String hour, String min) {
    //получаем экземпляр calendar для получения возможности задать время срабатывания сигнала
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    //Настраиваем время (здесь 8 утра) отправки ежедневного уведомления
    calendar.set(Calendar.HOUR_OF_DAY,
            Integer.getInteger(hour, 8),
            Integer.getInteger(min, 0));

    //Настраиваем intent к классу, в котором будет обработан отправленный сигнал
    Intent intent = new Intent(context, AlarmReceiver.class);
    //Настраиваем pending intent
    alarmIntentRTC = PendingIntent.getBroadcast(context, ALARM_TYPE_RTC, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    //получаем экземпляр службы AlarmManager
    alarmManagerRTC = (AlarmManager)context.getSystemService(ALARM_SERVICE);

    //Настраиваем сигнал для пробуждения устройства каждый день в заданное время.
    //AlarmManager.RTC_WAKEUP отвечает за пробуждение устройства, поэтому нужно аккуратно его использовать.
    //Используйте RTC когда вам нет необходимости пробуждать устройство, и уведомления будут приходить только когда оно не спит
    //Мы используем здесь RTC.WAKEUP только для демонстрации
    alarmManagerRTC.setInexactRepeating(AlarmManager.RTC_WAKEUP,
            calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, alarmIntentRTC);
}

Пример Elapsed:

public static void scheduleRepeatingElapsedNotification(Context context) {
    Intent intent = new Intent(context, AlarmReceiver.class);

    alarmIntentElapsed = PendingIntent.getBroadcast(context, ALARM_TYPE_ELAPSED, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    alarmManagerElapsed = (AlarmManager)context.getSystemService(ALARM_SERVICE);

    //Неточный ежедневный сигнал от момента загрузки устройства. Это наилучший выбор и
    //в этом случае он автоматически изменяется при смене локали/часового пояса
    //Настроим сигнал получения уведомления 15 минут после перезагрузки в каждые 15 минут
    alarmManagerElapsed.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
            SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_FIFTEEN_MINUTES,
            AlarmManager.INTERVAL_FIFTEEN_MINUTES, alarmIntentElapsed);
}
Класс AlarmReceiver (BroadcastReceiver)

Класс AlarmReceiver наследует BroadcastReceiver. В нём обрабатываются отправленные уведомления от AlarmManager в заданное время.

public class AlarmReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
         //Intent для вызова приложения по клику.
         //Мы хотим запустить наше приложение (главную активность) при клике на уведомлении
         Intent intentToRepeat = new Intent(context, MainActivity.class);
         //настроим флаг для перезапуска приложения
         intentToRepeat.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

         PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, NotificationHelper.ALARM_TYPE_RTC, intentToRepeat, PendingIntent.FLAG_UPDATE_CURRENT);

         //Создаём уведомление
         Notification repeatedNotification = buildLocalNotification(context, pendingIntent).build();

         //Отправляем уведомление
         NotificationHelper.getNotificationManager(context).notify(NotificationHelper.ALARM_TYPE_RTC, repeatedNotification);
     }

     public NotificationCompat.Builder buildLocalNotification(Context context, PendingIntent pendingIntent) {
         NotificationCompat.Builder builder =
                 (NotificationCompat.Builder) new NotificationCompat.Builder(context)
                 .setContentIntent(pendingIntent)
                 .setSmallIcon(android.R.drawable.arrow_up_float)
                 .setContentTitle("Morning Notification")
                 .setAutoCancel(true);

         return builder;
     }
 }

Внимание: не забудьте добавить тег в файл AndroidManifest.xml:

<receiver android:name=".notification.AlarmReceiver"/>
Вспомогательный класс NotificationHelper

Здесь исходник вспомогательного класса для создания уведомлений.

Читайте также:  Создание первого приложения Flutter, часть 2
Не теряем уведомления при перезагрузке устройства

Так устроено в android, что при перезагрузке все сигналы теряются. Чтобы наши сигналы работали в этом случае, необходимо отслеживать перезапуск устройства и восстановить их.

Конфигурация AndroidManifest.xml

Добавим необходимые разрешения в AndroidManifest.xml:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
...>

    <receiver android:name=".notification.AlarmBootReceiver"
            android:enabled="false">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"></action>
        </intent-filter>
    </receiver>

</application>

Мы прописали в получателе: android:enabled=»false» — для того, чтобы пользователь в настройках приложения мог включить/отключить уведомления. Вы можете прописать true, чтобы уведомления работали всегда после перезапуска/включения устройства.

Реализуем Boot Receiver

Класс AlarmBootReceiver несложный:

public class AlarmBootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            //для демонстрации запустим только один тип уведомлений при перезапуске
            NotificationHelper.scheduleRepeatingElapsedNotification(context);
        }
    }
}

Включим boot receiver когда пользователь включает уведомления. То есть при включении уведомлений в настройках, мы запускаем и отслеживание запуска устройства, и сами уведомления.

ComponentName receiver = new ComponentName(context, AlarmBootReceiver.class);
PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP);

Когда пользователь отписывается от уведомлений, вы можете отменить все сигналы и отключить boot receiver вот так:

ComponentName receiver = new ComponentName(context, AlarmBootReceiver.class);
PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP);

В принципе это всё.

Исходный код примера.

Однако есть некоторые сложности с китайскими устройствами, поэтому дополнительно опишу что необходимо сделать для работы уведомлений на них:

1. Дополнительно к разрешению RECEIVE_BOOT_COMPLETED пропишем разрешение на отслеживание события QUICKBOOT_POWERON, которое практикуется в устройствах HTC и некоторых других:

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.QUICKBOOT_POWERON" />

2. Для получателя AlarmBootReceiver пропишем (также в AndroidManifest.xml) следующие действия:

       <receiver
            android:name=".notification.AlarmBootReceiver"
            android:enabled="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.REBOOT" />
            </intent-filter>
        </receiver>

где «android.intent.action.REBOOT» — необходимо для устройств Xiaomi.

В этом примере мы реализовали повторяющиеся уведомления. Для одиночных нотификаций код придётся немного переписать.

Подготовлено по материалам «Scheduling Repeating Local Notifications using Alarm Manager» и ответов на StackOverflow.

Создание повторяющихся уведомлений под Android с помощью Alarm Manager

Разработчик: java, kotlin, c#, javascript, dart, 1C, python, php.

Для связи: @ighar

Leave a Comment

Чтобы не пропустить новые статьи, оставь свой Email

Поздравляем вы подписаны на новости ТехноДжем!

TВо время отправки данных произошла ошибка. Попробуйте ещё раз

Оставляя свою почту