Фоновая обработка 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.