Delphi 11.1
I need to start an Android Service which stays running even if the app is closed by the user or killed by the OS. The app has an SQLite database in the documents folder and the service needs to access this database and insert some data for a long time.
I’ve tried a lot of strategies, but none worked.
My main attempt is about creating a local service, using the START_STICKY
and calling StartForeground()
in the StartCommand
event.
Service data module:
unit UDMService;
interface
uses
System.SysUtils,
System.Classes, System.IOUtils,
System.Android.Service,
AndroidApi.JNI.GraphicsContentViewText,
Androidapi.JNI.Os, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error,
FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool,
FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.SQLite, FireDAC.Phys.SQLiteDef,
FireDAC.Stan.ExprFuncs, FireDAC.ConsoleUI.Wait, FireDAC.DApt,
FireDAC.Phys.SQLiteWrapper.Stat, Data.DB, FireDAC.Comp.Client;
type
TThreadTeste = class(TThread)
protected
procedure Execute; override;
end;
TDM = class(TAndroidService)
fdConDados: TFDConnection;
FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink;
function AndroidServiceStartCommand(const Sender: TObject;
const Intent: JIntent; Flags, StartId: Integer): Integer;
private
{ Private declarations }
FThreadGravarTabela: TThread;
procedure ConectarBanco;
procedure GerarRegistroTabela;
procedure GerarRegistrosTabela;
procedure StartForeground;
public
{ Public declarations }
end;
var
DM: TDM;
implementation
{%CLASSGROUP 'FMX.Controls.TControl'}
{$R *.dfm}
uses Androidapi.JNI.App, Androidapi.JNI.Support, Androidapi.Helpers;
procedure TDM.StartForeground;
var
{$if COMPILERVERSION > 34}
LBuilder: Japp_NotificationCompat_Builder;
{$else}
LBuilder: JNotificationCompat_Builder;
{$endif}
begin
try
//if FIsForeground or not TAndroidHelperEx.CheckBuildAndTarget(26) then
//Exit; // <======
LBuilder := TJapp_NotificationCompat_Builder.JavaClass.init(TAndroidHelper.Context);
LBuilder.setAutoCancel(True);
LBuilder.setContentTitle(StrToJCharSequence('Sample'));
LBuilder.setContentText(StrToJCharSequence('Monitoring location changes'));
LBuilder.setSmallIcon(TAndroidHelper.Context.getApplicationInfo.icon);
LBuilder.setTicker(StrToJCharSequence('Sample 2'));
TJService.Wrap(System.JavaContext).startForeground(3987, LBuilder.build);
except
end;
end;
function TDM.AndroidServiceStartCommand(const Sender: TObject;
const Intent: JIntent; Flags, StartId: Integer): Integer;
begin
StartForeground;
ConectarBanco;
//GerarRegistroTabela;
GerarRegistrosTabela;
Result := TJService.JavaClass.START_STICKY;
end;
procedure TDM.ConectarBanco;
begin
fdconDados.Params.Values['Database'] := TPath.Combine(TPath.GetDocumentsPath, 'MyServicePOC.db3');
fdconDados.Params.Add('SharedCache=False');
fdconDados.UpdateOptions.LockWait := True;
fdconDados.Connected := True;
end;
procedure TDM.GerarRegistrosTabela;
begin
FThreadGravarTabela := TThreadTeste.Create(True);
FThreadGravarTabela.FreeOnTerminate := FAlse;
FThreadGravarTabela.Start;
end;
procedure TDM.GerarRegistroTabela;
begin
var AQuery: TFDQuery := TFDQuery.Create(nil);
AQuery.Connection := fdConDados;
try
with AQuery, sql do
begin
Clear;
add('INSERT INTO tabela (data) VALUES (:data)');
ParamByName('data').AsDateTime := Now;
ExecSQL;
end;
finally
AQuery.Free;
end;
end;
{ TThreadTeste }
procedure TThreadTeste.Execute;
begin
var i: integer := 0;
var AQuery: TFDQuery := TFDQuery.Create(nil);
AQuery.Connection := DM.fdConDados;
try
while not Terminated do
begin
Inc(i);
try
with AQuery, sql do
begin
Clear;
add('INSERT INTO tabela (data) VALUES (:data)');
ParamByName('data').AsDateTime := Now;
ExecSQL;
end;
except
end;
Sleep(MSecsPerSec*10); //30 secs
if i > 10 then
Terminate;
end;
finally
AQuery.Free;
end;
end;
end.
Main form from App
unit UMainApp;
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, System.Android.Service,
FireDAC.Stan.ExprFuncs, FireDAC.Phys.SQLiteWrapper.Stat,
FireDAC.Phys.SQLiteDef, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.SQLite,
FireDAC.FMXUI.Wait, Data.DB, FireDAC.Comp.Client, System.IOUtils,
FMX.Memo.Types, FireDAC.DApt, FMX.ScrollBox, FMX.Memo, FMX.ListView.Types,
FMX.ListView.Appearances, FMX.ListView.Adapters.Base, FMX.ListView;
type
TForm1 = class(TForm)
Button1: TButton;
FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink;
fdConDados: TFDConnection;
mmLog: TMemo;
ButAtualizarLista: TButton;
ListView1: TListView;
procedure butStartServiceClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ButAtualizarListaClick(Sender: TObject);
private
{ Private declarations }
ConexaoServico : TLocalServiceConnection;
procedure AtualizarEstruturaBase;
function GetDadosTabelaTestes: TDataSet;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.AtualizarEstruturaBase;
function TabelaExiste(const pTable: String): Boolean;
begin
var AQuery: TFDQuery := TFDQuery.Create(nil);
AQuery.Connection := fdConDados;
try
AQuery.Open('PRAGMA table_info(' + QuotedStr(pTable) + ')');
result := AQuery.RecordCount > 0;
finally
AQuery.Free;
end;
end;
const SQL_CRIAR_TABELA_TESTES =
'CREATE TABLE tabela'
+ ' (id INTEGER PRIMARY KEY AUTOINCREMENT,'
+ ' data DATETIME)';
begin
var AQuery: TFDQuery := TFDQuery.Create(nil);
AQuery.Connection := fdConDados;
try
if not (TabelaExiste('tabela')) then
AQuery.ExecSQL(SQL_CRIAR_TABELA_TESTES);
finally
AQuery.Free;
end;
end;
procedure TForm1.ButAtualizarListaClick(Sender: TObject);
begin
ListView1.Items.Clear;
var ADataSet: TDataSet := GetDadosTabelaTestes;
with ADataSet do
begin
First;
while not (Eof) do
begin
ListView1.Items.Add.Text := Format('%d - %s',
[FieldBYName('id').AsInteger,
FormatDateTime('dd/mm/yyyy hh:nn:ss', FieldByName('data').AsDateTime)]);
Next;
end;
end;
end;
procedure TForm1.butStartServiceClick(Sender: TObject);
begin
ConexaoServico := TLocalServiceConnection.Create;
TThread.CreateAnonymousThread(
procedure
begin
ConexaoServico.StartService('MyService2');
end).Start;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
try
fdconDados.Params.Values['Database'] := TPath.Combine(TPath.GetDocumentsPath, 'MyServicePOC.db3');
fdconDados.Params.Add('SharedCache=False');
fdconDados.UpdateOptions.LockWait := True;
fdconDados.Connected := True;
AtualizarEstruturaBase;
except
on e: exception do
mmLog.Lines.Add('[TForm1.FormCreate]'+e.Message)
end;
end;
function TForm1.GetDadosTabelaTestes: TDataSet;
begin
fdConDados.ExecSQL('SELECT * FROM tabela ORDER BY id, data', result);
end;
end.
This code runs fine and generates the inserts and I can load the data, but when I close the app the service is killed by the OS as well.
The service was created as “Local”. I tried to created the same project group using the “Remote” option for the service, but the data is not inserted into the app’s database.
Could anyone help with this issue?
4