Се­год­ня мы узна­ем, как в OS Android мож­но кра­сиво и эле­ган­тно соз­дать собс­твен­ный пла­ниров­щик заданий, который будет полезен в тво­их, разуме­ется, свет­лых делах. Для это­го нам пот­ребу­ется исклю­читель­но стан­дар­тный инс­тру­мен­тарий: Android SDK от Google и редак­тор кода Eclipse c пла­гином ADT.
 

Постановка задачи

Наш пла­ниров­щик дол­жен уметь:

  • до­бав­лять неог­раничен­ное количес­тво заданий на про­изволь­ные момен­ты вре­мени;
  • вы­пол­нять фоновые задания (даже если устрой­ство зас­нуло);
  • бла­гопо­луч­но пережи­вать перезаг­рузку устрой­ства.
 

Включить сигнализацию!

Для выпол­нения пос­тавлен­ной задачи в Android пре­дус­мотрен спе­циаль­ный Java-класс AlarmManager (менед­жер сиг­нализа­ций), поз­воля­ющий уста­новить сиг­нализа­цию (Alarm), сра­баты­вающую в уста­нов­ленное вре­мя или с задан­ной пери­одич­ностью. В качес­тве фор­мата даты и вре­мени сиг­нализа­ции выс­тупа­ет ста­рое доб­рое UNIX-time, то есть вре­мя, выражен­ное в мил­лисекун­дах, про­шед­ших с 1 янва­ря 1970 года.

Преж­де чем дви­гать­ся даль­ше, необ­ходимо про­яснить ситу­ацию, ког­да у нас, нап­ример, зап­ланиро­ваны сот­ни или даже тысячи заданий. Впол­не оче­вид­но, что уста­нов­ка столь­ких же сиг­нализа­ций не самая луч­шая идея (хм, новый век­тор в сто­рону DoS?). Вмес­то это­го мы будем исполь­зовать толь­ко одну сиг­нализа­цию, вклю­чать которую будем динами­чес­ки. Наш пла­ниров­щик будет выпол­нять все задания пос­ледова­тель­но.

 

Архитектура проекта

Наш про­ект будет раз­делен на четыре модуля (см. исходник):

  1. Глав­ная активность (Main.java).
  2. Ши­роко­веща­тель­ный при­емник (AlarmReceiver.java).
  3. Фо­новый сер­вис (AlarmService.java).
  4. Ба­за дан­ных заданий (AlarmDB.java).
Приступаем к работе
Прис­тупа­ем к работе

Глав­ная активность (Main.java), по сути, явля­ется гра­фичес­ким интерфей­сом нашего при­ложе­ния, сос­тоящим из стан­дар­тных ком­понен­тов — тек­сто­вых меток (TextView), полей вво­да (EditView) и глав­ной кноп­ки (Button). Основная его цель — опре­делить парамет­ры задания: дату и вре­мя, час­тоту сра­баты­вания, само задание.

Не­пос­редс­твен­ная уста­нов­ка сиг­нализа­ций, а так­же весь полез­ный фун­кци­онал наших заданий будет выпол­нять­ся в фоновом сер­висе (AlarmService.java). Так как жур­нал у нас доб­рый, в качес­тве боевой наг­рузки будем прос­то выводить уве­дом­ление со зву­ком. Фоновый сер­вис (класс Service) в Android выпол­няет­ся в глав­ном потоке при­ложе­ния и тре­бует исполь­зования мно­гопо­точ­ности при дли­тель­ных опе­раци­ях (в про­тив­ном слу­чае рис­куешь уви­деть раз­дра­жающее ANR — «При­ложе­ние не отве­чает»). Для наших целей прек­расно подой­дет класс IntentService (нас­ледник Service), который, во‑пер­вых, самос­тоятель­но соз­даст рабочий поток, во‑вто­рых, авто­мати­чес­ки завер­шится пос­ле выпол­нения задачи.

