понедельник, 15 февраля 2016 г.

[AlarmManager] Автозапуск приложения в назначенное время

На форуме (http://fire-monkey.ru/), не раз просили рассказать, как использовать AlarmManager в RAD Studio или как написать свой будильник :). В этой статье, я расскажу, как его использовать, примеров будет два. За основу взят пример «автозапуска приложения», однако в данной статье автозапуск будет происходить в назначенное время.

Update 23.02.17. Разъяснил различия между двумя вариантам из статьи.

Начнём с теории.

Теоретическое содержание:
1)    AlarmManager
2)    PendingIntent

AlarmManager



AlarmManager – это диспетчер оповещений или проще говоря, что-то вроде планировщика, который в назначенное время выполнит запланированное задание.

Методов у  AlarmManager полно, я упомяну три основных (самых популярных), остальные вы найдёте в оф. справке.

Методы:
  • set(int type, long triggerAtMillis, PendingIntent operation) – устанавливает разовое задание
  • setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) – устанавливает повторяющееся задание
  • cancel(PendingIntent operation) – отменяет задание
В качестве параметров, методы принимают:
- тип задания (int type)
  • ELAPSED_REALTIME - ориентируются на время от начала загрузки ОС
  • ELAPSED_REALTIME_WAKEUP - ориентируются на время от начала загрузки ОС и пробуждает устройство из сна.
  • RTC - ориентируются на системное время
  • RTC_WAKEUP - ориентируются на системное время и пробуждает устройство из сна.
- Время для запуска задания, указывается в миллисекундах.
- (setRepeating, long intervalMillis) Интервал в миллисекундах между повторами
- PendingIntent – Ожидающее намерение с заданием, которое мы хотим выполнить

Особенности:
  • нельзя запланировать два одинаковых задания
  • после перезагрузки/выключения устройства, все задания будут удалены

PendingIntent

Официальная справка Google: http://developer.android.com/intl/ru/reference/android/app/PendingIntent.html

Основные методы:
  • getActivity –для запуска Activity
  • getBroadcast – для BroadcastReciver
  • getService – для Service
Параметры, принимаемые методами, на примере getBroadcast:
getBroadcast(
  • mContext, - контекст, активность или служба
  • 1, - идентификатор запроса, позволяет создавать похожие ожидающие намерения
  • intent, - намерение
  • 0); - флаги ожидающего намерения (флаги намерений полезны в управлении временем жизни ожидающего намерения. Однако в данном случае время жизни поддерживается диспетчером оповещений. Например, чтобы отменить ожидающее намерение, нужно запросить его отмену у диспетчера оповещений.)

На этом, краткий курс теории закончился. Давайте посмотрим, как же это всё использовать на деле.

Практика.

Как я говорил в самом начале, за основу взят пример автозапуска приложения.

Пример №1 – запускаем приложение, с помощью BroadcastReceiver и метода PendingIntent.getBroadcast

Пример №2 – запускаем приложение, используя только метод PendingIntent.getActivity

Update 23.02.17. Друзья, ниже в комментарии уведомили меня(?!), что непонятны различия между этими двумя вариантами.
Всё просто:
  • Для первого примера мы используем метод getBroadcast, а значит нам обязательно нужен BroadcastReceiver, а значит, нужен java класс и файл classes.dex. Опять же, я всегда стараюсь описывать все действия подробно, но вы можете опустить часть с classes.dex и просто добавить JAR в Project Manager'е, подробности читайте тут Как добавить jar библиотеку в проект 
  • Для второго примера мы используем метод getActivity, поэтому нам не нужны BroadcastReceiver, JAVA class, JAR, classes.dex, manifest, т.е. достаточно просто написать код из второго примера и всё заработает.


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

Общая функция для примеров:
function getTimeAfterInSecs(Seconds: Integer): Int64;
var
  Calendar: JCalendar;
begin
  Calendar := TJCalendar.JavaClass.getInstance;
  Calendar.add(TJCalendar.JavaClass.SECOND, Seconds);
  Result := Calendar.getTimeInMillis;
