понедельник, 12 июля 2021 г.

[Android Service] Пример Started сервиса

Пример очень простой и призван показать самые основы работы с сервисом.

Создание основы для дальнейшей разработки:

1. Создаём "Local сервис" с помощью "Мастера создания сервиса"

2. Переименовываем файл "Unit1.pas" в "uPlayService.pas", меняем имя проекта с "Project1" на "PlayService" 

3. Сохраняем проект в отдельной папке "PlayService".

В этом примере структура каталогов будет такая:


4. Делаем сборку (Build) проекта для 32-bit и для 64-bit

5. Сохраняем и теперь уже закрываем проект.

6. Создаём новый проект (Multi-Device Application) приложения.

7. Переименовываем файл "Unit1.pas" в "MainForm.pas", меняем имя проекта с "Project1" на "AppForPlayService"

8. Сохраняем проект приложения в отдельную папку "App".

9. Теперь добавляем сервис в проект приложения

10. После добавления сохраняем проект приложения. Файл "ProjectGroup1.groupproj" сохраним в корневой папке "ProjectGroup".

Вот и всё, половину дела сделали. Теперь остаётся написать код, однако перед этим вернёмся к теории. Это необходимо сделать, чтобы понимать, какие/как/почему методы будем использовать. 

 

Теория

Сервис запускается с помощью Android приложения и работает в одном потоке с приложением. Может работать в фоновом режиме неограниченное время, даже если приложение закрыто. Может быть "убит" и перезапущен (если мы явно указали это) системой (например, при нехватке оперативной памяти).
 

Жизненный цикл Started сервиса:

  1. onCreate()
  2. onStartCommand()
  3. onDestroy()


Запуск сервиса из приложения:

uses
System.Android.Service, Androidapi.Helpers;
// Вариант 1
var
FServiceConnection1: TLocalServiceConnection;
begin
FServiceConnection1 := TLocalServiceConnection.Create;
FServiceConnection1.StartService('Название сервиса');
end;
// Вариант 2
TLocalServiceConnection.StartService('Название сервиса');
// Вариант 3
TAndroidHelper.Activity.startService(Intent);

Остановка сервиса из приложения:

uses
Androidapi.Helpers;
begin
TAndroidHelper.Activity.stopService(Intent);
end;

Остановка сервиса из самого сервиса:

uses
System.Android.Service;
begin
// Вариант 1
JavaService.stopSelf;
// Вариант 2
JavaService.stopSelfResult(startId);
end;


Примечание 1. Если вы запустили сервис, то обязаны остановить его по завершении выполнения задачи или завершении приложения. Исключение, сервисы, которые должны постоянно работать (например, сервис, воспроизводящий музыку, сервис gps – трекинга и т.д.).
 

Примечание 2. Если сервис уже запущен, то система не будет запускать его второй раз, а просто передаст новые данные ("Intent" и др. информацию) в метод "onStartCommand()".
 

Примечание 3. В прослойке "TLocalServiceConnection" я не обнаружил метода "stopService". Да и в документации тоже. Видимо, предполагается, что мы будем останавливать сервис только с помощью "stopSelf".


Теперь поговорим о методе onStartCommand().

1. Этот метод вызывается после метода OnCreate(), если сервис запустили впервые. 

Примечание 5. Если сервис уже запущен и вы снова вызываете метод StartService(), то метод OnCreate() будет пропущен и сразу вызовется метод onStartCommand(). 

2. Этот метод вызывается всегда при запуске службы с помощью метода StartService().

3. В этом методе пишем код задачи, которую необходимо выполнить.

4. В этом методе мы можем получить доступ к параметрам:

  • Intent: JIntent; - интент с которым был вызван метод startService() и запущен сервис.
  • Flags: Integer – флаг, сообщающий о том, была ли повторная попытка запуска сервиса.(START_FLAG_REDELIVERY или START_FLAG_RETRY)
  • StartId: Integer – уникальное значение, относящееся к конкретному запуску. (используется совместно с методом stopSelfResult(int))

Метод должен вернуть одно из значений, указывающих системе как обрабатывать сервис:

  • START_NOT_STICKY – сервис не будет перезапущен системой, после того как будет убит системой.
  • START_REDELIVER_INTENT – сервис будет перезапущен в случае если его убила система. При этом будут переданы все незавершённые вызовы startService(). Отработают методы: OnCreate и onStartCommand().
  • START_STICKY – сервис будет пересоздан, если был убит системой. Все вызовы startService() будут потеряны. Метод onStartCommand() вернёт Intent равный nil. Отработают методы: OnCreate.

 

Теперь вернёмся к примеру.

