Delphi 10 Seattle Background Service and Threads

2019-08-15 06:00发布

问题:

Using Delphi 10 Seattle Update 1 to create an Android application.

Basic goal is to have an application the pops up a notification every few (4) hours to remind the user to get up and move.

I have created the basic UI and created the background service. In the service I can use a TNotificationCenter to post a notification just fine, but I need to post a notification at a regular interval.

Based on suggestions found at the following sites...

  • http://blog.blong.com/2015/02/delphi-and-android-services-part-3.html
  • http://blog.marcocantu.com/blog/2014_may_background_delphi_android_threads.html
  • embarcadero delphi XE10 android service with a timer
  • Also the AndroidNotificationServiceDemo that ships with the product...

I realized that I cannot use a TTimer in and Android Service. I have also tried to create a TTask, TThread.CreateAnonymousThread, and an old fashioned TThread descendant from both the AndroidServiceStartCommand and the AndroidServiceCreate event.

All of them throw the same error after the sleep command finishes. "Project MoveProof.apk raised exception class Segment fault (11)"

Here is the code I am using now....

unit UnitServiceMain;

interface

uses
    System.SysUtils
  , System.Classes
  , System.Android.Service
  , System.Notification

  , AndroidApi.JNI.GraphicsContentViewText
  , Androidapi.JNI.Os

  ;

type
  TServiceThread = class;

  TNotes = Array of String;

  TAndroidServiceDM = class(TAndroidService)
    NotificationCenterNotes: TNotificationCenter;
    function AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer;
    procedure AndroidServiceCreate(Sender: TObject);
    procedure AndroidServiceDestroy(Sender: TObject);
  private
    FNotes: TArray<string>;
    FThread: TServiceThread;
  public
    var Running: Boolean;

    Procedure LoadArray;
    Property Notes:TArray<string> read FNotes;
  end;

  TServiceThread = class(TThread)
  public
    Procedure Execute; override;
    Procedure DoNotification;
  end;

var
  AndroidServiceDM: TAndroidServiceDM;

implementation

{%CLASSGROUP 'FMX.Controls.TControl'}

{$R *.dfm}

Uses
    System.Threading
  , Androidapi.JNI.App
  ;

{ TServiceThread }

procedure TServiceThread.DoNotification;
Var
  NoteId: Integer;
  MyNotification: TNotification;
begin
  while (AndroidServiceDM <> nil) and AndroidServiceDM.Running do
    Begin
      try
        if (AndroidServiceDM <> nil) and AndroidServiceDM.Running then
          Begin
            AndroidServiceDM.NotificationCenterNotes.CancelAll;
            NoteID := Random(High(AndroidServiceDM.Notes));
            MyNotification := AndroidServiceDM.NotificationCenterNotes.CreateNotification;
            try
              MyNotification.Name := 'LoveNoteMessage'+InttoStr(NoteID);
              MyNotification.EnableSound := False;
              MyNotification.Number := NoteID;
              MyNotification.Title := 'Michael Said...';
              MyNotification.AlertBody := AndroidServiceDM.Notes[NoteID];
              AndroidServiceDM.NotificationCenterNotes.PresentNotification(MyNotification);
            finally
              MyNotification.DisposeOf;
            end;
          End;
      except
        on Exception do
          // Need to log this...
      end;
    end;
end;

procedure TServiceThread.Execute;
begin
  inherited;
  Sleep( 20000 );
  Synchronize(DoNotification);
end;

procedure TAndroidServiceDM.LoadArray;
begin
  if Length(FNotes) = 0 then
    Begin
      FNotes := TArray<string>.Create
      (
        'Get up and move.',
        'Time to keep moving.',
        'Lets take a walk.',
        'Move.'
      );
    End;
end;

procedure TAndroidServiceDM.AndroidServiceCreate(Sender: TObject);
begin
  Randomize;
  LoadArray;
end;

procedure TAndroidServiceDM.AndroidServiceDestroy(Sender: TObject);
begin
  FThread.Terminate;
  FThread := Nil;
end;

function TAndroidServiceDM.AndroidServiceStartCommand(const Sender: TObject; const Intent: JIntent; Flags, StartId: Integer): Integer;
begin
  JavaService.stopSelf;
  Result := TJService.JavaClass.START_STICKY;
  if not Running then
    begin
      Running := True;
      FThread := TServiceThread.Create(False);
    end;
end;


end.

I am not that experienced with threads, so maybe I am doing something wrong with synchronization. Any help would be appreciated.

回答1:

There's no need to put your DoNotification in a thread. Just setup a TTask with an eternal loop in your AndroidServiceCreate, and then check the elapsed time, and when it's passed, call the DoNotification then.

  T := TTask.Run (procedure
  begin
    TimeNow := Now;
    Count := 0;
    while true do
    begin
      sleep(20000);
      if SecondsBetween(TimeNow, Now) >= Floor(4 * 60 * 60) then
      begin
        DoNotification;
        TimeNow := Now;
      end;
    end;
  end);

Your notification looks more complicated than it needs to be, and the unnecessary loop you used there might be the cause of your segmentation fault.

  myNotification := NotificationCenter1.CreateNotification;
  try
    MyNotification.Name := 'ServiceNotification';
    MyNotification.Title := 'Android Service Notification';
    MyNotification.AlertBody := 'hello host, I'm your service'
    MyNotification.FireDate := Now;
    NotificationCenter1.PresentNotification(MyNotification); 
  finally
    myNotification.DisposeOf;
  end;

The good rule of thumb for Android services in Delphi is to write the simplest code you can to get the job done and no more.



回答2:

You don't need a service to do this, as 323go suggested.

a service should only be used when it is definitely needed because it consumes system resources and drains battery life, so use them on a need to only basis.

You can use the native AlarmManager.setRepeating method that will create a native recurring notification in a custom interval specified by you, the solution however is Android specific

refer to my answer here Custom notification interval

Just note that for your case that MyNotification.RepeatIntervalinMills is the interval in Milliseconds that you want your notifications to fire in so in your case of 4 Hours it would be

MyNotification.RepeatIntervalinMills := 14400000 // That is 4 hours in Milliseconds