Фоновая обработка Delphi Firemonkey для iOS

Описание проблемы:

В настоящее время я занимаюсь разработкой приложений для Android и iOS с Delphi XE7 Firemonkey. Это приложение должно работать в автономном режиме, чтобы пользователь мог работать с ним, и когда телефон подключается к сети, приложение отправляет всю работу на серверы, даже если приложение работает в фоновом режиме или телефон находится в режиме ожидания.

Android рабочее решение:

Как я выяснил, объекты TThread и TTimer не работают, поскольку они перестают работать, как только приложение переходит в фоновый режим или телефон переходит в режим ожидания.

В итоге я нашел эту библиотеку для Android, которая работает как объект TTimer, но все еще работает, когда приложение переходит в фоновый режим или телефон находится в режиме ожидания.

https://github.com/dkstar88/AsyncTask

К сожалению, он не работает на iOS, поскольку ведет себя как TThread и TTimer, и останавливается, когда приложение переходит в фоновый режим.

iOS опробовал решения

Я попробовал несколько подходов, но обнаружил, что у многих есть много информации об Android, но гораздо меньше для iOS.

Первым делом я попытался добавить в файл plist необходимые разрешения, чтобы сообщить iOS, что я хочу сделать что-то в фоновом режиме. Я попробовал свойство "извлечь".

Это само по себе не работает с TTimer, TThread или AsyncTask, опробованными в Android.

Итак, я подумал, что вы должны сказать iOS, что вы хотите сделать что-то в фоновом режиме И какую процедуру или код вы хотите, чтобы iOS выполняла в фоновом режиме.

Самый промо-код, который я нашел, был в этих ссылках (просмотр комментариев):

http://qc.embarcadero.com/wc/qcmain.aspx?d=128968

Вот код, который я использовал, адаптированный из предыдущей ссылки. Он просто добавляет строку в поле заметки для тестирования.

 unit uBackgroundiOS_2;

interface

uses iOSapi.UIKit,Macapi.ObjCRuntime,Macapi.ObjectiveC,iOSapi.CocoaTypes,FMX.Platform.iOS,iOSapi.Foundation,Macapi.Helpers,

FMX.Memo, system.SysUtils;

const UIBackgroundFetchResultNewData:NSUInteger = 0;
      UIBackgroundFetchResultNoData:NSUInteger = 1;
      UIBackgroundFetchResultFailed:NSUInteger = 2;

      UIApplicationBackgroundFetchIntervalMinimum:NSTimeInterval = 0;
      UIApplicationBackgroundFetchIntervalNever:NSTimeInterval = -1;

type
      id=Pointer;
      IMP = function( self : id; cmd : SEL; Param1 : NSUInteger ) : id; cdecl;

Var UIApp : UIApplication;
    memo: TMemo;

function imp_implementationWithBlock( block :Pointer ) : IMP; cdecl; external libobjc name  _PU + 'imp_implementationWithBlock';
function imp_removeBlock( anImp : IMP ) : integer; cdecl; external libobjc name _PU + 'imp_removeBlock';
function objc_addClass(Cls: Pointer): Integer; cdecl; external libobjc name _PU + 'objc_addClass';

procedure performFetchWithCompletionHandler(self:id; _cmd: SEL;application:id; handler:id);


procedure Init(p_memo: Tmemo);

implementation

procedure Init(p_memo: Tmemo);
var
  Res: Integer;
begin
{
Info:
use .\plist\BackgroundOPs.info.plist from Project Main Pfad and copy to Info.plist

include the Keys:
<key>UIBackgroundModes</key>
<array>
 <string>fetch</string>
 <string>remote-notification</string>
 <string>newsstand-content</string>
</array>
}
UIApp := TUIApplication.Wrap(TUIApplication.OCClass.sharedApplication);

objc_msgSend((UIApp as ILocalObject).GetObjectId,sel_getUid('setMinimumBackgroundFetchInterval:'),UIApplicationBackgroundFetchIntervalMinimum);
Res := class_addMethod( objc_getClass('DelphiAppDelegate') , sel_getUid('application:performFetchWithCompletionHandler:'), @performFetchWithCompletionHandler,'v@:@?');

memo := p_memo;
end;



procedure performFetchWithCompletionHandler(self:id; _cmd: SEL;application:id; handler:id);
{
Using your device you can fire application:performFetchWithCompletionHandler with the following steps:
Put your app in the Background state.
Lock your device and wait 5 minutes. (I know, it's a waste of time, get some coffee)
Unlock your device, this is will fire the method.
}
var
  ahandlerimp: IMP;
begin
  try
    // here your code max. 30 Sec.
    Memo.Lines.Add(FormatDateTime('hh:nn:ss', Now));

    ahandlerimp := imp_implementationWithBlock(handler);
    ahandlerimp(self,_cmd,UIBackgroundFetchResultNewData); // return the Do- code
    imp_removeBlock(ahandlerimp);
  except
  end;
end;

end.

Это не работает в моем iphone 4 с iOS 8.4.1. Я оставил телефон в режиме ожидания на некоторое время, и когда я проверил приложение, поле для заметок было пустым.

Есть идеи, что может быть не так? Я немного тупик здесь.

Большое спасибо!

PS: в своем оригинальном сообщении я добавил больше ссылок и источников (включая другие сообщения от stackru), но, к сожалению, я оставил только самые релевантные два, потому что у меня нет 10 репутации, необходимой для публикации большего количества.


Смешивая код из этих двух примеров, я сделал следующий блок:

http://qc.embarcadero.com/wc/qcmain.aspx?d=128968 (см. комментарии) Вызов объективного блока кода C из delphi

unit uBackgroundiOS;

interface

uses fmx.platform.iOS,iOSapi.CocoaTypes, Macapi.ObjCRuntime, Macapi.ObjectiveC,
     iOSapi.UIKit;

const
  UIBackgroundFetchResultNewData:NSUInteger = 0;
  UIBackgroundFetchResultNoData:NSUInteger = 1;
  UIBackgroundFetchResultFailed:NSUInteger = 2;

  UIApplicationBackgroundFetchIntervalMinimum:NSTimeInterval = 0;
  UIApplicationBackgroundFetchIntervalNever:NSTimeInterval = -1;

type

  // copied from fmx.platform.iOS as it's on private declaration
  id = Pointer;
  SEL = Pointer;
  PUIApplication = Pointer;

  IMP = function( self : id; cmd : SEL; Param1 : NSUInteger ) : id; cdecl;

  function imp_implementationWithBlock( block :id ) : IMP; cdecl; external libobjc name  _PU + 'imp_implementationWithBlock';
  function imp_removeBlock( anImp : IMP ) : integer; cdecl; external libobjc name _PU + 'imp_removeBlock';

  procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application:    PUIApplication; handler : id );

  procedure initializeBackgroundFetch;

  //to test if procedure is called in background
  var fecth_string_test: string;

