Jump to content
fisipjm

TakePhotoAction crashes android app and starts again on xiaomi Android 12

Recommended Posts

Crosspost Stackoverflow

 

Environment:

  • Delphi 12.2 (latest SP)
  • APP for Android and iOS
  • Hardware Pixel 7a (Android 15) / Xiaomi Redmi (Android 12 SP1)

 

Problem:
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:

  1.  Uninstall the app
  2.  Reinstall it
  3.  Take a photo on the first run.

The crash happens every time in this scenario.

 

Investigations so far:

 

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.

 

Log Files
I also checked the ADB logs. There’s a specific message logged every time the crash occurs, but I couldn’t find any information about it online neither do I know if this is a Problem or not. Here’s a snippet from the logs:

    2025.01.23 09:48:27.293    W    5929    System.err    org.json.JSONException: No value for cannong
    2025.01.23 09:48:27.298    W    5929    System.err    at org.json.JSONObject.get(JSONObject.java:398)
    2025.01.23 09:48:27.298    W    5929    System.err    at org.json.JSONObject.getJSONObject(JSONObject.java:618)
    2025.01.23 09:48:27.298    W    5929    System.err    at android.util.MiuiMultiWindowUtils.initFreeFormResolutionArgsOfDevice(MiuiMultiWindowUtils.java:1436)
    2025.01.23 09:48:27.298    W    5929    System.err    at android.util.MiuiMultiWindowUtils.initFreeFormResolutionArgs(MiuiMultiWindowUtils.java:1430)
    2025.01.23 09:48:27.298    W    5929    System.err    at android.util.MiuiMultiWindowUtils.<clinit>(MiuiMultiWindowUtils.java:212)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.android.internal.policy.DecorViewMultiWinStubImpl.<init>(DecorViewMultiWinStubImpl.java:19)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.android.internal.policy.DecorViewMultiWinStubImpl$Provider.provideNewInstance(DecorViewMultiWinStubImpl$Provider.java:14)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.android.internal.policy.DecorViewMultiWinStubImpl$Provider.provideNewInstance(DecorViewMultiWinStubImpl$Provider.java:8)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.miui.base.MiuiStubRegistry.get(MiuiStubRegistry.java:129)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.miui.base.MiuiStubUtil.newInstance(MiuiStubUtil.java:77)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.android.internal.policy.DecorViewMultiWinStub.newInstance(DecorViewMultiWinStub.java:11)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.android.internal.policy.DecorView.<init>(DecorView.java:335)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.android.internal.policy.PhoneWindow.generateDecor(PhoneWindow.java:2380)
    2025.01.23 09:48:27.298    W    5929    System.err    at com.android.internal.policy.PhoneWindow.installDecor(PhoneWindow.java:2760)
    2025.01.23 09:48:27.299    W    5929    System.err    at com.android.internal.policy.PhoneWindow.getDecorView(PhoneWindow.java:2140)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.view.Window.findViewById(Window.java:1516)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.Activity.findViewById(Activity.java:3492)
    2025.01.23 09:48:27.299    W    5929    System.err    at com.embarcadero.firemonkey.keyboard.VirtualKeyboardFrameObserver.<init>(VirtualKeyboardFrameObserver.java:56)
    2025.01.23 09:48:27.299    W    5929    System.err    at com.embarcadero.firemonkey.keyboard.VirtualKeyboard.<init>(VirtualKeyboard.java:41)
    2025.01.23 09:48:27.299    W    5929    System.err    at com.embarcadero.firemonkey.FMXNativeActivity.onCreate(FMXNativeActivity.java:125)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.Activity.performCreate(Activity.java:8176)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.Activity.performCreate(Activity.java:8143)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1329)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3752)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3946)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:106)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2324)
    2025.01.23 09:48:27.299    W    5929    System.err    at android.os.Handler.dispatchMessage(Handler.java:106)
    2025.01.23 09:48:27.300    W    5929    System.err    at android.os.Looper.loopOnce(Looper.java:210)
    2025.01.23 09:48:27.300    W    5929    System.err    at android.os.Looper.loop(Looper.java:299)
    2025.01.23 09:48:27.300    W    5929    System.err    at android.app.ActivityThread.main(ActivityThread.java:8273)
    2025.01.23 09:48:27.300    W    5929    System.err    at java.lang.reflect.Method.invoke(Native Method)
    2025.01.23 09:48:27.300    W    5929    System.err    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:576)
    2025.01.23 09:48:27.300    W    5929    System.err    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1073)

 