end;
Данная функция возвращает время срабатывания в миллисекундах.

Пример №1

Создаём java-файл с классом «AlarmReceiver», генерим файл classes.dex. Как это сделать, я писал уже не один раз, например, почитайте последние статьи в блоге. Больше всего подойдёт статья «[BroadcastReceiver] Автозапуск приложения после перезагрузки ОС».

Update 23.02.17. Действительно для XE7 и выше. BAT - файл для JAVA 1.7 - 1.8
Для тех, кто уже понимает или наоборот не хочет понимать, для чего нужен classes.dex. Можете его не создавать.
Вы можете создать JAR файл с вашим JAVA классом и добавить его через Project Manager (Как добавить jar библиотеку в проект, в данном случае, обёртку делать не нужно).
Для создания JAR файла, вам необходимо использовать bat файл с таким содержимым:
@echo off
setlocal

if x%ANDROID% == x set ANDROID=C:\Android\sdk
set ANDROID_PLATFORM=%ANDROID%\platforms\android-24
set PROJ_DIR=%CD%
set VERBOSE=0

echo.
echo Compiling the Java service activity source files
echo.
mkdir output 2> nul
mkdir output\classes 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=-verbose
javac -source 1.7 -target 1.7 %VERBOSE_FLAG% -Xlint:deprecation -cp %ANDROID_PLATFORM%\android.jar -d output\classes src\com\TestReceiver\BootCompletedReceiver.java

echo.
echo Creating jar containing the new classes
echo.
mkdir output\jar 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=v
jar c%VERBOSE_FLAG%f output\jar\test_classes.jar -C output\classes com

echo.
echo Now we have the end result, which is output\jar\test_classes.jar

:Exit

pause

endlocal

После выполнения этого bat файла, у вас появится файл output\jar\test_classes.jar. Его то и добавьте через Project Manager (подробности в статье, указанной чуть выше).


Код в java-файле:
package com.TestReceiver;

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

public class AlarmReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
            Intent TestLauncher = new Intent();
            TestLauncher.setClassName(context, "com.embarcadero.firemonkey.FMXNativeActivity");
            TestLauncher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(TestLauncher);
    }
}

Создаём приложение, кидаем кнопку, подключаем необходимые для работы модули (юниты):
uses
  Androidapi.JNI.JavaTypes, Androidapi.JNI.App, Androidapi.Helpers,
  Androidapi.JNI.GraphicsContentViewText;

пишем код в событии OnClick:
procedure TForm2.Button1Click(Sender: TObject);
var
  Intent: JIntent;
  PendingIntent: JPendingIntent;
begin
  // Создаём Интент
  Intent := TJIntent.Create;
  Intent.setClassName(TAndroidHelper.Context, StringToJString('com.TestReceiver.AlarmReceiver'));

  // Оборачиваем Интент в PendingIntent
  PendingIntent := TJPendingIntent.JavaClass.getBroadcast(TAndroidHelper.Context, 1, Intent, 0);

  // Устанавливаем оповещение
  TAndroidHelper.AlarmManager.&set(TJAlarmManager.JavaClass.RTC_WAKEUP, getTimeAfterInSecs(30),
    PendingIntent);
end;

Делаем сборку (Build) проекта, вносим правку в файл AndroidManifest.template.xml:
<receiver android:name="com.TestReceiver.AlarmReceiver" />

Заменяем стандартный classes.dex и теперь всё готово. Приложение, уже можно запустить, но можете сразу добавить в приложение второй пример :)

Пример №2

Этот пример независим от первого примера. Т.е. в этом примере вам НЕ нужно всё то, что мы делали в первом примере (BroadcastReceiver, JAVA class, JAR, classes.dex, manifest). Можно смело копировать код из этого примера и использовать в своём приложении, ничего другого не нужно делать.

Добавляем на форму вторую кнопку и пишем код:

procedure TForm2.Button2Click(Sender: TObject);
var
  Intent: JIntent;
  PendingIntent: JPendingIntent;
