Что такое alarmmanager android

Программируем будильник или использование AlarmManager в Android

При разработке приложения порой бывает необходимо выполнять какие-то действия в будущем. Например, вы хотите создать будильник или периодически отправлять данные на свой сайт в строго определенное время. Для решения подобных задач в Android используется классAlarmManager, который позволяет выполнять код в требуемый момент времени, даже если ваше приложение не запущено. То-есть AlarmManager — аналог corn в Linux или планировщика задач Windows. Вы говорите, что такой-то кусок кода должен выполниться тогда-то и AlarmManager обеспечивает запуск кода. В англоязычный литературе таймер, который выполняет код в требуемый момент времени обозначается терминомalarm. Давайте в рамках данной статьи называть этот таймербудильником, чтобы отделить это понятие от таймера.

Несколько слов о классе AlarmManager

Фактически класс AlarmManager обеспечивает доступ к сервису планировки задач Android. Для получения объекта этого класса нужно вызвать методContext.getSystemService(Context.ALARM_SERVICE).AlarmManagerрегистрирует в системе интент и когда наступает обозначенное время, AlarmManager запускает этот интент. Если момент вызова приложение закрыто, то оно будет вновь запущено. AlarmManager нужно использовать только в случае, если код должен быть выполнен даже при закрытом приложении, во всех других случаях рекомендуется использовать классHandler.

Класс AlarmManager довольно прост. В нашем распоряжении шесть методов:

  • void cancel(PendingIntent operation) — отменяет установленный таймер для переданного в качестве параметра интента.
  • void set (int type, long triggerAtMillis, PendingIntent operation) — устанавливает будильник, который сработает один раз.
  • void setInexactRepeating (int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) — установка будильника с неточным повторением.
  • setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)— установка будильника с точным повторением.
  • setTime(long millis) — установка системного времени.
  • setTimeZone(String timeZone) — установка временной зоны, выбранной по-умолчанию.

ЗдесьPendingIntent— класс, который представляет интент и связанное с его выполнением действие. Передавая PendingIntent другому приложению Вы тем самым даете ему право запускать кусок кода из своей программы.

В качестве типа будильника (параметрtype) может быть передано одно из значений ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC и RTC_WAKEUP. ELAPSED_REALTIME и ELAPSED_REALTIME_WAKEUP — время задается с момента загрузки устройства, а RTC и RTC_WAKEUP — системное время в UTC. Для ELAPSED_REALTIME и RTC если устройство находится в спящем режиме в момент срабатывания будильника, то связанный с ним интент будет вызван позже, когда пользователь выведет устройство из спящего режима. Для ELAPSED_REALTIME_WAKEUP и RTC_WAKEUP если устройство находится в спящем режиме, оно немедленно пробуждается и вызывается интент.

Программируем приложение-будильник для Android

Давайте в качестве примера разработаем приложение — будильник. При запуске программы пользователю будет показано окно с тремя кнопками. С их помощью пользователь сможет: установить будильник, который срабатывает один раз; будильник, который будет срабатывать периодически; а также удалить ранее установленные будильники.

Создадим новый проект в макет шаблона добавим три кнопки

КнопкаbtStartустанавливает повторяющийся будильник, кнопкаbtCancel— отменяет его. КнопкаbtOneTime— создает не повторяющийся будильник. Прямо в шаблоне мы указали методы, которые будут вызываться при нажатии на кнопки: startRepeatingTimer, cancelRepeatingTimer и onetimeTimer соответственно. Код этих методов будут приведен в классе Activity.

Для работы сAlarmManagerнапишем отдельный класс. В качестве базового класса используем BroadcastReciever. Наш класс будет управлять зарегистрированным с помощью AlarmManager интентом. Мы переопределим методonReceive(), который будет вызываться после получения интента. Внутри метод onReceive() мы должны попытаться получить связанные с интентом параметры. В своей программе мы будем использовать один параметр ONE_TIME, который позволяет определить, относится ли интент к однократно срабатывающему будильнику или нет. После того, как значение ONE_TIME получено, пользователю показывается соответствующее сообщение.

