Can somebody clarify please why I have such a weird behavior. Up to documentation the Bundle savedInstanceState which is set in onSaveInstanceState() is alive as long as application alive, so when it it in foreground or background. After the application is being killed the savedInstanceState instance is killed as well. Here is what I have:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (savedInstanceState != null) {
Log.i("Dev", "not null");
} else {
Log.i("Dev", "null");
}
}
Here is how I set it:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("bool", true);
}
Then, I'm starting the application in the emulator. After application is opened I click home button so the Launcher is visible. Then I kill the application's process using adb. After that I start the application from the list of recently used application expecting for "null" in the Logcat, but what I actually see is "not null", so my understanding is incorrect?
The Bundle is saved for as long as Android wants it to be saved/can save it. One of the "features" (quotes because it ends up being a bad idea as often as it is a good one) of Android is that applications are never exited (to the user's view). Their mechanism of doing this is the onSaveInstanceState- it stores the Bundle, and when the app is later reinitialized by some method (such as from the recent activities menu) it will pass that Bundle to the onCreate and let it re-initialize itself.
Of course this also causes problems. For example, if you save login info, exiting an application won't log you out. So a user can then just hand his phone to a friend to watch a video, thinking that he exited his mobile banking app and is safe, yet the friend can call it back up and recreate it. If your app has large data structures in static variables or singletons they will not be recreated unless you code it carefully. Apps that require activities to be explored in order can be restarted from the middle.
Now Android can choose to forget your Bundle. If you put several MB in it, I would expect android to forget it rapidly. But it will remember it for as long as it can.
Isn't it very clearly stated here ? Or do I missunderstand your question?
[..]To save additional data about the activity state, you must override the onSaveInstanceState() callback method. The system calls this method when the user is leaving your activity and passes it the Bundle object that will be saved in the event that your activity is destroyed unexpectedly. If the system must recreate the activity instance later, it passes the same Bundle object to both the onRestoreInstanceState() and onCreate() methods.
I mean for me this is also reasonable in most situations. Because when your activity / app is in the background and the android system closes it (let's say because it needs more memory), then it first saves the state. So next time the users opens your activity, you can restore it's previous state (and that may also exactly be what the user wants, since it wasn't him who closed the activity, but the system itself).
Related
I am not understanding how android activities are managed.
I have an activity and every once in a while i have noticed that, when my app goes into the background, android destroys whatever the current activity is (say Activity3) and several other singletons and objects etc. Thats fine. The problem is when the app is resumed then intuition tells me that since android has destroyed the activity and objects for memory or whatever, then android would just restart the app completely from Activity1 so all the objects and data members would get properly initalized.
NOT SO!
It seems that when my app is resumed, the Activity3 is recreated and onCreate is called with the same parameters as it was the first time (when it was called from Activity2) only this time all the singletons and other objects that were initialized in Activity1 and Activity2 are recreated with their default values and are rendered useless.
How is this a safe policy/technique? How can android just randomly destroy objects and activities and then when the user resumes just call onCreate on the recent activity and expect everything to be hunky doory and NOT have to go through the proper startup procedure/initialization?
UPDATE / SOLUTION
Thanks to the commentors for their excellent info.
ACCORDING TO ANDROID DOCUMENTATION
onCreate
Bundle: If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Note: Otherwise it is null.
THEREFORE what I ended up doing is I set TWO flags. One in onSaveInstanceState in the Bundle so to know that it is a valid Bundle set by me. The other in the class itself to determine if onCreate was called because of recreation or Auto-Rotation. And so in onCreate I checked to see if onSaveInstanceState is not null, check the Bundle flag, and check bInit (which defaults to false). If both flags are true then it means android dumped and destroyed our apps memory and the safest way to ensure everything is initialized again in a linear-style application is to just restart it and launch the beginning activity.
public class SomeMiddleActivity extends AppCompatActivity
{
private static boolean bInit = false; // only way it will be false again is if android cleared our memory and we are recreating
#Override
public void onSaveInstanceState(Bundle state)
{
// set a flag so that onCreate knows this is valid
state.putBoolean("StateSaved", true);
super.onSaveInstanceState(state);
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
// this must be called first always for some reason
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
if (savedInstanceState.getBoolean("StateSaved", false) && !bInit)
{
// we were recreated... start app over
Intent intent = new Intent(getApplicationContext(), Startup.class);
startActivity(intent);
finish();
return;
}
}
bInit = true; // this will stay true until android has cleared our memory
.......
}
Although this has worked thus far, if anyone has a different suggestion let me know. I will be posting another article on this.
And FYI: the onSaveInstanceState(Bundle, PersistableBundle) version of onSaveInstanceState is never called ever so I dont know why they even implement it. (?)
#goldenb #Rishabh Thanks to goldenb and Rishabh for the insight.
Android, if destroys, also gives you tools to handle it.
Mobile devices have limited amount of memory which needs to be shared among Applications running simultaneously. Thus, smart resource allocation is necessary. The Apps running on foreground are used by End-User and gain high priority for better performance and user experience. Thus, applications running in background need to release the resources to suffice the memory requirements for foreground applications. Hence, background applications are destroyed (not completely) sometimes (in case of low memory).
Android Activities have Callbacks likes onSaveInstanceState() and onRestoreInstanceState() which enable you to save your current state of Activity (i.e., values of variables) when it is destroyed and retrieve them when the Activity is recreated.
You can get more information from here: How to save and retrieve the state of Activity using onSaveInstanceState and onRestoreInstanceState.
You can perform validations on the retreived state to ensure the Activity performs exactly as it was doing pre-destruction. You would find it very easy and logical once you get hands-on it.
Just giving my 50 cents on the issue. The correct way to deal with the issue of an activity being killed by the system for its resources in background is a common problem in android and according to Google the solution for this is:
onPause() is where you deal with the user leaving your activity. Most
importantly, any changes made by the user should at this point be
committed (usually to the ContentProvider holding the data).
Emphasis is mine. But what this means is that the Android lifecycles are designed so that under normal conditions onPause should be called as an Activity or Fragment is sent to the background. They hint at this in several of the android documentation pages:
As your activity enters the paused state, the system calls the onPause() method on your Activity, which allows you to stop ongoing actions that should not continue while paused (such as a video) or persist any information that should be permanently saved in case the user continues to leave your app.
Also worthy of your attention: if you wish that views are restored during Activity recreation, you should have set the ID attribute of all views ;)
Note: In order for the Android system to restore the state of the
views in your activity, each view must have a unique ID, supplied by
the android:id attribute.
PS.
You were wondering why onSaveInstanceState(Bundle, PersistableBundle) is not called, one possibility is that you do not have the right activity attribute set
This is the same as onRestoreInstanceState(Bundle) but is called for
activities created with the attribute persistableMode set to
persistAcrossReboots..
I know this is not a question but I've been struggling with this issue for a long time, whenever i work with fragments, when I leave the app (not destroyed, just click on home button) and I return to it after some amount of time, it gives nullpointerexception for all my variables and everything, I know this is a memory issue where it clears some space for other apps, but how can I workaround this and protect my objects from beeing destroyed,
thank you all
Have a look at
onSaveInstanceState(..)
This gets called when user minimizes your app -> by hitting the home button or when the OS is about to destroy your app. At this point you can save all the needed variables in the bundle that is passed in. This bundle is written to disk and passed back when your activity is recreated:
If you just want to retrieve your variables in the Activity you can use:
protected void onRestoreInstanceState (Bundle savedInstanceState)
Fragment do not have onRestoreInstanceState(...) but you can use onActivityCreated(...) to access the bundle if you want to retrieve your variables in the fragment.
Does anyone know of an exhaustive list of when the savedInstanceState bundle will be used in an activity?
I know it's used when the device orientation changes. However, it doesn't seem to be used when the user force closes the app from the Android settings, but this might be due to something in my code.
What other cases are there?
To be clear, by "used" I mean when onCreate() is called, the savedInstanceState bundle is not null and contains the data I passed into it the last time onSaveInstanceState() was called.
It's used when the Activity is forcefully terminated by the OS (ex: when your Activity is in the background and another task needs resources). When this happens, onSaveInstanceState(Bundle outstate) will be called and it's up to your app to add any state data you want to save in outstate.
When the user resumes your Activity, onCreate(Bundle savedInstanceState) gets called and savedInstanceState will be non-null if your Activity was terminated in a scenario described above. Your app can then grab the data from savedInstanceState and regenerate your Activity's state to how it was when the user last saw it.
Basically in onCreate, when savedInstanceState is null, then it means this is a 'fresh' launch of your Activity. And when it's non-null (if your app saved the data in onSaveInstanceState(...), it means the Activity state needs to be recreated.
onSaveInstanceState is used to store data only for application lifetime (i.e. temporarily)
The data is held in memory only until the application is alive, in other words this data is lost when the application closes, so in your case when you force close the app onSaveInstanceState is not used.
It can only be called when you do operations while your application is still alive, for e.g. when you change the screen orientation the activity is still intact so onSaveInstanceState is called.
However if you want to permanently store the data you would have to use SharedPreferences and SQLite database.
So I have the following:
A Common class that many of my Activities access in my android application, via setting the class in my manifest:
<application android:name="com.dev.games.phraseparty.Common"... />
Now, within this class I have several objects that are constructed to preserve application state and common services to perform applications that are constructed in the Common constructor
ie
GameStateVO gameState;
public Common()
{
gameState = new GameStateVO();
}
My problem is that my Activity has an Admob ad. When the user clicks on the ad, it calls the webbrowser intent to open the ad URL in a webbrowser.
Now, when I click back from the webbrowser launched by admob, it takes me back to the caling activity and the OnCreate method is called.
This activity then gets a null pointer exception because it will do something like:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Common common = this.getApplication();
//null pointer here since the game state VO is null since the Common has lost its state.
int score = common.getGameState().getScore();
}
If you have no active foreground activity, then your process is ripe for shutdown by the OS to obtain more resources. The browser app in particular i've noticed uses a lot of resources and can quickly lead to background activities and later processes being killed off. Having a service can help keep your process around, but even then it can still be killed off if needed. I think you'll need to use the activity lifetime cycle method to save & restore your state. See the process lifecycle section of the docs for more info
You might want to look into implementing the onSaveInstanceState method: this lets you store any relevant information before it gets killed off. See http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState%28android.os.Bundle%29 for the actual call you need to implement, and Saving Android Activity state using Save Instance State for a quite excellent example.
I am using a separate class with only static fields, to store current application data.
It is partly populated from sharedpreferences on application startup. The rest is data like results of some action, used for further browsing these results (multiple activities that use the results).
I can go to the home screen, start other applications etc. and when I return to my own application it just works correctly.
However, since the new Error Reporting feature I get some bug reports all related to a nullreference error. The object that is null is a reference to the static field in the mentioned separate class.
Since I cannot reproduce the bug I am inclined to think this is due to the application getting killed due to low memory, and when it relaunches it calls the oncreate from the activity that the user was currently in. However all the static data in the separate class is not restored and thus it crashes.
I would like to know: Is there a way to force the application to "restart" completely, and not start with the last used activity if it gets killed? Or is that standard behaviour?
Can I do this programmatically? Like when the static fields are null, restart app?
Restarting the activity where the user was is normal behaviour - the idea is to make it look to the user like the app was never closed. There are two things you can look at:
protected void onSaveInstanceState(Bundle outState){
// This gets called by the system when it's about to kill your app
// Put all your data in the outState bundle
}
That bundle is the same one that gets passed to the activity in onCreate(). You can then get any necessary information out of it and restore the values in the static class.
The other way is to simply check the values in the onResume() method of any of your activities. If the values are null or wrong in some way, then you can call start the original activity and finish() the one being started.