Пример очень простой и призван показать самые основы работы с сервисом.
Создание основы для дальнейшей разработки:
1. Создаём "Local сервис" с помощью "Мастера создания сервиса"
2. Переименовываем файл "Unit1.pas" в "uPlayService.pas", меняем имя проекта с "Project1" на "PlayService"
3. Сохраняем проект в отдельной папке "PlayService".
В этом примере структура каталогов будет такая:
5. Сохраняем и теперь уже закрываем проект.
6. Создаём новый проект (Multi-Device Application) приложения.
7. Переименовываем файл "Unit1.pas" в "MainForm.pas", меняем имя проекта с "Project1" на "AppForPlayService"
8. Сохраняем проект приложения в отдельную папку "App".
9. Теперь добавляем сервис в проект приложения
10. После добавления сохраняем проект приложения. Файл "ProjectGroup1.groupproj" сохраним в корневой папке "ProjectGroup".
Вот и всё, половину дела сделали. Теперь остаётся написать код, однако перед этим вернёмся к теории. Это необходимо сделать, чтобы понимать, какие/как/почему методы будем использовать.
Теория
Сервис запускается с помощью Android приложения и работает в одном потоке с приложением. Может работать в фоновом режиме неограниченное время, даже если приложение закрыто. Может быть "убит" и перезапущен (если мы явно указали это) системой (например, при нехватке оперативной памяти).
Жизненный цикл Started сервиса:
- onCreate()
- onStartCommand()
- 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. |
Код файла 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 API документацию по сервисам: Про сервис в манифесте и Про сервисы в целом
- Документацию по сервисам в Delphi: Creating Android Services
- Всемирную глобальную сеть Интернет, для поиска необходимой вам информации. Гуглите и всё найдётся ;)
Ссылка на основную статью: [Android Service] Сервисы в Android
p.s. К сожалению, все примеры и варианты использования Started сервиса не уместить в одной статье.