Важно (9.07.22)

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

среда, 21 сентября 2016 г.

Очередь событий Delphi приложения на Android

Это маленькая заметка о том, какие события происходят, когда мы запускаем приложение на Android. В ней я покажу логи из LogCat (с описанием тестов, которые провёл) и мы выясним, какие же события происходят всегда, а какие нет.

Ап. Добавил ссылку на пример, когда и как нужно сохранять данные приложения и объяснение, почему не видно событий в некоторых тестах.
Ап.2. Добавил данных по 4 тесту, добавил тест 8, изменил выводы :)
АП.3 (03.03.2017) Добавил табличку для визуализации результатов.
АП.4 (23.03.2017) Добавлена табличка для RAD Studio 10.2 Tokyo. Тесты проходили на Android 4.1.2; 4.4.2; 6.0.1.



Наверняка многие читатели вспомнят, что я уже писал о жизненном цикле приложения в Android (это самая первая статья в блоге). В этот раз мы рассмотрим стандартные события приложения (onCreate, onPaint и т.д., полный список ниже) и события жизненного цикла.
Думаю, заметка пригодится многим, кто хочет корректно завершать работу функций в своём приложении.

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

Какие события будем мониторить:
  • Стандартные: OnActivate, OnClose, OnCloseQuery, OnCreate, OnDeactivate, OnDestroy, OnFocusChanged, OnHide, OnPaint, OnResize, OnSavaState, OnShow.
  • Android: События жизненного цикла (используем обновлённый код из предыдущей заметки - Справка)
Код приложения:
unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform;

type
  TForm1 = class(TForm)
    procedure FormActivate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormDeactivate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormFocusChanged(Sender: TObject);
    procedure FormHide(Sender: TObject);
    procedure FormPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
    procedure FormResize(Sender: TObject);
    procedure FormSaveState(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.FormActivate(Sender: TObject);
begin
  Log.d('FormActivate');
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Log.d('FormClose');
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  Log.d('FormCloseQuery');
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  aFMXApplicationEventService: IFMXApplicationEventService;
begin
  Log.d('FormCreate');
  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(aFMXApplicationEventService)) then
    aFMXApplicationEventService.SetApplicationEventHandler(HandleAppEvent)
  else
    Log.d('Application Event Service is not supported.');
end;

procedure TForm1.FormDeactivate(Sender: TObject);
begin
  Log.d('FormDeactivate');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Log.d('FormDestroy');
end;

procedure TForm1.FormFocusChanged(Sender: TObject);
begin
  Log.d('FormFocusChanged');
end;

procedure TForm1.FormHide(Sender: TObject);
begin
  Log.d('FormHide');
end;

procedure TForm1.FormPaint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
begin
  Log.d('FormPaint');
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Log.d('FormResize');
end;

procedure TForm1.FormSaveState(Sender: TObject);
begin
  Log.d('FormSaveState');
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  Log.d('FormShow');
end;

function TForm1.HandleAppEvent(AAppEvent: TApplicationEvent;
  AContext: TObject): Boolean;
begin
  case AAppEvent of
    TApplicationEvent.FinishedLaunching: Log.d('Finished Launching');
    TApplicationEvent.BecameActive: Log.d('Became Active');
    TApplicationEvent.WillBecomeInactive: Log.d('Will Become Inactive');
    TApplicationEvent.EnteredBackground: Log.d('Entered Background');
    TApplicationEvent.WillBecomeForeground: Log.d('Will Become Foreground');
    TApplicationEvent.WillTerminate: Log.d('Will Terminate');
    TApplicationEvent.LowMemory: Log.d('Low Memory');
    //aeTimeChange: Log('Time Change'); //not supported for Android
    //aeOpenURL: Log('Open URL'); //not supported for Android
  end;
  Result := True;
end;

end.



Компилим и устанавливаем на устройство.