Ба­за дан­ных (AlarmDB.java) — вспо­мога­тель­ный класс для помеще­ния заданий в базу SQLite и их извле­чения. Струк­тура таб­лицы пред­став­лена во врез­ке. Оста­нав­ливать­ся на этом модуле смыс­ла нет, так как в нем исполь­зуют­ся стан­дар­тные SQL-зап­росы вида INSERT/SELECT.

У тебя навер­няка воз­ник воп­рос: для чего нужен широко­веща­тель­ный при­емник (AlarmReceiver.java)? Дело в том, что при перезаг­рузке устрой­ства все сиг­нализа­ции AlarmManager’а про­пада­ют, что, естес­твен­но, нас не устра­ивает (см. пос­ледний пункт пос­танов­ки задачи). Единс­твен­ное, что мы можем сде­лать, — поп­росить сис­тему сра­зу пос­ле заг­рузки запус­тить наш код, вос­ста­нав­лива­ющий все задания (о соот­ветс­тву­ющем раз­решении для при­ложе­ния смот­ри врез­ку). Подоб­ная про­цеду­ра дол­жна быть обер­нута в широко­веща­тель­ный при­емник (BroadcastReceiver) и опре­деле­на в (public void onReceive). В нашем слу­чае эта фун­кция будет опре­деле­на ста­тичес­кой (static void scheduleAlarms), что поз­волит ее вызывать из любого мес­та при­ложе­ния. Любой широко­веща­тель­ный при­емник дол­жен отра­ботать мак­сималь­но быс­тро — Android отво­дит для это­го мак­симум десять секунд, ина­че работа при­емни­ка, веро­ятно (зависит от мно­гих фак­торов — вер­сии Android, осо­бен­ностей про­шив­ки про­изво­дите­ля), будет прер­вана. Это­го вре­мени впол­не дос­таточ­но, что­бы запус­тить наш фоновый сер­вис.

Ре­зюми­руя, сос­тавим алго­ритм работы при­ложе­ния. Пос­ле опре­деле­ния задания в глав­ной активнос­ти оно помеща­ется в базу дан­ных, пос­ле чего вызыва­ется ста­тичес­кая фун­кция scheduleAlarms из клас­са AlarmReceiver (она же вызыва­ется и при заг­рузке устрой­ства). Единс­твен­ное, что дела­ет эта фун­кция, — запус­кает фоновый сер­вис со спе­циаль­ным клю­чом SET_ALARM, уве­дом­ляющим о необ­ходимос­ти извлечь из базы дан­ных бли­жай­шее задание и пос­тавить его на сиг­нализа­цию. Как толь­ко она сра­бота­ет, сно­ва запус­кает­ся фоновый сер­вис, но уже с клю­чом RUN_ALARM, который выпол­няет экс­плойт (шучу, выводит уве­дом­ление) и сно­ва вызыва­ет scheduleAlarms, но уже для уста­нов­ки сле­дующей сиг­нализа­ции, то есть сле­дующе­го задания, и так далее. Таким обра­зом, мы фак­тичес­ки исполь­зуем толь­ко одну сиг­нализа­цию при неог­раничен­ном количес­тве заданий.

На этом теория закан­чива­ется и начина­ется, как ни стран­но, кодинг.

Структура БД

Для прос­тоты наша база дан­ных будет сос­тоять из одной таб­лицы:

CREATE TABLE tbSheduler (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
utime NUMERIC,
msg TEXT ),

где _id — пер­вичный ключ, utime — зап­ланиро­ван­ное вре­мя задания, msg — текст сооб­щения. В боевом про­екте вмес­то поля типа TEXT пра­виль­нее было бы ука­зать уни­каль­ный ключ (FOREIGN KEY), опи­сыва­ющий задание в рам­ках дру­гих свя­зан­ных таб­лиц.

 

Главная активность