Также в этом классе определим методы setAlarm(), cancelAlarm() и onetimeTimer(). Конечно эти методы можно было определить и в другом месте, мы включили их в данный класс из соображения простоты примера.

  • Метод setAlarm() устанавливает повторяющийся будильник с помощью метода setRepeating(). Этому методу требуется четыре параметра: тип будильника, время запуска (устанавливаем текущий момент), интервал в миллисекундах, интент, который будет вызываться при срабатывании будильника.
  • Метод cancelAlarm() отменяет зарегистрированный ранее будильник с помощью вызова метода cancel(), которому перезается в качестве параметра интент. При совпадении этого параметра с зарегистрированным ранее интентом, произойдет удаление будильника.
  • Метод onetimeTimer() создает будильник, который срабатывает один раз. Делается с помощью метода set(), которому передается три параметра: тип будильника, время запуска, вызываемый интент.

Ниже приводится файл манифеста. Обратите внимание, для корректной работы программе необходимо дать разрешениеWAKE_LOCK, поскольку мы используем блокировку потока в методе onReceive(). Также тут мы регистрируем AlarmManagerBroadcastReceiver, как получатель широковещательных сообщений

Теперь давайте напишем класс, в котором реализуем обработчики кнопок. Здесь мы создадим экземпляр описанного выше класса AlarmManagerBroadcastReciever и будем вызывать методы setAlarm(), cancelAlarm() и setOnetime().

При надатии на кнопки будут установлен будильник. Исходный код проекта можно скачатьздесь.

Источник

Полный список

— подробно разбираемся с PendingIntent
— используем AlarmManager

Перед тем, как продолжить тему виджетов, нам надо будет знать две темы: PendingIntent и AlarmManager.

Читайте также:  Factory setting android что это

С PendingIntent мы уже мельком встречались при работе с уведомлениями (Notifications) в Уроке 99. Вспомним, как создается этот объект. Мы создаем обычный Intent с нужными нам параметрами. Затем мы создаем PendingIntent каким-либо из методов: getActivity, getBroadcast, getService. Здесь имя метода определяет, какой тип объекта будет вызван с помощью нашего Intent. Ведь мы сами, когда отправляем Intent, явно говорим системе, что хотим вызвать: startActivity, sendBroadcast, startService.

Методы создания PendingIntent одинаковы по параметрам и требуют на вход:

context – тут все понятно.
requestCode – в хелпе почему-то написано «currently not used». Но при этом использовать их вполне можно. Это своего рода ключи, чтобы отличать один PendingIntent от других при необходимости.
intent – этот Intent будет впоследствии использован для вызова activity/broadcast/service (в зависимости от метода создания)
flags – флаги, влияющие на поведение и создание PendingIntent

Создавая и передавая другому приложению PendingIntent (с Intent внутри), мы предоставляем ему возможность и полномочия отправлять Intent от нашего имени. В этом уроке мы будем работать с PendingIntent. Увидим, что при создании есть свои нюансы, и разберемся, как можно использовать флаги и requestCode.

Вторая тема урока – AlarmManager. Это планировщик, которому можно передать PendingIntent и сказать, когда именно надо будет его использовать. В общем, что-то типа будильника. Там особо не о чем говорить, поэтому рассмотрим его совсем кратко.

PendingIntent

Создадим пример, в котором будем создавать различные PendingIntent и использовать их в Notifications. Результатом выполнения будет вызов BroadcastReceiver, в котором будем логировать входящую информацию.

Project name: P1191_PendingIntent
Build Target: Android 2.3.3
Application name: PendingIntent
Package name: ru.startandroid.develop.p1191pendingintent
Create Activity: MainActivity

Добавим строки в strings.xml:

Это наш Receiver, в который будут падать Intent из PendingIntent. В нем мы читаем Intent и выводим в лог action и один параметр из extra-данных. Не забудьте прописать этот класс в манифесте.