begin
  // Создаём Интент
  Intent := TJIntent.Create;
  Intent.setClassName(TAndroidHelper.Context, StringToJString('com.embarcadero.firemonkey.FMXNativeActivity'));

  // Оборачиваем Интент в PendingIntent
  PendingIntent := TJPendingIntent.JavaClass.getActivity(TAndroidHelper.Context, 1, Intent, 0);

  // Устанавливаем оповещение
  TAndroidHelper.AlarmManager.&set(TJAlarmManager.JavaClass.RTC_WAKEUP, getTimeAfterInSecs(30),
    PendingIntent);
end;

Запускаем приложением и тестим.

Вот так, просто, можно использовать AlarmManager в RAD Studio.


Видео

Исходный код: Скачать с Google Drive

10 комментариев:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
  2. Этот комментарий был удален автором.

    ОтветитьУдалить
  3. Спасибо за интересный, полезный пример! Можете помочь разобраться с getService, каким образом возможно запустить AndroidServiceStartCommand?

    ОтветитьУдалить
  4. Андрей, здравствуйте!
    Пишу не в тему, контактов Ваших не нашел... уже и в мой круг добавил и в чат...
    Вопрос вот в чем:
    Нигде не можем найти как средствами ред-студио запретить в апк использовать приложение для устройств экраном меньше 1024*600...
    http://docwiki.embarcadero.com/ - непонятно - на английском и даже не нашли рубрику где менять... вручную не получается манифест изменить, да и некорректно...
    Вы не подскажите путь где это в ред-студио?
    У ребенка 12-ти лет проект, а я в этом не смыслю ничего.

    С уважением,
    Антон Епифанов

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. В чате бываю редко. Контакт указан на странице http://delphifmandroid.blogspot.ru/p/blog-page_26.html.
      По вашей теме уже ответил тут: http://fire-monkey.ru/topic/2623-%D0%B7%D0%B0%D0%BF%D1%80%D0%B5%D1%82-%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9-%D1%8D%D0%BA%D1%80%D0%B0%D0%BD%D0%BE%D0%B2/#comment-14923

      Удалить
  5. Привет.
    Для варианта 2 никакой Dex файл не нужен. Также не нужен и Jar файл.
    Также ничего не нужно в манифест добавлять, т.к. вызывается activity напрямую.
    Но это только для варианта 2. Для первого варианта (Broadcast) все это нужно (правда начиная с версии XE7 можно сразу добавлять Jar файлы в проект, надеюсь автор это осветит).

    Я проверял на новом проекте с вариантом 2 - все работает. Также см. вопрос на эту тему http://stackoverflow.com/questions/42368123/how-to-pass-boolean-or-integer-to-intent-and-read-it-to-detect-that-my-activi

    ОтветитьУдалить
    Ответы
    1. Здравствуйте.
      Кому адресовано ваше сообщение? Но всё же отвечу.
      Да, всё правильно, для второго варианта dex не нужен. Видимо недостаточно понятно описал в статье, различия между getBroadcast и getActivity, а также между двумя тестовыми вариантами. Сейчас добавлю дополнительное, прямое указание на отличия.

      По поводу JAR файла. Для подобных статей, я всегда стараюсь детально описать каждый шаг дальнейших действий. Это направлено на то, чтобы человек, начал понимать, что он делает, а не просто копипастить и потом на форуме кучу тем создавать с вопросами, как и что.
      Если вам хочется добавлять JAR через Project Manager, то и пожалуйста, http://delphifmandroid.blogspot.ru/2015/03/jar.html

      Удалить
    2. Добавил во все статьи дополнительную информацию по генерации JAR файла. И постарался разделить примеры в тексте, чтобы стали понятнее различия между ними.

      Удалить
  6. When I run the project, it makes the following mistake. Why could it be?

    Undeclared identifier:TAndroidHelper

    Version : xe7

    ОтветитьУдалить
  7. Hello,
    Very good example, thanks for that.

    I have a different request: If the alarm is set and the app is not running it will start. If it is running the app should not come into the foreground but fire an event. Is that possible?

    regards
    Kostas

    ОтветитьУдалить