Для интерфей­са глав­ной активнос­ти, рас­положен­ной в фай­ле Main.java, мы выберем три поля вво­да: edDate — для опре­деле­ния даты и вре­мени сра­баты­вания задания, edRepeat — для ука­зания необ­ходимо­го количес­тва дней пов­тора, edMessage — собс­твен­но для тек­ста сооб­щения. В методе onCreate с помощью findViewByIdпо­луча­ем ссыл­ки на все ком­понен­ты GUI, а для кноп­ки bGo опре­делим обра­бот­чик:

public void bGo_click(View v){
Calendar c = getCalendarFromDate(edDate.getText().toString());
int repeat = getIntFromString(edRepeat.getText().toString());
String message = edMessage.getText().toString();
// Проверки на корректность ввода пропущены
AlarmDb db = new AlarmDb(this);
for (int i = 0; i < repeat; i++){
db.insertAlarm(c.getTimeInMillis(), message);
c.add(Calendar.DAY_OF_MONTH, 1);
}
AlarmReceiver.scheduleAlarms(this);
}

Автозагрузка

Раз­решение на авто­заг­рузку при­ложе­ния нуж­но зап­росить в фай­ле‑манифес­те (AndroidManifest.xml):

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

Ког­да будешь пуб­ликовать свое при­ложе­ние в Play Market, пра­вила хороше­го тона рекомен­дуют в опи­сании при­ложе­ния доход­чиво объ­яснить поль­зовате­лю, зачем, собс­твен­но, тебе нуж­на авто­заг­рузка, а то ведь поль­зователь может и испу­гать­ся.

Здесь вспо­мога­тель­ная фун­кция getCalendarFromDate воз­вра­щает экзем­пляр клас­са Calendar с уста­нов­ленной в качес­тве парамет­ра датой. Обра­ти вни­мание на задание фор­мата стро­ки с датой: String fmt = "dd.MM.yyyy HH:mm", разуме­ется, ты можешь опре­делить свой. Фун­кция getIntFromString баналь­но перево­дит стро­ку в чис­ло для перемен­ной repeat.

GUI главной активности нашего приложения в редакторе...
GUI глав­ной активнос­ти нашего при­ложе­ния в редак­торе...
...и на смартфоне
...и на смар­тфо­не

Сле­дующий за этим цикл помеща­ет задачу в базу дан­ных с помощью метода insertAlarm (long UTIME, String MSG), при­нима­юще­го в качес­тве парамет­ров UNIX-time и текст сооб­щения. Затем, исполь­зуя метод (с.add), экзем­пляр кален­даря сдви­гает­ся на один день впе­ред. Весь цикл про­дол­жает­ся repeat раз. Мы исполь­зуем кон­стан­ту Calendar.DAY_OF_MONTH для при­бав­ления одно­го дня, но так­же мож­но вос­поль­зовать­ся кон­стан­тами Calendar.MONTH, Calendar.YEAR, Calendar.HOUR_OF_DAY, Calendar.MINUTE и подоб­ными. Если вто­рой параметр отри­цатель­ный — ука­зан­ный параметр вычита­ется.

info

Что­бы пос­мотреть все уста­нов­ленные сиг­нализа­ции на смар­тфо­не, мож­но вос­поль­зовать­ся коман­дой

adb shell dumpsys alarm > alarm.txt

Ана­лиз получен­ного фай­ла поз­волит выявить все при­ложе­ния, зло­упот­ребля­ющие про­буж­дени­ем устрой­ства (и сле­дова­тель­но, раз­ряжа­ющие батарею), а так­же узнать, нап­ример, как час­то Gmail про­веря­ет поч­ту, а Hangout зап­рашива­ет сооб­щения.

В завер­шении фун­кции вызыва­ется ста­тичес­кий метод scheduleAlarms(this), опре­делен­ный в широко­веща­тель­ном при­емни­ке AlarmReceiver и ини­циали­зиру­ющий уста­нов­ку сиг­нализа­ции. В качес­тве парамет­ра мы переда­ем ссыл­ку на текущий кон­текст. У это­го метода будет еще одна фор­ма, но об этом поз­днее.

 