So, after all this, I’m running out of ideas on how to solve this problem. At this point, it feels like Android is terminating some background processes, which causes my app to crash. This might be happening because my app uses more RAM compared to a simple demo application like the one provided by Emba. Does anyone else have experience with this issue or any suggestions on how to move forward? Any help would be greatly appreciated.

 

Best Regards
PJM

Share this post


Link to post

Hi

Do you ask for Android permissions to take a photo in your projects options and in your code ?

The standard actions only call the APIs, they don't ask for the permissions for us.

Share this post


Link to post
procedure TfrmMain.GoToPhotoScreen(AskPermission: boolean);
begin
  if AskPermission then
    TPermissionsService.DefaultService.RequestPermissions
      (['android.permission.CAMERA'],
      procedure(const APermissions: TClassicStringDynArray;
        const AGrantResults: TClassicPermissionStatusDynArray)
      var
        i: integer;
      begin
        for i := 0 to length(AGrantResults) - 1 do
          if (AGrantResults[i] = TPermissionStatus.Denied) then
            raise exception.create
              ('Permission nécessaire pour prendre une photo.');
        GoToPhotoScreen(false);
      end,
      procedure(const APermissions: TClassicStringDynArray;
        const APostRationaleProc: TProc)
      begin
        TDialogService.showmessage
          ('Il est nécessaire d''avoir cette autorisation pour prendre une photo.',
          procedure(Const AModalResult: TModalResult)
          begin
            if assigned(APostRationaleProc) then
              APostRationaleProc;
          end);
      end)
  else
  begin
    MaskPath.data.data := FCurrentProject.MaskPath.data.data;

    ActiveLaCamera(tconfig.CameraType);

    CurrentScreen := rPhotoScreen;
  end;
end;

You need something like this code from https://github.com/DeveloppeurPascal/Fie-Frapic/blob/main/src/fMain.pas and the good permissions for your manifest.

The TakePhoto call is in "ActiveLaCamera() but you can put it in the ELSE part of this code.

 

 

Edited by Patrick PREMARTIN

Share this post


Link to post

Hi Patrick,

 

Thanks for your Reply. But what you are saying is not true.

It is true, If you use the Camera Component on your App itself for lets say perform qrCodeScan or somthing similar. But if you use the TakePhotoAction from the action list, the Application that is used to take the picture is switched to system camera and handles the Bitmap in the on Finished procedure. That works without the need of any System and User Permission for the Camera. The Example from Embarcadero shows this too.

Share this post


Link to post

Right, I use the camera stream.

 

This page explains what Android expect (for the gallery)https://developer.android.com/about/versions/14/changes/partial-photo-video-access

 

And this one for the TakePhotoFromCamera API : https://developpeur-pascal.fr/utiliser-l-action-standard-takephotofromcamera-pour-les-applications-android-9-et-plus.html
where I use the same permissions, but you also need the "secured file sharing" right from Project / Options / Rights / Android32&64

Edited by Patrick PREMARTIN

Share this post


Link to post
1 hour ago, fisipjm said:

... But if you use the TakePhotoAction ... That works without the need of any System and User Permission for the Camera.

I'm not sure, if that is still true on modern phones with modern OS, the examples were quite old, from the era before permission discussions perhaps.

I would generally recommend to request permission before touching any camera part, that cost not much,
and will be considered as good behaviour to ask the user before touching dangerous parts.

Share this post


Link to post

Thank you both for your Answers.

 

