Важно (9.07.22)

Если картинки в постах не отображаются, зайдите в блог через прокси. РКН заблокировал поддомены blogger.com на которые загружались картинки.

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