Пример сервиса (в самом простейшем его виде):

  • Воспроизводит музыкальный трек при помощи Android API MediaPlayer
  • Принимает путь до трека переданный при помощи Intent’а и вызова startService()
  • Перезапускается (если был убит системой) с последним вызовом StartService().
  • Управление через приложение (кнопки Start, Stop, Next).
  • Запускать сервис можно передавая путь и не передавая путь (тогда будет воспроизведён трек по умолчанию).


Код файла MainForm.pas для приложения:

unit MainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls,
Androidapi.JNI.GraphicsContentViewText, Androidapi.Helpers, System.IOUtils;
type
TFormMain = class(TForm)
bPlay: TButton;
bStop: TButton;
bNext: TButton;
procedure bPlayClick(Sender: TObject);
procedure bStopClick(Sender: TObject);
procedure bNextClick(Sender: TObject);
private const
ServicePrefix = 'com.embarcadero.services.';
ServiceName = 'PlayService';
private
{ Private declarations }
// Вспомогательный метод для создания Intent'а
function CreateIntent(const AServiceName: string; const AParam: string = string.Empty): JIntent;
public
{ Public declarations }
end;
var
FormMain: TFormMain;
implementation
{$R *.fmx}
procedure TFormMain.bNextClick(Sender: TObject);
const
SecondTrack = 'track2.mp3';
var
FilePath: string;
begin
// Запуск сервиса с передачей доп. параметров. (передадим новый путь до файла)
// Два .mp3 файла добавлены через Deployment Manager, Remote Path = .\assets\internal\
FilePath := TPath.Combine(TPath.GetDocumentsPath, SecondTrack);
TAndroidHelper.Activity.startService(CreateIntent(ServiceName, FilePath));
end;
procedure TFormMain.bPlayClick(Sender: TObject);
begin
// Запуск сервиса без доп. параметров
TAndroidHelper.Activity.startService(CreateIntent(ServiceName));
end;
procedure TFormMain.bStopClick(Sender: TObject);
begin
TAndroidHelper.Activity.stopService(CreateIntent(ServiceName));
end;
function TFormMain.CreateIntent(const AServiceName: string; const AParam: string): JIntent;
const
IntentExtraName = 'Param1';
var
FullServiceName: string;
begin
FullServiceName := ServicePrefix + AServiceName;
Result := TJIntent.JavaClass.init;
Result.setClassName(TAndroidHelper.Context.getPackageName, TAndroidHelper.StringToJString(FullServiceName));
if AParam <> string.Empty then
Result.putExtra(TAndroidHelper.StringToJString(IntentExtraName), TAndroidHelper.StringToJString(AParam));
end;
end.
view raw MainForm.pas hosted with ❤ by GitHub

 

Код файла uPlayService.pas для сервиса:

unit uPlayService;
interface
uses
System.SysUtils,
System.Classes,
System.Android.Service,
AndroidApi.JNI.GraphicsContentViewText,
AndroidApi.JNI.Os,
AndroidApi.JNI.Media;
type
TDM = class(TAndroidService)
procedure AndroidServiceCreate(Sender: TObject);
procedure AndroidServiceDestroy(Sender: TObject);
function AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer;
private
{ Private declarations }
FMediaPlayer: JMediaPlayer;
public
{ Public declarations }
end;
var
DM: TDM;
implementation
{%CLASSGROUP 'FMX.Controls.TControl'}
{$R *.dfm}
uses
AndroidApi.JNI.JavaTypes, AndroidApi.Helpers, System.IOUtils, AndroidApi.JNI.App;
procedure TDM.AndroidServiceCreate(Sender: TObject);
begin
FMediaPlayer := TJMediaPlayer.JavaClass.init;
end;
procedure TDM.AndroidServiceDestroy(Sender: TObject);
begin
FMediaPlayer.release;
FMediaPlayer := nil;
end;
function TDM.AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer;
const
IntentExtraName = 'Param1';
DefaultTrackName = 'track1.mp3';
var
ExtraName: JString;
FilePath: string;
begin
ExtraName := StringToJString(IntentExtraName);
if Intent.hasExtra(ExtraName) then
FilePath := JStringToString(Intent.getStringExtra(ExtraName))
else
FilePath := TPath.Combine(TPath.GetDocumentsPath, DefaultTrackName);
FMediaPlayer.reset;
FMediaPlayer.setDataSource(StringToJString(FilePath));
FMediaPlayer.prepare;
FMediaPlayer.start;
Result := TJService.JavaClass.START_REDELIVER_INTENT;
end;
end.

 

Это простой пример (один из кучи возможных). 

Для дополнительного изучения рекомендую:

 

Ссылка на основную статью: [Android Service] Сервисы в Android


p.s. К сожалению, все примеры и варианты использования Started сервиса не уместить в одной статье.