Только две кнопки.

Метод createIntent создает Intent с указанными параметрами.

Метод sendNotif создает уведомление с указанными ID и PendingIntent.

Метод compare выводит в лог результат сравнения Intent и PendingIntent. Для Intent используем метод filterEquals, который сравнивает Intent по action, data и пр., игнорируя extra-данные .

В onClick1 мы, используя все эти методы, будем создавать различные комбинации, и смотреть, что получается. Сейчас мы там создаем пару Intent и PendingIntent и просто сравниваем их, нигде не используя. В качестве флагов и requestCode мы пока используем нули.

В onClick2 пока ничего не пишем.

Все сохраним и запустим приложение.

Жмем Button1, смотрим лог.

intent1 = intent2: false
pIntent1 = pIntent2: false

Видим, что Intent-ы не равны (т.к. у них различается параметр action). А PendingIntent не равны потому, что их Intent не равны между собой.

Давайте теперь сравняем основные части Intent. Перепишем onClick1:

action будут равны, а Extra отличается. Сохраним, запустим. Жмем Button1:

intent1 = intent2: true
pIntent1 = pIntent2: true

Теперь метод filterEquals говорит нам, что Intent равны. Напомню – это потому, что этот метод не учитывает данные в Extra. PendingIntent получились равны, потому что (по версии filterEquals) равны их Intent. Этот момент надо четко понять, т.к. он очень сильно влияет на создание новых PendingIntent.

Попробую расписать алгоритм, как я его понимаю. Когда приложение создает PendingIntent, система его хранит в себе. При этом, Intent создаваемого PendingIntent сравнивается с Intent уже существующих PendingIntent. Если есть PendingIntent с Intent равным Intent создаваемого PendingIntent, то создаваемый PendingIntent получит этот существующий Intent. А Intent, с которым мы его пытались создать будет отброшен.

Т.е. в нашем случае мы создали pIntent1 с intent1, система его хранит. Далее мы создаем pIntent2 с intent2. Система проверяет существующие PendingIntent, и видит, что есть pIntent1 с intent1, и этот intent1 равен intent2. И ваш созданный pIntent2, теперь содержит intent1. А intent2 выкинут. pIntent1 при этом, разумеется, также остается со своим intent1. Просто теперь один Intent будет использоваться двумя PendingIntent-ами.

Можно сказать, что дефолтное поведение дает нам полную копию существующего PendingIntent, вместо создаваемого, если их Intent-ы равны без учета extra-данных.

Давайте убедимся в этом. Перепишем onClick1:

Будем создавать разные Intent, использовать их в PendingIntent и отправлять уведомления.

Все сохраним и запустим. Жмем Button1. Появились два уведомления.

Жмем первое (Title 1), смотрим лог:

onReceive
action = action 1
extra = extra 1

Все верно. Что помещали в intent1, то и получили в результате срабатывания pIntent1.

Жмем второе (Title 2) уведомление:

onReceive
action = action 2
extra = extra 2

Также все верно. Что помещали в intent2, то и получили в результате срабатывания pIntent2.

Теперь сделаем action одинаковым

Сохраняем, запускаем. Жмем Button1. Жмем первое уведомление:

onReceive
action = action
extra = extra 1

Жмем второе уведомление и наблюдаем в логах разрыв шаблона такое:

onReceive
action = action
extra = extra 1

При втором уведомлении сработал pIntent2, в который мы помещали intent2 c другим extra. Но получили мы extra из intent1. Причины описаны выше. Система увидела, что intent1 равен intent2 (без учета данных в Extra) и созданному pIntent2 дала intent1.

Это дефолтное поведение системы, но, к счастью, мы можем на него повлиять. Для этого и нужны флаги и requestCode, которые передаются в метод создания PendingIntent.

Читайте также:  Режим невидимки для андроида

Флаги

