3

In my app, I'm working with some huge objects in memory, which are persisted when users use "save" function. The problem is that when the user leaves the app in the background, without saving, after some time, the OS removes those huge objects from memory for increasing free ram memory, so, when the user returns to the app, those objects are null and it crashes.

Does exist a way to know when the OS is killing parts of the app, and block that killing process to store those objects first? If so, I will be able to recover them, but I can't find anything in android documentation.

I know there is a method called onLowMemory, but it is not the solution, because:

While the exact point at which this will be called is not defined, generally it will happen when all background process have been killed.

http://developer.android.com/reference/android/app/Application.html#onLowMemory%28%29

EDIT: sample code for knowing the app comes to foreground and to background:

public class CustomApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // register observer for knowing when application goes to background and to foreground
        ProcessLifecycleOwner.get().getLifecycle().addObserver(new LifecycleObserver(){
            @OnLifecycleEvent(Lifecycle.Event.ON_START)
            public void onAppStarted() {
                Log.d("XXXX", "onAppStarted() called");
            }

            @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
            public void onAppStopped() {
                Log.d("XXXX", "onAppStopped() called");
            }
        });
    }
}
NullPointerException
  • 36,107
  • 79
  • 222
  • 382

2 Answers2

1

the OS removes those huge objects from memory for increasing free ram memory, so, when the user returns to the app, those objects are null and it crashes

More accurately, Android terminates your process.

Does exist a way to know when the OS is killing parts of the app

Android does not "[kill] parts of the app". It terminates your process.

And, no, you are not informed when your process is about to be terminated.

and block that killing process to store those objects first?

No.

You can find out when your app overall moves to the background using ProcessLifecycleOwner. Any time after that occurs, your process could be terminated, for any reason — it is not just memory pressures. The user could revoke a runtime permission via the Settings app, for example.

