понедельник, 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

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

  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

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