The issue only happens on the Xiaomi device (or possibly other brands I haven’t tested). It works fine on the Pixel.
In my project, I load data from an API endpoint into a local SQLite database. Then, I take photos and save them alongside the downloaded data.
Here is the problematic code:
procedure TPhotoForm.TakePicture(Sender: TObject);
begin
if TOSVersion.Check(11) then
ActionTakePhotoFromCamera.Execute;
end;
I’m using the exact same code from the Embarcadero example: https://docwiki.embarcadero.com/CodeExamples/Athens/en/FMX.PhotoEditorDemo_Sample
The issue happens after calling the procedure. The system camera opens, and I can take a picture. However, when I select the picture (to trigger the OnFinishTakingPicture event), the app crashes. There’s no error message—the app just restarts like I launched it again.
Here’s the twist: this doesn’t happen every time. After a few retries (3–4 attempts), or if I restart the app, the photo-taking process works fine. Once it works, it will keep working—unless I restart the app.
To reproduce the issue consistently:
What works
The sample app from Embarcadero works perfectly on the Xiaomi device. After realizing this, I tested a few things:
Access rights
At first, I thought it was an issue with access rights or the Android manifest. I updated everything to match the demo project. I also discovered that you don’t actually need camera permissions when using the system camera dialog. Interesting, but it didn’t help—the issue still occurs.
Bug in FMX.MediaLibrary.Android
Next, I looked deeper into the code for TAction.TakePhoto
. I traced it to FMX.MediaLibrary.Android
. There are two key methods I investigated:
TImageManagerAndroid.TakePhoto
TImageManagerAndroid.TPhotoActivityResponseListener.onResponse
TakePhoto
passes the required data to FActivityClient.TakePhoto(LRequestParams);
, which is implemented in a JAR file. This always works as expected—it launches the system camera dialog.
The onResponse
method is called every time the camera dialog closes, even when the app crashes. If the app crashes, first, Application.Run
is triggered, then TPhotoActivityResponseListener.onResponse
. But when the app crashes, LParams.OnDidFinishTaking
is invalid because the app restarts. So everything looks fine here—no obvious issues.
Energy management It feels like some kind of battery-saving issue. Maybe Android is trying to free up resources and closes my app while the camera is open. To test this, I manually installed the APK and disabled all battery optimizations for my app. The crash still happened.
**Edit: ** Turns out, the problem is based on the Xiaomi memory optimisation. Every Time the RAM usage of the app is around 150 - 450 MB the system sometimes suspend the App when opening the System Camera (because than my app is going to the background). If the RAM is over 500 MB it fails every time. I tested it even with the Embarcadero Demo app and was able to reproduce the failure.
But I noticed that, because the start Form in the Demo app is the one with the Actionlist on it, the app can regenerate even when getting restarted be getting the Event Call from the OnFinishEvent. Any Idea on how to get the Bitmap if the start Form is not the Same as the one that originally called the take Photo action? Here is the code from FMX.MediaLibrary.Android. interesting part is the one I using TMessageManager.
try
LConverted := JBitmapToBitmap(LNativeBitmap, LBitmap);
LNativeBitmap.recycle;
if LConverted then
begin
if Assigned(LParams.OnDidFinishTaking) then
begin
log.d('OnDidFinishTaking is Assigned');
LParams.OnDidFinishTaking(LBitmap)
end
else
begin
log.d('OnDidFinishTaking is not Assigned');
LRequestKind := response.getRequestKind;
if LRequestKind.equals(TJPhotoActivityRequestKind.JavaClass.PICK) then
TMessageManager.DefaultManager.SendMessage(Self, TMessageDidFinishTakingImageFromLibrary.Create(LBitmap))
else if LRequestKind.equals(TJPhotoActivityRequestKind.JavaClass.TAKE) then
TMessageManager.DefaultManager.SendMessage(Self, TMessageDidFinishTakingImageFromCamera.Create(LBitmap));
end;
end;
finally
LBitmap.Free;
end;
The Problem
When you use the TakePhotoAction
from the ActionList
, your app is pushed to the background, and the system's camera app comes to the foreground. Many manufacturers use this moment to clean up memory, which can result in your app being suspended by the system.
After completing the photo capture, the TPhotoActivityResponseListener.onResponse
from FMX.MediaLibrary.Android
is triggered. However, the original LParams.OnDidFinishTaking method is no longer assigned because the app was suspended during the process.
The threshold for this behavior depends on the manufacturer's memory management settings. Based on my findings:
To mitigate this, you may need to implement logic to handle app suspension and restoration properly or look for alternative ways to manage the photo capture process.
The Solution
First, ensure that the code executed in the OnTakePhotoFinish event only processes the image passed to the event, and that every component involved in this process is properly available after the app restarts. If necessary, save your application's current state by using the SaveState event. This allows you to restore the required app information and navigate to the correct points upon restarting the app. You can find more details in the FireMonkey Save State documentation. https://docwiki.embarcadero.com/RADStudio/Athens/en/FireMonkey_Save_State
On the first form created after your app restarts, you should hook into the MessageManager in the OnCreate event. Here’s an example:
type
TStartForm = class(TForm)
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private-Deklarationen }
procedure MessageHandlerTakePhoto(const Sender: TObject; const M: TMessage);
end;
procedure TStartForm.FormCreate(Sender: TObject);
begin
TMessageManager.DefaultManager.SubscribeToMessage(TMessageDidFinishTakingImageFromCamera, MessageHandlerTakePhoto);
end;
procedure TStartForm.FormDestroy(Sender: TObject);
begin
TMessageManager.DefaultManager.Unsubscribe(TMessageDidFinishTakingImageFromCamera, MessageHandlerTakePhoto);
end;
procedure TStartForm.MessageHandlerTakePhoto(const Sender: TObject; const M: TMessage);
begin
if M is TMessageDidFinishTakingImageFromCamera then
begin
if Assigned(TMessageDidFinishTakingImageFromCamera(M).Value) then
begin
// Do something with the TMessageDidFinishTakingImageFromCamera(M).Value (TBitmap)
end;
end;
end;