Широковещательный приемник

Код при­емни­ка (AlarmReceiver.java) пред­став­лен ниже:

public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
scheduleAlarms(context);
}
static void scheduleAlarms(Context ctxt) {
long NOW = Calendar.getInstance().getTimeInMillis();
startAlarmService(ctxt, NOW);
}
static void scheduleAlarms(Context ctxt, long TIME) {
startAlarmService(ctxt, TIME);
}
static void startAlarmService(Context ctxt, long UTIME) {
Intent i = new Intent(ctxt, AlarmService.class);
i.setAction(AlarmService.SET_ALARM);
i.putExtra("utime", UTIME);
ctxt.startService(i);
}
}

Дан­ный код тре­бует некото­рых пояс­нений. Класс AlarmReceiver нас­леду­ется от супер­клас­са BroadcastReceiver, который тре­бует реали­зации абс­трак­тно­го метода public void onReceive (Context context, Intent intent), сра­баты­вающе­го при пос­тупле­нии опре­делен­ного намере­ния (в нашем слу­чае — intent.action.BOOT_COMPLETED).

Лю­бой при­емник дол­жен быть обя­затель­но зарегис­три­рован в манифес­те при­ложе­ния (AndroidManifest.xml):

<receiver android:name=".AlarmReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

Ме­тод scheduleAlarms име­ет две фор­мы: с ука­зани­ем вре­мени (рас­ширен­ная), назовем его базовым, отно­ситель­но которо­го нуж­но ста­вить сиг­нализа­цию, и без. Если вре­мя не ука­зыва­ется, то в качес­тве базово­го исполь­зует­ся текущее вре­мя, то есть мы будем ста­вить сиг­нализа­ции на бли­жай­шее вре­мя (опе­режа­ющее текущее) и отбро­сим все сиг­нализа­ции в прош­лом. Рас­ширен­ная вер­сия при­годит­ся нам поз­же.

Вен­цом работы широко­веща­тель­ного при­емни­ка будет вызов метода startAlarmService, запус­кающе­го фоновый сер­вис. В качес­тве парамет­ра в StartService исполь­зует­ся намере­ние. Намере­ние (Intent) — осно­ва основ механиз­ма для вызова раз­личных ком­понен­тов в Android. Таковы­ми явля­ются активнос­ти, сер­висы, при­емни­ки и про­чее. В нашем слу­чае в качес­тве намере­ния ука­зыва­ется экзем­пляр нашего сер­виса (AlarmService). Все намере­ния класс IntentService обра­баты­вает по оче­реди. Для переда­чи стро­ково­го клю­ча SET_ALARM вос­поль­зуем­ся методом SetAction, дела­ющим наше намере­ние уни­каль­ным в сис­теме (кста­ти, для уни­каль­нос­ти в кон­стан­ту SET_ALARM вклю­чено имя пакета: SET_ALARM = "com.example.cron_SET_ALARM"). Так­же намере­ния могут содер­жать поля типа «ключ = зна­чение», чем мы и вос­поль­зуем­ся, ука­зав в качес­тве клю­ча utime базовое вре­мя (метод putExtra). Наконец, мы подош­ли к самой глав­ной час­ти нашего пла­ниров­щика — фоново­му сер­вису.

 

Фоновый сервис

Для начала фоновый сер­вис необ­ходимо зарегис­три­ровать в манифес­те при­ложе­ния:

<service android:name=".AlarmService" >
</service>

Код сер­виса условно мож­но раз­делить на две час­ти: уста­нов­ка сиг­нализа­ции и собс­твен­но сама сиг­нализа­ция. Нач­нем с уста­нов­ки (для эко­номии цен­ного мес­та в жур­нале отла­доч­ная печать опу­щена):