So, in your case, if you do not want to save the data when changes are being made, you could save the data to a temporary file when your app moves to the background, and restore the data from that temporary file when your process starts back up again.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • thank you @CommonsWare I like your suggestion, I attached a sample code of your proposal to my question, check it. But one point: the app is also calling Lifecycle.Event.ON_START on app start, so, it is a problem, because I need to differenciate between an app starting and an app returning from background. How whould you differenciate them? I only can think in persisting in disk if the app did go to background and reset that value after each Lifecycle.Event.ON_START call – NullPointerException May 06 '20 at 16:28
  • @NullPointerException: "because I need to differenciate between an app starting and an app returning from background" -- why? Either you have a temporary file representing data to be restored, or you do not. – CommonsWare May 06 '20 at 16:32
  • 1
    I understand you. Thanks. One more thing. It is possible to redirect the application to an activity when Lifecycle.Event.ON_START is called? I need to display a "restore" style activity for asking the user if wan'ts to restore the data or go to the main menu. The problem is that the application has tens of activities, and the Lifecycle.Event.ON_STOP can be called in any of them, so I need to centralize all the data recover in an activity with a dialog and the management of the restored data. – NullPointerException May 06 '20 at 17:12
  • @NullPointerException: "It is possible to redirect the application to an activity when Lifecycle.Event.ON_START is called?" -- technically, nothing is stopping you from calling `startActivity()` here, but you run the risk of strange UX. "I need to display a "restore" style activity for asking the user if wan'ts to restore the data or go to the main menu" -- why not have a restore option on the main menu itself? – CommonsWare May 06 '20 at 17:16
  • ummm, i have another problem. I discovered that onResume() of the activity that was in screen before going to background is being called before my recover logic of Lifecycle.Event.ON_START ends starting the "recover" activity. I think that the system is not closing the old activity when I'm starting the new one in Application class. " why not have a restore option on the main menu itself?" -> this app doesn't have a main menu, just screens and navigation between them. In fact, onDestroy is not being called on that old activity. – NullPointerException May 06 '20 at 17:39
  • @NullPointerException: "this app doesn't have a main menu" -- you said it did in your earlier comment ("for asking the user if wan'ts to restore the data or go to the main menu"). "I think that the system is not closing the old activity when I'm starting the new one in Application class" -- at minimum, it will depend on whether your process was terminated. This sort of thing is why I warned against calling `startActivity()` from an `Application`-scope `ProcessLifecycleOwner` observer. – CommonsWare May 06 '20 at 17:44
  • I mean that doesn't have a menu present in all the screens, with mainmenu i'm talking about the first screen visible when app is started. That main menu is not present when you are using the application, so if I put the restore option in that main menu, I need to redirect the user to that screen. But I have that problem, when I call to startActivity, onDestroy is not being called on the old activity present before going to background, so the application is crashing because the old activity is trying to access the missing data. How can i get the current activity (for killing it) in Application? – NullPointerException May 06 '20 at 17:49
  • @NullPointerException: "How can i get the current activity (for killing it) in Application?" -- you can't, sorry. – CommonsWare May 06 '20 at 17:50
  • ummm, so then it's not possible to redirect the user to a "recover" screen or something similar in application class after coming back ground background. – NullPointerException May 06 '20 at 17:51
  • @NullPointerException: Not from your `Application` class directly. Your `Application` could raise an event to say that this recovery option needs to occur, then have the activities react to that event (e.g., via observing a `LiveData`) as needed. – CommonsWare May 06 '20 at 17:54
  • well @CommonsWare I'm changing the application to simply store and recover the data without presenting a recover screen. I like a lot your approach, so I must achieve a solution using these events. The problem is that storing the data every time Lifecycle.Event.ON_STOP is annoying, because in slow devices it takes some seconds. exist a way to do it when the system needs memory and the app killing is near? Or maybe whould be safe to use a postDelayed handler with a 20 second delay to do it? Will that handler be launched always even with the app in background? – NullPointerException May 06 '20 at 18:44
  • @NullPointerException: "exist a way to do it when the system needs memory and the app killing is near?" -- again, low memory is not the only reason why your process might be terminated while it is in the background. You are welcome to use `onTrimMemory()`, but that might not get called. "Will that handler be launched always even with the app in background?" -- I forget, as I would not use `postDelayed()` for non-UI work. `ScheduledExecutorService` is not tied to activity lifecycles and would be a safer option. – CommonsWare May 06 '20 at 18:50
  • but in this case I need to freeze UI, because the data must be stored and recovered before the activity gets resumed, because of that I'm proposing to use postDelayed and load the data in the UI thread. Is not safe? can be cancelled by the system in only 20 seconds? – NullPointerException May 06 '20 at 18:54
  • @NullPointerException: "Is not safe?" -- I forget the behavior of `postDelayed()` in the background, that's all. – CommonsWare May 06 '20 at 19:02
  • Thank you. @CommonsWare I need to differenciate between system destroying the app and the user closing the app, because the backup of the data is being created in both events, but the restore of the data must be done only under some circunstances. For example, using the app, when you are in the first screen, the data is still not necessary, so if the backup is generated and then the user closes the app, when the user opens it again, the first scren will be loaded, without data, and with backup, so the app will understand that must load the backup. Thats a problem. How can I differenciate? – NullPointerException May 06 '20 at 19:33
  • @NullPointerException: Sorry, but I do not know what you mean by "user closing the app". There are countless ways the user could move your app's UI into the background (BACK, HOME, respond to a notification, overview screen, etc.). – CommonsWare May 06 '20 at 19:39
  • With "user closes" I mean if the user closes it in "recent apps" menu, or if the user kills it from "kill application" button in application properties, or if the user press the exit button in the main menu, which does a finish on the last alive activity. These cases will cause a start from the very first screen of the application (splash and main menu) and those screens doesn't need the backup. But the other cases that makes the app going to background (HOME, CALL) can be in activities that needs the data, so in those cases the backup is necessary – NullPointerException May 06 '20 at 19:44
  • @NullPointerException: Well, you know when the user clicks your Exit button, so you can control the behavior of your app when that occurs. IMHO, treating the overview/recent apps screen differently than anything else is unwise. The only way that I know of to detect that scenario requires a running service, which will get called with `onTaskRemoved()`. – CommonsWare May 06 '20 at 19:54
  • it is possible to simulate or forze the sistem to free memory killing the app? I need to check if my final solution works. I'm using a variation of your proposal. Thank you very much – NullPointerException May 08 '20 at 07:04
  • @NullPointerException: I do not know a way of literally doing that. What I sometimes do is terminate the process via `adb` or the red square "stop" button in Android Studio. That leaves the task intact but terminates the process, which is the same effect as the out-of-memory-killer terminating the process. – CommonsWare May 08 '20 at 11:32
  • thank you very much, I think i achieved what i need, but instead of recovering the data in Lifecycle.Event.ON_START, I'm recovering it in the onCreate of the activities which needs the data, but only if the data is null and the backup is created. I'm still testing it, but i think it will work. You helped me a lot. Thank you very much – NullPointerException May 08 '20 at 12:22
0

Have you tried using a custom Application class that extends Application.ActivityLifecycleCallbacks? There is also OnTrimMemory in the Application class itself

tch199
  • 3
  • 1
  • 1
  • 3