Определимся с формулировкой. Фраза «создаваемый PendingIntent похож на существующий» означает, что равны (без extra) Intent-ы двух этих PendingIntent.

FLAG_CANCEL_CURRENT

Если система видит, что создаваемый с таким флагом PendingIntent похож на существующий, то она отменит (удалит) существующий.

Сохраняем, запускаем, жмем Button1, жмем первое уведомление. В логах ничего. pIntent1 был отменен системой в момент создания pIntent2, т.к. они похожи, а pIntent2 содержал флаг FLAG_CANCEL_CURRENT. И т.к. первое уведомление было создано с использованием pIntent1, оно ничего и не сделало.

Жмем второе уведомление.

onReceive
action = action
extra = extra 2

pIntent2 отменил pIntent1, но сам остался и выполнился.

FLAG_UPDATE_CURRENT

Этот флаг включает поведение, обратное дефолтному. Если система видит, что создаваемый с таким флагом PendingIntent похож на существующий, то она возьмет extra-данные Intent создаваемого PendingIntent и запишет их вместо extra-данных Intent существующего PendingIntent. Проще говоря, существующий PendingIntent будет использовать Intent из создаваемого.

Сохраняем, запускаем, жмем Button1, жмем первое уведомление. В логах:

onReceive
action = action
extra = extra 2

Видим, что pIntent1 использовал intent2 вместо intent1. Жмем второе уведомление :

onReceive
action = action
extra = extra 2

Данные из intent2. Получилось, что в обоих PendingIntent использовался intent2.

FLAG_ONE_SHOT

PendingIntent с этим флагом сработает лишь один раз. Сначала сделаем пример без флага. Перепишем onClick1:

Будем использовать один PendingIntent в обоих уведомлениях.

Сохраняем, запускаем, жмем Button1, жмем первое уведомление. В логах:

onReceive
action = action
extra = extra 1

жмем второе уведомление:

onReceive
action = action
extra = extra 1

Все логично. Оба уведомления использовали один pIntent1 с intent1.

Теперь используем флаг:

Этот флаг отменит PendingIntent после первого применения.

Сохраняем, запускаем, жмем Button1, жмем первое уведомление. В логах:

onReceive
action = action
extra = extra 1

Все ок, pIntent1 сработал. Жмем второе уведомление – в логах ничего. pIntent1 не сработал. Система увидела FLAG_ONE_SHOT и отменила PendingIntent после первого вызова. Т.е. PendingIntent с таким флагом может быть использован только один раз.

FLAG_NO_CREATE

PendingIntent не будет создан, если среди существующих нет похожего.

Перепишем onClick1 и onClick2

Сохраняем, запускаем. Жмем Button2, в логах:

pIntent2 is null

pIntent2 не создался, т.к. не было создано похожих PendingIntent. Дальнейшие нажатия будут возвращать тот же результат.

Теперь нажмем Button1, а затем Button2:

pIntent1 created
pIntent2 created

После создания pIntent1, получилось создать и pIntent2 (т.к. похожий pIntent1 существовал). Зачем нужен такой флаг? Единственное, что мне приходит в голову – с его помощью можно проверить существование в системе определенного PendingIntent. Если такой PendingIntent существует, то он вам и вернется (здесь действует дефолтное правило), а если не существует – то получим null.

Как оно там внутри работает

Наверняка назрели вопросы типа: где в системе хранятся эти PendingIntent, как долго они хранятся и когда отменяются (удаляются)?

К сожалению, не знаю. Самому было бы интересно узнать.

В последнем примере:

Если создать pIntent1, закрыть Activity, открыть Activity и создать pIntent2, то он создастся.
Если создать pIntent1, убить процесс, открыть Activity и создать pIntent2, то он создастся.
Если создать pIntent1, переустановить приложение, открыть Activity и создать pIntent2, то он не создастся.

Т.е. переустановка приложения точно скидывает все созданные в нем PendingIntent.

Если есть какие-нить мысли или ссылки на этот счет, пишите на форуме.