implementation

procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application: PUIApplication; handler : id );
var
  ahandlerimp: IMP;
begin
  //Code to perform fetch
  fecth_string_test := 'entered background code!!';

  ahandlerimp := imp_implementationWithBlock( handler ); //Create c function for block
  ahandlerimp(self,_cmd, UIBackgroundFetchResultNewData); //Call c function, _cmd is ignored
  imp_removeBlock(ahandlerimp); //Remove the c function created two lines up
end;

procedure initializeBackgroundFetch;
Var
  UIApp : UIApplication;
  Interval: Pointer;
begin
  UIApp := TUIApplication.Wrap(TUIApplication.OCClass.sharedApplication);

  Interval := objc_msgSend((UIApp as ILocalObject).GetObjectId,
                            sel_getUid('setMinimumBackgroundFetchInterval:'));
//               Interval);
  class_addMethod( objc_getClass('DelphiAppDelegate') ,
                  sel_getUid('application:performFetchWithCompletionHandler:'),
                  @performFetchWithCompletionHandler,
                  'v@:@?'
  );
end;

end.

Этот код не работает. Я вызываю initializeBackgroundFetch при запуске приложения. Я уверен, что делаю что-то не так, но не могу понять, что это.

Также я заметил, что мое приложение не отображается в фоновом режиме, разрешать приложения в настройках iPhone, должно ли оно появиться? Я добавил следующее в файл info.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
[...]
    <key>UIBackgroundModes</key>
    <array>
        <string>fetch</string>
        <string>remote-notification</string>
        <string>newsstand-content</string>
    </array>
</dict>
</plist>

Есть идеи?

1 ответ

Решение

iOS рабочее решение

Я наконец-то заставил его работать под Delphi 10 Seattle (вам нужна эта версия, я пробовал XE7 и не работает, я не пробовал xe8). Вот шаги:

1- Проект> Параметры> Информация о версии. Проверьте нужные вам UIBackgroundModes (я использовал Fetch)

2- Это код, который я использовал:

unit uBackgroundiOS;

interface

uses fmx.platform.iOS,iOSapi.CocoaTypes, Macapi.ObjCRuntime, Macapi.ObjectiveC,
     iOSapi.UIKit;

const
  UIBackgroundFetchResultNewData:NSUInteger = 0;
  UIBackgroundFetchResultNoData:NSUInteger = 1;
  UIBackgroundFetchResultFailed:NSUInteger = 2;

  UIApplicationBackgroundFetchIntervalMinimum:NSTimeInterval = 0;
  UIApplicationBackgroundFetchIntervalNever:NSTimeInterval = -1;

type

  // copied from fmx.platform.iOS as it's on private declaration
  id = Pointer;
  SEL = Pointer;
  PUIApplication = Pointer;

  IMP = function( self : id; cmd : SEL; Param1 : NSUInteger ) : id; cdecl;

  function imp_implementationWithBlock( block :id ) : IMP; cdecl; external libobjc name  _PU + 'imp_implementationWithBlock';
  function imp_removeBlock( anImp : IMP ) : integer; cdecl; external libobjc name _PU + 'imp_removeBlock';

  procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application:    PUIApplication; handler : id );

  procedure initializeBackgroundFetch;

  function objc_msgSend(theReceiver: Pointer; theSelector: Pointer): Pointer; cdecl; varargs;
  external libobjc name _PU + 'objc_msgSend';

  //to test if procedure is called in background
  var fecth_string_test: string;

implementation

procedure performFetchWithCompletionHandler(self : id; _cmd : SEL; application: PUIApplication; handler : id );
var
  ahandlerimp: IMP;
begin
  //Code to perform fetch HERE!!!!
  fecth_string_test := 'entered background code!!';

  ahandlerimp := imp_implementationWithBlock( handler ); //Create c function for block
  ahandlerimp(self,_cmd, UIBackgroundFetchResultNewData); //Call c function, _cmd is ignored
  imp_removeBlock(ahandlerimp); //Remove the c function created two lines up
end;

procedure initializeBackgroundFetch;
Var
  UIApp: UIApplication;
begin
  UIApp := TUIApplication.Wrap(TUIApplication.OCClass.sharedApplication);

  objc_msgSend((UIApp as ILocalObject).GetObjectId,
                sel_getUid('setMinimumBackgroundFetchInterval:'),
                UIApplicationBackgroundFetchIntervalMinimum);

  class_addMethod(objc_getClass('DelphiAppDelegate') ,
                  sel_getUid('application:performFetchWithCompletionHandler:'),
                  @performFetchWithCompletionHandler,
                  'v@:@?');
end;

end.
Другие вопросы по тегам