public class AlarmService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
long TIME = extras.getLong("utime");
if (intent.getAction().equalsIgnoreCase(SET_ALARM)) {
AlarmDb db = new AlarmDb(this);
db.open();
Cursor c = db.select_NEXT_ALARM(TIME);
AlarmManager mgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(this, AlarmService.class);
if (c.getCount() > 0) {
c.moveToFirst();
int UTIMEi = c.getColumnIndex("utime");
long UTIME = c.getLong(UTIMEi);
i.putExtra("utime", UTIME);
i.setAction(AlarmService.RUN_ALARM);
PendingIntent pi = PendingIntent.getService(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.cancel(pi);
mgr.set(AlarmManager.RTC_WAKEUP, UTIME, pi);
} else {
PendingIntent pi = PendingIntent.getService(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.cancel(pi);
}
c.close();
db.close();
}
}

Вни­матель­ный читатель навер­няка заметит, что в клас­се AlarmManager уже есть под­ходящая фун­кция для задания пери­оди­чес­ких сиг­нализа­ций — setRepeating, но, тем не менее, мы ее не исполь­зуем. Эта фун­кция, безус­ловно, хороша в том слу­чае, если у тебя все­го десяток сиг­нализа­ций, а если их в десять раз боль­ше? Управлять всем этим зоопар­ком будет гораз­до слож­нее, чем в нашем слу­чае. Кро­ме того, как ты сам видишь, руч­ной рас­чет пери­одич­ности с помощью Java-кален­даря (Calendar) не пред­став­ляет осо­бой слож­ности.

Об­работ­ку оче­ред­ного пос­тупа­юще­го намере­ния сер­вис (IntentService) выпол­няет в методе protected void onHandleIntent (Intent intent). Получив намере­ние (Intent), мы извле­каем дан­ные, запако­ван­ные ранее в широко­веща­тель­ном при­емни­ке, — базовое вре­мя (getLong) и флаг (getAction). Если флаг соот­ветс­тву­ет кон­стан­те SET_ALARM, под­клю­чаем­ся к базе дан­ных и получа­ем запись по сфор­мирован­ному в db.select_NEXT_ALARM(TIME) зап­росу:

SELECT utime FROM tbSheduler WHERE utime >= TIME ORDER BY utime LIMIT 1

То есть мы зап­рашива­ем бли­жай­шее задание со вре­менем, опе­режа­ющим базовое (которое мы извлек­ли в перемен­ную TIME с помощью getLong). Разуме­ется, при опре­деле­нии пер­вого задания в TIMEока­жет­ся текущее вре­мя сис­темы.

По­лучив задание и опре­делив его вре­мя в перемен­ной UTIME, мы прис­тупа­ем непос­редс­твен­но к уста­нов­ке сиг­нализа­ции. Как и ранее, в широко­веща­тель­ном при­емни­ке мы дол­жны соз­дать намере­ние, свя­зан­ное с нашим фоновым сер­висом: Intent i = new Intent(this, AlarmService.class), а так­же ука­зать флаг сра­баты­вания сиг­нализа­ции: i.setAction(AlarmService.RUN_ALARM) и вре­мя сра­баты­вания: i.putExtra("utime", UTIME). Так как наше намере­ние отло­жено до вре­мени сра­баты­вания, оно дол­жно быть обер­нуто в обо­лоч­ку ожи­дающе­го намере­ния (PendingIntent):

PendingIntent pi = PendingIntent.getService(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);

Флаг FLAG_UPDATE_CURRENT озна­чает, что, если намере­ние уже соз­дано, необ­ходимо лишь обно­вить его дан­ные: в нашем слу­чае — ключ‑зна­чение utime (без это­го фла­га намере­ние не изме­нит­ся).
Итак, у нас все готово для уста­нов­ки сиг­нализа­ции:

AlarmManager mgr = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC_WAKEUP, UTIME, pi);