Отмена

Вы всегда можете вручную отменить любой PendingIntent методом cancel.

Все сохраним, запустим, жмем Button1. Появилось уведомление. Теперь жмем Button2. pIntent1 отменился. Но при этом, заметьте, уведомление спокойно продолжает себе висеть. Ему неважно, живой его PendingIntent или нет.

Нажимаем на уведомление и видим, что в логах ничего не происходит, т.к. PendingIntent был отменен.

requestCode

Что делать, если у нас есть Intent-ы, различающиеся только extra-данными, и нам все-таки нужно получить разные PendingIntent с ними? Можно использовать requestCode. При их использовании PendingIntent не будут считаться похожими, даже при равных Intent.

Используем requestCode = 1 для pIntent1 и 2 для pInten2.

Сохраняем, запускаем, жмем Button1. В логах:

intent1 = intent2: true
pIntent1 = pIntent2: false

Intent-ы равны, а PendingIntent-ы (благодаря requestCode) – нет.

Жмем первое уведомление

onReceive
action = action
extra = extra 1

Жмем второе уведомление

onReceive
action = action
extra = extra 2

Все сработало, как и ожидалось. Никто ни у кого Intent-ы не менял. Все остались при своих.

Есть еще вариант как сделать разные PendingIntent. Он подходит, если у вас в Intent не используется поле data. Туда можно поместить какие-то свои бессмысленные Uri, содержащие ID, которые просто будут разными, чтобы Intent-ы, а следовательно и PendingIntent-ы получились разными.

Либо можно, чтобы весь Intent преобразовывался в Uri и помещался в data. Делается это так:

Метод toUri преобразует Intent со всем его содержимым и всеми параметрами в строку. parse преобразует строку в Uri. Теперь даже если основные параметры Intent-ов будут одинаковы, то разное содержимое extra-данных сделает полученные Uri разными. И Intent-ы будут отличаться по полю data.

AlarmManager

Разобрались с PendingIntent. Взглянем на AlarmManager. Как я уже написал, да и как понятно из названия объекта, он отвечает за то, чтобы срабатывать по расписанию. «Срабатывать» означает использовать ваш PendingIntent, который вы ему отправите.

Два основных метода: set и setRepeating. Они позволяют запланировать, соответственно, разовое и повторяющееся событие.

Читайте также:  Автомагнитолы с андроидом 1din

Рассмотрим пример. Перепишем методы:

В onClick1 создадим два разных PendingIntent. Первый отправим методом set. На вход метод требует тип «будильника», время срабатывания (в милисекундах) и PendingIntent.

Типы будильника бывают следующие: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC и RTC_WAKEUP. В чем отличие между ними?

Те, название которых начинается с RTC, ориентируются на системное время. И время запуска таких будильников надо указывать относительно System.currentTimeMillis. Т.е. это RTC и RTC_WAKEUP.

Те, название которых начинается с ELAPSED_REALTIME, ориентируются на время от начала загрузки оси (включения устройства). Время запуска таких будильников надо указывать относительно SystemClock.elapsedRealtime(). Т.е. это ELAPSED_REALTIME и ELAPSED_REALTIME_WAKEUP.

Те, в названии которых есть WAKEUP, при срабатывании будут выводить устройство из спячки. Т.е. это ELAPSED_REALTIME_WAKEUP и RTC_WAKEUP.

Тут надо не путать спячку с выключенным экраном. Экран может быть выключен, но устройство вовсю будет работать. А когда задач нет устройство уходит в спячку, вырубая процессор для экономии энергии. Если будильники с типом не WAKEUP должны сработать, а устройство в это время спит, то их запуск откладывается до пробуждения. А WAKEUP-будильники разбудят устройство.

Вернемся к методу set. Используем здесь тип RTC, а, значит, время запуска (второй параметр метода) указываем относительно системного времени. Укажем текущее время + 4 секунды. Т.е. будильник сработает через 4 секунды после текущего времени создания. А когда он сработает, он выполнит pIntent1 (третий параметр метода).