The information about not needing any permission when using the System Camera is completely accurate. I tested it, and it works as expected. Please mark the permission topic as completed. This makes sense because my app isn’t directly taking the photo—it’s the System Camera app that has access to the hardware.

 

I’ve gathered some new information about what seems to be the app being "wiped" from the background. To investigate further, I added a timer and an information panel to the sample application to monitor its memory usage. The app also includes a function to add 50 MB to the RAM with each click.

Here’s what I found:

  1. Normal Operation: When the app runs on a Xiaomi device, it typically uses around 50–60 MB of RAM. Taking a photo works without any issues.
  2. High RAM Usage: When I increase the app’s RAM usage significantly (e.g., by manually adding memory), the demo app crashes when taking a photo.
  3. Memory Thresholds:
    • A "soft limit" seems to exist between 200 MB and 450 MB, beyond which the app becomes unstable.
    • At 500 MB, there is a "hard limit" where the app consistently crashes and restarts.
    • After the crash, the app restarts with a memory footprint of around 50–60 MB again

Unfortunately, I couldn’t stop MIUI from killing the app in the background.

I followed all the suggestions from this StackOverflow thread:

https://stackoverflow.com/questions/70860054/xiaomi-miui-keeps-killing-my-background-application 

Without success.

 

One thing that baffles me is that even if the demo app crashes during the photo-taking process, the picture taken is still displayed in the system image viewer. How is this even possible? It looks like the 

TImageManagerAndroid.TPhotoActivityResponseListener.onResponse

 trys to prevent the Bitmap from getting loss even if the OnDidFinishTaking Event is not Assigned any more. Do you have any Idea on how to use the Bitmap after the app starts from the beginning? My Starting form is not the One I want to get the Image into. Here is what the onResponse method does (with some little Logs added) but I do not know how to handle that properly. See the Part where log.d is onDidFinishTaking is not assigned. 

 

procedure TImageManagerAndroid.TPhotoActivityResponseListener.onResponse(response: JPhotoActivityResponse);
var
  LParams: TParamsPhotoQuery;
  LNativeBitmap: JBitmap;
  LBitmap: TBitmap;
  LConverted: Boolean;
  LRequestKind: JPhotoActivityRequestKind;
  LException: JException;
  LFailureCause: TTakingFailureCause;

  function GetFailureCauseFromException(const Exception: JException): TTakingFailureCause;
  var
    LName: string;
  begin
    LName := JStringToString(Exception.getClass.getName);

    if LName = 'com.embarcadero.firemonkey.medialibrary.FileNotCreatedException' then
      Result := TTakingFailureCause.FileNotCreated
    else if LName = 'java.io.FileNotFoundException' then
      Result := TTakingFailureCause.FileNotAvailable
    else if LName = 'com.embarcadero.firemonkey.decode.BitmapDecodeException' then
      Result := TTakingFailureCause.DecodeError
    else
      Result := TTakingFailureCause.UnknownError;
  end;

begin
  log.d('Response Listener Start');
  LParams := FImageManager.FParams;

  if response.isCancelled then
  begin
    log.d('Response is Cancelled');
    if Assigned(LParams.OnDidCancelTaking) then
      LParams.OnDidCancelTaking
    else
      TMessageManager.DefaultManager.SendMessage(FImageManager, TMessageDidCancelTaking.Create);
  end
  else if response.isSuccessful then
  begin
    log.d('Response is Successful');
    LNativeBitmap := TJBitmap.Wrap(response.getResult);
    LBitmap := TBitmap.Create;

    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;
  end
  else
  begin
    log.d('Response is Exception');
    LException := response.getException;
    LFailureCause := GetFailureCauseFromException(LException);

    TJutil_Log.JavaClass.w(StringToJString('PhotoLibrary'), LException);

    if Assigned(LParams.OnDidFailTaking) then
      LParams.OnDidFailTaking(LFailureCause)
    else
      TMessageManager.DefaultManager.SendMessage(FImageManager, TMessageDidFailTaking.Create(LFailureCause));
  end;
    log.d('Response Listener End');
end;


 

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×