Внимание! Логи основаны на тестах на LG L70 D-325 (Android 4.4.2) и Nexus 5 (Android 6.1), за тесты на Nexus 5 отдельное спасибо Равилю Зарипову, посетителю с форума Fire Monkey от А до Я

Результаты тестов (кроме №5) в таблице:




Переходим к тестам.

Тест 1:
  1. Запускаем приложение
  2. Выходим из приложения кнопкой «Back»
Результаты:

1) Запуск приложения
09-20 20:13:43.151: I/info(15893): FMX: Project1: FormCreate
09-20 20:13:43.151: I/info(15893): FMX: Project1: FormResize
09-20 20:13:43.151: I/info(15893): FMX: Project1: FormResize
09-20 20:13:43.161: I/info(15893): FMX: Project1: FormResize
09-20 20:13:43.161: I/info(15893): FMX: Project1: FormShow
09-20 20:13:43.161: I/info(15893): FMX: Project1: FormActivate
09-20 20:13:43.161: I/info(15893): FMX: Project1: Finished Launching
09-20 20:13:43.221: I/info(15893): FMX: Project1: FormPaint
09-20 20:13:43.231: I/info(15893): FMX: Project1: Became Active
09-20 20:13:43.261: I/info(15893): FMX: Project1: FormPaint
09-20 20:13:43.281: I/info(15893): FMX: Project1: FormPaint
09-20 20:13:43.311: I/info(15893): FMX: Project1: FormPaint

2) Выходим из приложения кнопкой «Back»
09-20 20:13:53.621: I/info(15893): FMX: Project1: FormCloseQuery
09-20 20:13:53.621: I/info(15893): FMX: Project1: FormClose
09-20 20:13:53.641: I/info(15893): FMX: Project1: FormSaveState
09-20 20:13:53.851: I/info(15893): FMX: Project1: Entered Background
09-20 20:13:53.871: I/info(15893): FMX: Project1: Will Become Inactive (в Android 6 – это событие происходит перед Entered Background)
09-20 20:13:54.421: I/info(15893): FMX: Project1: Will Terminate
09-20 20:13:54.671: I/info(15893): FMX: Project1: FormDeactivate
09-20 20:13:54.671: I/info(15893): FMX: Project1: FormDestroy

Как видим, все события отработали на ура, но не радуйтесь, это только первый тест…
Забегая вперёд, сразу скажу, что ниже не буду публиковать логи «Запуска приложения», т.к. они всегда одинаковы. Также я опустил все варнинги, которые вылезали при запуске.

Тест 2:
  1. Запускаем приложение
  2. Жмём кнопку «Home», тем самым возвращаясь на рабочий стол
  3. Зажимаем кнопку «Home» и жмём на наше приложение
Результаты:
  1. Запуск приложения – Логи как в первом тесте
  2. Жмём кнопку «Home», тем самым возвращаясь на рабочий стол
09-20 20:15:57.531: I/info(16159): FMX: Project1: Entered Background
09-20 20:15:57.651: I/info(16159): FMX: Project1: Will Become Inactive (в Android 6 – это событие происходит перед Entered Background)
09-20 20:15:58.041: I/info(16159): FMX: Project1: FormSaveState
      3. Зажимаем кнопку «Home» и жмём на наше приложение
09-20 20:16:11.591: I/info(16159): FMX: Project1: Will Become Foreground
09-20 20:16:11.701: I/info(16159): FMX: Project1: FormPaint
09-20 20:16:11.711: I/info(16159): FMX: Project1: Became Active
09-20 20:16:11.721: I/info(16159): FMX: Project1: FormPaint
09-20 20:16:11.731: I/info(16159): FMX: Project1: FormPaint
09-20 20:16:11.731: I/info(16159): FMX: Project1: FormPaint

Как видим, события FormCloseQuery, FormClose, Will Terminate, FormDeactivate, FormDestroy не происходят вообще.


Тест 3:
  1. Запускаем приложение (приложение находится на экране)
  2. Уничтожаем приложение через «список недавно запущенных приложений»