Второй будильник ставим методом setRepeating. Он похож на set, только позволяет задать еще период повтора срабатывания. Для примера используем здесь тип ELAPSED_REALTIME. С таким типом время запуска мы должны указать, через сколько милисекунд после старта системы сработает будильник. Мы берем время уже прошедшее со старта (SystemClock.elapsedRealtime()), тем самым получая текущее время, и прибавляем к нему 3 секунды. Период повтора (третий параметр метода) сделаем равным 5 секунд. При срабатывании будет выполнен pIntent2.

Т.е. после того, как мы нажмем кнопку у нас установятся два будильника.

Первый – однократный, через 4 секунды от текущего времени, pIntent1.

Второй – повторяющийся каждые 5 сек., с первым запуском через 3 секунды от текущего времени.

В onClick2 мы будем выключать второй будильник, чтобы он у нас не работал вечно. Для этого выполняем метод cancel с указанием PendingIntent, который используется в будильнике.

Все сохраняем, запускаем приложение. Жмем Button1. В логах наблюдаем, как начали срабатывать будильники. Секунд через 20 жмем кнопку Button2, движуха в логах прекратилась. Смотрим логи:

07:55:22.380: start
07:55:25.450: onReceive
07:55:25.450: action = action 2
07:55:25.459: extra = extra 2
07:55:26.430: onReceive
07:55:26.430: action = action 1
07:55:26.430: extra = extra 1
07:55:30.499: onReceive
07:55:30.499: action = action 2
07:55:30.499: extra = extra 2
07:55:35.429: onReceive
07:55:35.429: action = action 2
07:55:35.429: extra = extra 2
07:55:40.450: onReceive
07:55:40.450: action = action 2
07:55:40.450: extra = extra 2

Второй будильник сработал через 3 секунды после старта и далее срабатывал каждые 5 сек, пока мы его не выключили методом cancel. А первый сработал один раз, через 4 секунды. Все, как и задумывалось.

Есть еще метод setInexactRepeating. Он аналогичен методу setRepeating, но периодичность его срабатывания не всегда точно такая, какую вы указали. Он может подстраивать запуск вашего будильника под будильники, которые будут выполнены примерно в то же время. Сделано это для экономии энергии, чтобы не будить два раза устройство, если оно спит. Судя по хелпу погрешность запуска может быть значительной, так что используйте аккуратнее.

Расскажу еще несколько полезных фактов из жизни будильников.

Если вы запланируете будильник с просроченным временем запуска – он выполнится сразу же.

После перезагрузки устройства все будильники стираются.

Если вы попытаетесь создать два разных будильника на основе одного или похожих PendingIntent, то сработает только второй, а первый будет удален при создании второго.

Создаем похожие PendingIntent и запланируем их в два разных будильника – через 2 и 4 секунды.

Сохраняем, запускаем. Жмем Button1. В логах видим:

8:04:03.740: action = action

8:04:03.750: extra = extra 1

Видим, что пришел Intent из pIntent1, который мы использовали для первого будильника, но пришел он через 4 секунды. Что произошло? Выше мы уже рассмотрели, что произойдет при создании похожих PendingIntent — второй будет использовать Intent первого. Кроме того AlarmManager определил, что pIntent1 (используемый в первом будильнике) и pIntent2 (используемый во втором) похожи, и решил, что негоже иметь разные будильники, с одинаковыми PendingIntent. И оставил нам только последний созданный будильник, т.е. второй.

Как решить проблему одинаковости PendingIntent, мы уже рассмотрели выше. Повторяться не буду.

Тема вроде несложная для понимания, но достаточно сложная для объяснения. Все аспекты и варианты рассмотреть в одном уроке трудновато, поэтому если у вас остались какие-либо вопросы, то самым лучшим способом получить ответы на них будут тесты.

На следующем уроке:

— обрабатываем нажатия на виджет

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Оцените статью