Сна­чала мы получа­ем дос­туп к сис­темной служ­бе менед­жера сиг­нализа­ций, ука­зывая соот­ветс­тву­ющую кон­стан­ту — Context.ALARM_SERVICE. Для уста­нов­ки сиг­нализа­ции мы будем исполь­зовать метод set (о методе setRepeating см. врез­ку), переда­вая ему в качес­тве вто­рого парамет­ра вре­мя сра­баты­вания сиг­нализа­ции UTIME, а в качес­тве треть­его — уже под­готов­ленное нами ожи­дающее намере­ние pi. При­мене­ние в качес­тве пер­вого парамет­ра AlarmManager.RTC_WAKEUP при­водит к тому, что сиг­нализа­ция будет про­буж­дать устрой­ство (см. вто­рой пункт пос­танов­ки задачи). Если бы нам не нуж­но было будить устрой­ство, то мы бы мог­ли ука­зать флаг AlarmManager.RTC для дос­тавки намере­ния уже пос­ле про­буж­дения устрой­ства.

Спящий Android

Нес­мотря на то что мы исполь­зовали флаг AlarmManager.RTC_WAKEUP, сущес­тву­ет ненуле­вая веро­ятность того, что раз­бужен­ный с его помощью фоновый сер­вис (IntentService) фак­тичес­ки не успе­ет начать обра­баты­вать намере­ния. Все дело в бло­киров­ке про­буж­дения, которой обла­дают широко­веща­тель­ный при­емник (BroadcastReceiver) и менед­жер сиг­нализа­ций (AlarmManager) для про­буж­дения устрой­ства, но которая осво­бож­дает­ся пос­ле выпол­нения кода в onReceive при­емни­ка. Фоновый сер­вис подоб­ной бло­киров­кой не обла­дает. На стра­ницах «Хакера» уже под­нималась эта тема в № 6 за 2013 год (см. «Задачи на собесе­дова­ниях»).

Ес­ли в базе дан­ных боль­ше нет под­ходящих заданий, сиг­нализа­ция отме­нит­ся с помощью метода cancel(pi), при­нима­юще­го в качес­тве парамет­ра то же ожи­дающее намере­ние.

Наш планировщик за работой
Наш пла­ниров­щик за работой

Нам оста­лось рас­смот­реть толь­ко код сра­баты­вания сиг­нализа­ции. Здесь, как ты сам уви­дишь, все три­виаль­но:

long TIME = extras.getLong("utime");
...
if (intent.getAction().equalsIgnoreCase(RUN_ALARM)) {
AlarmDb db = new AlarmDb(this);
db.open();
Cursor c = db.select_ALARM_BY_UTIME(TIME);
int MSGi = c.getColumnIndex("msg");
String title, text;
c.moveToFirst();
if (!c.isAfterLast()) {
title = "Тревога!";
text = c.getString(MSGi);
String link = "Нажми меня... ";
showNotification(title, text, link);
}
c.close();
db.close();
TIME += 500;
AlarmReceiver.scheduleAlarms(this, TIME);
}

При сра­баты­вании сиг­нализа­ции зап­рашива­ем из базы дан­ных все задания с вре­менем TIME. Зап­рос в db.select_ALARM_BY_UTIME(TIME) выг­лядит сле­дующим обра­зом:

SELECT msg FROM tbSheduler WHERE utime = TIME

www

Го­товый класс WakefulIntentService, сох­раня­ющий семан­тику исполь­зования IntentService, но с бло­киров­кой про­буж­дения на GitHub

Фун­кция showNotification выводит в ста­тус­ной стро­ке новое уве­дом­ление, сос­тоящее из двух ико­нок (икон­ки при­ложе­ния и уве­дом­ления), заголов­ка (title), тек­ста (text) и допол­нитель­ной информа­ции (info):

