Урок 119. PendingIntent – флаги, requestCode. AlarmManager


В этом уроке:

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


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

С 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:

<string name="button1">Button1</string>
<string name="button2">Button2</string>


Создаем Receiver.java:

package ru.startandroid.develop.p1191pendingintent;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class Receiver extends BroadcastReceiver {

 
final String LOG_TAG = "myLogs";

 
@Override
 
public void onReceive(Context ctx, Intent intent) {
   
Log.d(LOG_TAG, "onReceive");
    Log.d
(LOG_TAG, "action = " + intent.getAction());
    Log.d
(LOG_TAG, "extra = " + intent.getStringExtra("extra"));
 
}
}

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


Layout-файл main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick1"
android:text="@string/button1">
</Button>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick2"
android:text="@string/button2">
</Button>
</LinearLayout>

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


MainActivity.java:

package ru.startandroid.develop.p1191pendingintent;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

 
final String LOG_TAG = "myLogs";

  NotificationManager nm;
  AlarmManager am;
  Intent intent1;
  Intent intent2;
  PendingIntent pIntent1;
  PendingIntent pIntent2;

 
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);
    nm =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    am =
(AlarmManager) getSystemService(ALARM_SERVICE);
 
}

 
public void onClick1(View view) {

   
intent1 = createIntent("action 1", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);

    intent2 = createIntent
("action 2", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, 0);

    compare
();
 
}

 
public void onClick2(View view) {

  }

 
Intent createIntent(String action, String extra) {
   
Intent intent = new Intent(this, Receiver.class);
    intent.setAction
(action);
    intent.putExtra
("extra", extra);
   
return intent;
 
}

 
void compare() {
   
Log.d(LOG_TAG, "intent1 = intent2: " + intent1.filterEquals(intent2));
    Log.d
(LOG_TAG, "pIntent1 = pIntent2: " + pIntent1.equals(pIntent2));
 
}

 
void sendNotif(int id, PendingIntent pIntent) {
   
Notification notif = new Notification(R.drawable.ic_launcher, "Notif "
       
+ id, System.currentTimeMillis());
    notif.flags |= Notification.FLAG_AUTO_CANCEL;
    notif.setLatestEventInfo
(this, "Title " + id, "Content " + id, pIntent);
    nm.notify
(id, notif);
 
}
}

Метод 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:

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
   
    intent2 = createIntent
("action", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, 0);
   
    compare
();
 
}

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:

  public void onClick1(View view) {
   
intent1 = createIntent("action 1", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);

    intent2 = createIntent
("action 2", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, 0);

    compare
();

    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent2);
 
}

Будем создавать разные 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 одинаковым

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
   
    intent2 = createIntent
("action", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, 0);
   
    compare
();
   
    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent2);
 
}

Сохраняем, запускаем. Жмем 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 похож на существующий, то она отменит (удалит) существующий.

Перепишем onClick1:

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
   
    intent2 = createIntent
("action", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, PendingIntent.FLAG_CANCEL_CURRENT);
   
    compare
();
   
    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent2);
 
}


Сохраняем, запускаем, жмем 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 из создаваемого.

Перепишем onClick1:

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
   
    intent2 = createIntent
("action", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT);
   
    compare
();
   
    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent2);
 
}


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

onReceive
action = action
extra = extra 2

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

onReceive
action = action
extra = extra 2

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


FLAG_ONE_SHOT

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

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
   
    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent1);
 
}

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

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

onReceive
action = action
extra = extra 1

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

onReceive
action = action
extra = extra 1

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

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

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, PendingIntent.FLAG_ONE_SHOT);
   
    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent1);
 
}

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

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

onReceive
action = action
extra = extra 1

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


FLAG_NO_CREATE

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

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

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
    Log.d
(LOG_TAG, "pIntent1 created");
 
}
 
 
public void onClick2(View view){
   
intent2 = createIntent("action", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, PendingIntent.FLAG_NO_CREATE);
   
if (pIntent2 == null) Log.d(LOG_TAG, "pIntent2 is null");
     
else Log.d(LOG_TAG, "pIntent2 created")
 
}


Сохраняем, запускаем. Жмем 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.

Перепишем методы:

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
    sendNotif
(1, pIntent1);
 
}

 
public void onClick2(View view) {
   
pIntent1.cancel();
    Log.d
(LOG_TAG, "cancel pIntent1");
 
}


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

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


requestCode

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

Перепишем onClick1:

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 1, intent1, 0);
   
    intent2 = createIntent
("action", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 2, intent2, 0);
   
    compare
();
   
    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent2);
 
}

Используем 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. Делается это так:

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    Uri data1 = Uri.parse
(intent1.toUri(Intent.URI_INTENT_SCHEME));
    intent1.setData
(data1);
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
   
    intent2 = createIntent
("action", "extra 2");
    Uri data2 = Uri.parse
(intent2.toUri(Intent.URI_INTENT_SCHEME));
    intent2.setData
(data2);
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, 0);
   
    compare
();
   
    sendNotif
(1, pIntent1);
    sendNotif
(2, pIntent2);
 
}

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


AlarmManager

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

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

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

  public void onClick1(View view) {
   
intent1 = createIntent("action 1", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);

    intent2 = createIntent
("action 2", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, 0);

    Log.d
(LOG_TAG, "start");
    am.set
(AlarmManager.RTC, System.currentTimeMillis() + 4000, pIntent1);
    am.setRepeating
(AlarmManager.ELAPSED_REALTIME,
        SystemClock.elapsedRealtime
() + 3000, 5000, pIntent2);
 
}

 
public void onClick2(View view) {
   
am.cancel(pIntent2);
 
}

В 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, то сработает только второй, а первый будет удален при создании второго.

Пример:

  public void onClick1(View view) {
   
intent1 = createIntent("action", "extra 1");
    pIntent1 = PendingIntent.getBroadcast
(this, 0, intent1, 0);
   
    intent2 = createIntent
("action", "extra 2");
    pIntent2 = PendingIntent.getBroadcast
(this, 0, intent2, 0);
   
    Log.d
("qwe", "start");
    am.set
(AlarmManager.RTC, System.currentTimeMillis() + 2000, pIntent1);
    am.set
(AlarmManager.RTC, System.currentTimeMillis() + 4000, pIntent2);
 
}

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

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

8:03:59.669: start

8:04:03.740: onReceive

8:04:03.740: action = action

8:04:03.750: extra = extra 1

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

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

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


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

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