Результаты:
  1. Запускаем приложение (приложение находится на экране)  – Логи как в первом тесте
  2. Закрываем приложение через «список недавно запущенных приложений»
09-20 20:19:05.771: I/info(16547): FMX: Project1: Entered Background
09-20 20:19:05.771: I/info(16547): FMX: Project1: Will Become Inactive(в Android 6 – это событие происходит перед Entered Background)
09-20 20:19:06.321: I/info(16547): FMX: Project1: FormSaveState

Как видим, события FormCloseQuery, FormClose, Will Terminate, FormDeactivate, FormDestroy не происходят вообще.

Тест 4:
  1. Запускаем приложение (приложение находится на экране)
  2. Даём устройству уснуть (экран гаснет, включается блокировка экрана) На моём устройстве: Экран гаснет через 30 секунд, блокировка через 5 секунд после экрана.
  3. Выждав немного времени, будим устройство
Результаты:

  1. Запускаем приложение (приложение находится на экране) – Логи как в первом тесте
  2. Даём устройству уснуть (экран гаснет, включается блокировка экрана) На моём устройстве: Экран гаснет через 30 секунд, блокировка через 5 секунд после экрана.
Android 4.4.2:
09-20 20:20:18.911: I/info(16867): FMX: Project1: Entered Background
09-20 20:20:18.961: I/info(16867): FMX: Project1: FormSaveState
09-20 20:20:25.061: I/info(16867): FMX: Project1: Will Become Inactive
      3. Выждав немного времени, будим устройство
09-21 20:40:08.250: I/info(18438): FMX: Project1: Will Become Foreground
09-21 20:40:08.370: I/info(18438): FMX: Project1: FormPaint
09-21 20:40:08.390: I/info(18438): FMX: Project1: Became Active


Тест 5:
  1. Устанавливаем галочку «Не сохранять действия» в «Опции разработчика»
  2. Запускаем приложение
  3. Сворачиваем кнопкой «Home»
Результаты:
  1. тут логов нет :)
  2. тут логи как в первом тесте
  3. Сворачиваем кнопкой «Home»
09-21 14:09:36.962: I/info(14135): FMX: Project1: Entered Background
09-21 14:09:37.582: I/info(14135): FMX: Project1: Will Become Inactive
09-21 14:09:37.962: I/info(14135): FMX: Project1: FormSaveState
09-21 14:09:37.992: I/info(14135): FMX: Project1: Will Terminate
09-21 14:09:38.242: I/info(14135): FMX: Project1: FormDeactivate
09-21 14:09:38.242: I/info(14135): FMX: Project1: FormDestroy

Тест 7:
  1. Запускаем приложение
  2. Сворачиваем  кнопкокой «Home»
  3. Ждём, пока система сама убьёт приложение
Результаты: Не выполнен, т.к. просто не смог дождаться момента, когда система убьёт приложение.
АП
Если свернуть приложение, то потом мы уже не увидим события завершения приложения, т.к. система просто убивает процесс.

Пример из справки, когда сохранять данные приложения - FireMonkey Save State

АП.2.
Тест 8:
  1. Запускаем приложение
  2. Ждём пока экран выключится
  3. И сразу же включаем его обратно (экрана блокировки ещё не должно быть)
Результаты:
Первые два пункта совпадают с тестом 4 (первые два пункта)
3) 09-21 20:47:10.240: I/info(18907): FMX: Project1: Will Become Foreground


Общие выводы:

События, которые всегда(почти, смотри тест 8) происходят при старте (первый старт или восстановление через «Home»): Became Active (получение фокуса), Will Become Foreground и FormPaint.

События, которые всегда происходят при сворачивании или закрытии приложения: Entered Background (onPause), Will Become Inactive (потеря фокуса), FormSaveState.



Надеюсь, эта информация поможет вам правильно строить приложение.
Использовалась RAD Studio Berlin (include update 1)