private void showNotification(String title, String text, String info) {
Intent intent = new Intent(this, Main.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
NotificationCompat.Builder b = new NotificationCompat.Builder(this);
b.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_SOUND)
.setContentTitle(title)
.setContentText(text)
.setContentInfo(info)
.setContentIntent(pendingIntent)
.setSmallIcon(R.drawable.ic_notify)
.setLargeIcon(bm)
.setWhen(System.currentTimeMillis())
.setTicker(title);
Notification n = b.getNotification();
notificationManager.notify(NOTIFY_GROUP, n);
}

С помощью кон­стан­ты Context.NOTIFICATION_SERVICE мы зап­рашива­ем дос­туп к сис­темной служ­бе уве­дом­лений. Далее запол­няем все поля, опре­деля­ющие над­писи и икон­ки (отли­чия уве­дом­лений в раз­ных вер­сиях Android пред­став­лены на скрин­шоте), а так­же вре­мя сра­баты­вания — setWhen(System.currentTimeMillis()), то есть немед­ленно. Метод setContentIntent зада­ет уже зна­комое тебе ожи­дающее намере­ние, вызыва­емое при нажатии на уве­дом­ление. В нашем слу­чае мы прос­то запус­каем единс­твен­ную активность нашего пла­ниров­щика (Main.java). Ука­зан­ная в setDefaults кон­стан­та Notification.DEFAULT_SOUND опре­деля­ет в качес­тве зву­ково­го соп­ровож­дения уве­дом­ления стан­дар­тный зву­ковой сиг­нал.

Уведомление: Android 2 vs Android 4
Уве­дом­ление: Android 2 vs Android 4

Как ты мог заметить, мы обра­бота­ли толь­ко одно задание на кон­крет­ное вре­мя, обра­бот­ка осталь­ных ста­нет тво­им домаш­ним задани­ем.

Иконки в уведомлениях

При выводе уве­дом­лений в ста­тус­ной стро­ке неп­лохо бы вмес­те с тек­стом показы­вать соот­ветс­тву­ющую икон­ку. В Eclipse есть удоб­ный инс­тру­мент, помога­ющий соз­дать такую икон­ку сра­зу для всех раз­меров экра­на и вер­сии Android. Что­бы его выз­вать в кон­текс­тном меню сво­его про­екта, выбери New → Other → Android Icon Set → Notification Icons. В появив­шемся мас­тере открой гра­фичес­кий файл или ука­жи необ­ходимый текст (см. скрин­шот). Все воз­можные виды тво­ей икон­ки будут соз­даны авто­мати­чес­ки.

Создаем иконку
Соз­даем икон­ку

Ра­зоб­равшись с одной сиг­нализа­цией, мы уве­личи­ваем вре­мя на пол­секун­ды и вызыва­ем рас­ширен­ную вер­сию scheduleAlarms что два задания могут быть столь близ­ки по вре­мени, что вто­рое прос­то не сра­бота­ет, пос­коль­ку еще не завер­шилось пер­вое. Переда­вая в качес­тве парамет­ра вре­мя пер­вого с неболь­шим сдви­гом (хотя бы в 1 мс), мы гаран­тиру­ем, что вто­рое задание, пусть и с вынуж­денной задер­жкой, сра­бота­ет. К сло­ву, в нашем пла­ниров­щике задания ста­вят­ся с точ­ностью до минуты, но нич­то не меша­ет уве­личить точ­ность до секунд или даже мил­лисекунд.

Отладочная печать незаменима при разработке
От­ладоч­ная печать незаме­нима при раз­работ­ке
 

Выводы

Се­год­ня мы поз­накоми­лись с механиз­мом работы широко­веща­тель­ного при­емни­ка в OS Android, запус­тили фоновый сер­вис, нем­ного порабо­тали с базой дан­ных, научи­лись выводить сис­темные уве­дом­ления, соз­дали пару ожи­дающих намере­ний. И конеч­но же, под­готови­ли неп­лохой кар­кас для пла­ниров­щика, наращи­вая который ты смо­жешь соз­дать свою уни­каль­ную вер­сию Cron’a.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии