I've created a simple FireMonkey screenshot capture that works fine on Android and Windows..:
procedure Capture;
var
B : TBitmap;
begin
try
B := Layout1.MakeScreenshot;
try
B.SaveToFile( ..somefilepath.png );
finally
B.Free;
end;
except
on E:Exception do
ShowMessage( E.Message );
end;
end;
end;
when I moved it to a thread as below, it works fine in Windows but in Android I get the exception 'bitmap too big' from the call to MakeScreenshot. Are there additional steps needed to employ MakeScreenshot in a thread?
procedure ScreenCaptureUsingThread;
begin
TThread.CreateAnonymousThread(
procedure ()
var
B : TBitmap;
begin
try
B := Layout1.MakeScreenshot;
try
B.SaveToFile( '...somefilepath.png' );
finally
B.Free;
end;
except
on E:Exception do
TThread.Synchronize( nil,
procedure ()
begin
ShowMessage( E.Message );
end );
end).Start;
end;
Later addition. Based on suggestions by Sir Rufo and Sebastian Z, this solved the problem and allowed use of a thread:
procedure Capture;
begin
TThread.CreateAnonymousThread(
procedure ()
var
S : string;
B : TBitmap;
begin
try
// Make a file name and path for the screen shot
S := '...SomePathAndFilename.png';
try
// Take the screenshot
TThread.Synchronize( nil,
procedure ()
begin
B := Layout1.MakeScreenshot;
// Show an animation to indicate success
SomeAnimation.Start;
end );
B.SaveToFile( S );
finally
B.Free;
end;
except
on E:Exception do
begin
TThread.Synchronize( nil,
procedure ()
begin
ShowMessage( E.Message );
end );
end;
end;
end).Start;
end;
MakeScreenShot is not thread safe, so you cannot safely use it in a thread. If this works in Windows then I'd say you were just lucky. I'd suggest that you take the screenshot outside of the thread and only use the thread for saving the screenshot to png. Painting should be fast while encoding to png needs a lot more resources. So you should still benefit pretty much from the thread.
Related
The same project runs perfect on Windows/iOS platform, but cause deadlock on Android (two different android devices) after running for uncertain duration.
procedure TTestThread.FSynProc;
begin
Inc(Form1.FExecuted); //Variable of TForm
Form1.Text1.Text:= IntToStr(Form1.FExecuted);
end;
procedure TTestThread.Execute;
var
begin
while not Self.Terminated do
begin
Self.Synchronize(FSynProc);
sleep(10);
end;
end;
constructor TTestThread.Create;
begin
inherited Create(True);
Self.FreeOnTerminate:= True;
end;
destructor TTestThread.Destroy;
begin
inherited;
end;
code for launch TTestThread
procedure TForm1.StartTTProc;
begin
ftt:= TTestThread.Create; // Variable of TForm
ftt.Start;
end;
After StartTTProc, the number shows in 'Text1' increase continusely.
After running for an uncertain duration on android device, App freezed.
any advice?
I have a thread downloading and saving a clientDataset file cds file from a remote Datasnap Server.
This thread works in Windows, IOS (Simulator, Iphone, Ipad) but not on Android.
The app crashes after completed the file download and save it on Android.
The code is simple.
Thread:
TDownloadSaveRemoteDBThread = class (TThread)
public
Username,Password,host,port : String;
error : String;
Folder : String;
ServerMethod : String;
filename : String;
protected
procedure Execute; override;
end;
procedure TDownloadSaveRemoteDBThread.Execute;
var
sqlCon : TSqlConnection;
cds : TClientDataset;
ssm : TSqlServerMethod;
dpr : TDataSetProvider;
begin
inherited;
error :='';
SqlCon:=TSQLConnection.Create(nil);
try
sqlCon.DriverName:='Datasnap';
sqlCon.LoginPrompt:=false;
sqlCon.Params.Values['hostname']:=host;
sqlCon.Params.Values['port']:=port;
sqlCon.Params.Values['UserName']:=Username;
sqlCon.Params.Values['Password']:=Password;
try
sqlCon.Open;
cds:=TClientDataset.Create(nil);
ssm:=TSqlServerMethod.Create(nil);
dpr:=TDataSetProvider.Create(nil);
try
ssm.SQLConnection:=SqlCon;
ssm.ServerMethodName:=ServerMethod;
dpr.DataSet:=ssm;
cds.SetProvider(dpr);
cds.Open;
cds.SaveToFile(folder+filename);
cds.Close;
finally
cds.free;
ssm.free;
dpr.free;
end;
Except
on E : Exception do
Begin
error:=E.Message;
SQLCon.Close;
End;
end;
finally
SQLCon.Close;
SQLCon.free;
end;
end;
I have a button to start the thread in my mainform “Start Form”
procedure TStartForm.Button1Click(Sender: TObject);
var
DownloadSaveRemoteDBThread : TDownloadSaveRemoteDBThread;
begin
DownloadSaveRemoteDBThread:= TDownloadSaveRemoteDBThread.Create(true);
DownloadSaveRemoteDBThread.OnTerminate:= StartForm.ferdigHentDB;
DownloadSaveRemoteDBThread.Password:='test';
DownloadSaveRemoteDBThread.Username:='test';
DownloadSaveRemoteDBThread.host:=edit1.text; //127.0.0.1
DownloadSaveRemoteDBThread.port:='211';
DownloadSaveRemoteDBThread.Folder:=System.IOUtils.TPath.GetDocumentsPath + PathDelim +'db'+ PathDelim;
DownloadSaveRemoteDBThread.ServerMethod:= 'TServerMethods2.hentselect3';
DownloadSaveRemoteDBThread.filename:='select3.cds';
DownloadSaveRemoteDBThread.FreeOnTerminate:=true;
DownloadSaveRemoteDBThread.Start;
end;
Then after thread is finished I have this simple procedure
procedure TStartForm.ferdigHentDB(sender: Tobject);
begin
with sender as TDownloadSaveRemoteDBThread do
Begin
if error > '' then
Begin
showmessage(error);
End;
End;
end;
Any suggestions why Android crashes, is there an easy fix for this issue?
TThread in XE5 has two major bugs on Android. Sychronize() and Queue() are broken (Synchronize() is used to trigger the OnTerminate event), and TThread does not detach itself from the Android JVM before termination if any JNI objects are used by the thread. The first bug has not been fixed yet. The second bug is fixed in XE6, but there is a workaround you can use in XE5.
If this has already been visited here on SO, please point me to it, cause I cant seem to find it. Having said that:
Using the standard delphi application events, as well as the Mobile app lifecycle events handling , i am trying to find the best spot to read and write a INI file?
as I test, i created a demo app with a button which increments a count variable and displays it in a show message
procedure TfrmMain.Button1Click(Sender: TObject);
begin
inc(Count);
ShowMessage(IntToStr(Count));
end;
In the main form's OnCreate even, I read the inifile
procedure TfrmMain.FormCreate(Sender: TObject);
var
Ini: TIniFile;
begin
Ini := TIniFile.Create( TPath.GetDocumentsPath + PathDelim + 'fortysixtozero.ini' );
try
Count := Ini.ReadInteger( 'Main', 'Count', 0 );
finally
Ini.Free;
end;
end;
Now, knowing that a mobile app can have different states, i am wondering where the best place is to write the ini file?
Best states to save the application state or store settings is "aeEnteredBackground". I used the delphi FMX event here. You should also check the "aeWillBecomeInactive" and "aeWillTerminate" events, but the first one is the most relavant. The application enters background when another application is opened or yours is closed (they are not terminated right away).
Check this article.
The code to listen for events looks like this:
function TfMain.HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
begin
case AAppEvent of
aeFinishedLaunching: ;
aeBecameActive: ;
aeWillBecomeInactive: ;
aeEnteredBackground: ;
aeWillBecomeForeground: ;
aeWillTerminate: ;
aeLowMemory: ;
aeTimeChange: ;
aeOpenURL: ;
end;
Result := True;
end;
To attach the listener you use the platform services:
if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(SvcEvents)) then
SvcEvents.SetApplicationEventHandler(HandleAppEvent);
Just add "FMX.Platform" to your uses clause.
In Delphi XE7, there is an "OnSaveState" event for forms. This is the preferred place to save application data since it will execute when an iOS app goes into the "background" state. The documentation is quite helpful... search for "save state".
Here is my code in the main form's OnCreate handler:
procedure TMainWindow.FormCreate( Sender : TObject );
var
IniFile : TIniFile;
Metric : BOOLEAN;
IniFileName : STRING;
Reader : TBinaryReader;
begin
fInitializing := True;
SaveState.StoragePath := TPath.GetLibraryPath;
if SaveState.Stream.Size > 0 then begin
Reader := TBinaryReader.Create( SaveState.Stream );
try
Metric := Reader.ReadBoolean;
vMetricUnits.IsChecked := Metric;
SetSliderLimits( Metric );
Temperature := Reader.ReadDouble;
Dewpoint := Reader.ReadDouble;
Humidity := Reader.ReadDouble;
WindSpeed := Reader.ReadDouble;
finally
Reader.Free;
end;
end
else begin
Metric := False;
vMetricUnits.IsChecked := Metric;
SetSliderLimits( Metric );
Temperature := 70;
Dewpoint := 70;
Humidity := 100;
WindSpeed := 0;
end;
SetMetricUnits( cMetricUnits );
fInitializing := False;
WriteTrackbarCaptions;
CalculateTemperatures;
end;
And here is the code in the form's OnSaveState handler:
procedure TMainWindow.FormSaveState( Sender : TObject );
var
Writer : TBinaryWriter;
begin
SaveState.Stream.Clear;
Writer := TBinaryWriter.Create( SaveState.Stream );
try
Writer.Write( cMetricUnits );
Writer.Write( Temperature );
Writer.Write( Dewpoint );
Writer.Write( Humidity );
Writer.Write( WindSpeed );
finally
Writer.Free;
end;
end;
I've tested this on both the iPad and in Windows and it works on both platforms. Doing it this way completely avoids the use of the .ini file, however it does create a somewhat oddly named .tmp file in the Windows version. I assume that an equivalent file is also created on the iPad.
Work on a project and I want to save the images I downloadin a blob field. I use idHTTP with success but UI freezes.
I create a procedure to download images from URL using ImageLoader method (Image Loader page). Everything is OK with the images but I cannot save them in the specific field in DB.
How can I do it?
Here is my code :
procedure TForm1.LoadImages;
var
i: integer;
IMLO: TImageLoader;
IM: TImage;
Stream: TMemoryStream;
imgAddress: string;
begin
Table1.First;
for i := 0 to Table1.RecordCount-1 do
begin
imgAddress := Table1.FieldByName('URL').AsString;
Stream := TMemoryStream.Create;
IM:= TImage.Create(self);
IM.Name := 'IM'+inttostr(i);
IM.Parent := HorzScrollBox1;
IM.Position.X := i * 320;
IM.Align:= TAlignLayout.alLeft;
IM.Width := 300;
IMLO.LoadImage(IM, imgAddress);
{ Table1.Edit;
TBlobField(Table1.FieldByName('image')).LoadFromStream(Stream);
Table1.Post;
Stream.Free; }
Table1.Next;
end;
end;
The code works perfectly if I want just to show images. How can I use stream to save them?
Problem
The problem is that the TImageLoader.LoadImage loads the data from the net asynchronously.
Because of that the data is not yet available at the time when you're loading the data.
Simple solution
In order to stop TImageLoader from doing that you can add an extra method to it and call that one instead.
The other problem is that you are loading the Blob with the Stream data, but you never load the stream with anything.
Luckily you don't need the stream.
function TImageLoader.LoadImageNow(const AImage: TImage; const AImageURL: string): boolean;
const
success = true;
failure = false;
var
MS : TMemoryStream;
Image: TImage;
begin
Result:= success;
MS := TMemoryStream.Create;
Image:= TImage.Create;
try
IdHTTP1.get(AImageURL,MS);
Ms.Seek(0,soFromBeginning);
Image.LoadFromStream(MS);
AImage.Picture.Assign(Image);
except
Result:= failure;
end;
FreeAndNil(Image);
FreeAndNil(MS);
end;
Now the loader will only return when the image is loaded.
This also explains why you get an exception upon freeing IM.
You're freeing IM, but the loader is still using it in the background.
That's the problem with using multi-threaded applications, you need to keep track of what the other threads are doing.
The code for your loop will now look like this:
procedure TForm1.LoadImages;
var
i: integer;
IMLO: TImageLoader;
IM: TImage;
imgAddress: string;
FieldImage: TGraphicField;
FieldUrl: TField;
begin
FieldImage:= TGraphicField(Table1.FieldByName('image'));
FieldUrl:= Table1.FieldByName('URL');
Table1.First;
for i := 0 to Table1.RecordCount-1 do try
imgAddress := FieldUrl.AsString;
IM:= TImage.Create(self);
IM.Name := 'IM'+inttostr(i);
IM.Parent := HorzScrollBox1;
IM.Position.X := i * 320;
IM.Align:= TAlignLayout.alLeft;
IM.Width := 300;
if IMLO.LoadImageNow(IM, imgAddress) then begin
Table1.Edit;
FieldImage.Assign(IM.Picture);
Table1.Post;
end; {if}
finally
IM.Free;
Stream.Free;
Table1.Next;
end; {for .. try}
end;
Because FieldByName is slow it is best to pull it out of a loop (it probably will not matter because of the slowness of LoadImageNow).
See: http://docwiki.embarcadero.com/Libraries/XE5/en/Data.DB.TBlobField.Assign
And: http://docwiki.embarcadero.com/CodeExamples/XE3/en/HTTP_Get_%28Delphi%29
Other option
Add an event to the thread that you can hook into every time a image finishes loading.
In the event handler you can then display the image and put it into the database.
In fact the async loader may already have an option for this, but I do not know that codebase well enough to answer that question.
I am building an application for Android using Delphi XE5 that makes use of the Zxing barcode application and it uses the clipboard to retrieve the result. All of the code (Most of it anyway) is from a tutorial that I have found on the web. When I followed the tutorial, it worked to a charm but when applying the SAME code within an application that I was already working on - it did not work. Whenever accessing the clipboard ( in the 'OnTimer' event), the application always hangs and stops working. No error, nothing. App freezes and I have to close it via the phone's task manager.
The application fails right after the following line
Log.Add('AndroidClipboardScanner:1'); Log.SaveToFile(INIFileLog);
I have changed the coding around so that the app assumed the Clipboard service was available in the Ontimer event and it proceeded but it then failed after the following line:
Log.Add('AndroidClipboardScanner:4'); Log.SaveToFile(INIFileLog);
I am not sure where to begin debugging because the same code works in the other application that I created following the initial guide I found. I can also confirm that the ClipService is being assigned properly, otherwise the intent wouldn't even begin. Any help or guidance would be much appreciated ! Below is my code...
This Declared in the 'Private' variables section of the form:
ClipService: IFMXClipboardService;
This within the 'OnTimer' event for Timer1:
procedure TMain_Form.Timer1Timer(Sender: TObject);
var
barCode : String;
begin
timer1.Enabled := false;
Log.Add('AndroidClipboardScanner:0.1'); Log.SaveToFile(INIFileLog);
Try
if assigned(ClipService) then begin
Log.Add('AndroidClipboardScanner:1'); Log.SaveToFile(INIFileLog);
if (ClipService.GetClipboard.ToString <> 'nil') then
begin
Log.Add('AndroidClipboardScanner:2'); Log.SaveToFile(INIFileLog);
timer1.Enabled := false;
Log.Add('AndroidClipboardScanner:3'); Log.SaveToFile(INIFileLog);
Elapsed := 0;
Log.Add('AndroidClipboardScanner:4'); Log.SaveToFile(INIFileLog);
editHold.PasteFromClipboard;
//EditHold.Text := ClipService.GetClipboard.ToString;
Log.Add('AndroidClipboardScanner:5'); Log.SaveToFile(INIFileLog);
end else
begin
Log.Add('AndroidClipboardScanner:6'); Log.SaveToFile(INIFileLog);
Timer1.Enabled := False;
Log.Add('AndroidClipboardScanner:7'); Log.SaveToFile(INIFileLog);
end;
Log.Add('AndroidClipboardScanner:8'); Log.SaveToFile(INIFileLog);
end else begin
ShowMessage('Unexpected error has occured');
end;
Except
ShowMessage('Unexpected error has occured..');
End;
end;
Within the ONCreate procedure of the form:
if not TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService,
IInterface(ClipService)) then begin
ShowMessage('Clipboard Failed:1');
ClipService := nil;
end;
Elapsed := 0;
This is for click event for the button that begins the intent:
procedure TMain_Form.Button_ShowScannerClick(Sender: TObject);
{$IFDEF ANDROID}
var
intent: JIntent; {$ENDIF}
begin
{$IFDEF ANDROID}
//ShowMessage('Scanner:1');
if assigned(ClipService) then begin
//ShowMessage('Scanner:2');
ClipService.SetClipboard('nil');
intent := tjintent.Create;
intent.setAction(stringtojstring('com.google.zxing.client.android.SCAN'));
intent.putExtra(tjintent.JavaClass.EXTRA_INTENT,
stringtojstring('"SCAN_MODE"'));
sharedactivity.startActivityForResult(intent,0);
Elapsed := 0;
timer1.Enabled := true;
//ShowMessage('Scanner:3');
end;
{$ENDIF}
You didn't set the SCAN_MODE parameters like:
intent.putExtra(tjintent.JavaClass.EXTRA_TEXT,
stringtojstring('"SCAN_MODE","ONE_D_MODE,QR_CODE_MODE,PRODUCT_MODE,DATA_MATRIX_MODE"'));
Also you can check timer interval parameter...
I tested the solution on few devices.
With low interval value I had black screen sometimes