I started reading around about the activity life cycle callbacks and saving state and there are quite a few things I don't understand - I'm writing an android app but I want to ask more general questions than how to do it specifically for the few activities etc I have at the moment, I would like to have a better overall view of how this works!
There are two groups of methods I have seen being used (I have seen one or two others but don't want to confuse myself even further...)
onPause, onResume etc,
and then the onSaveInstanceState ones.
What is the difference between them and the circumstances we should be looking to use them? I have seen some questions where a poster is using one of the normal life cycle callbacks, and is told to use onSaveInstanceState instead, so when should we be implementing onPause rather than onSaveInstanceState and so on. Some posts mentioned about methods being used for transient state only, could someone expand on that?
I have seen state being used to mean slightly different things - UI/View state and Activity state, what is the difference between the two?
I am also a bit unsure with what they mean by state, when we are saving state what kind of things are we saving exactly, could anyone give some quick examples (I don't mean actual code)? The android developer guides say that the android system automatically takes care of some of this, so what should we be concerned with? Bundle objects used by onCreate and onSaveInstanceState only store simple values, so what about more complex objects and arrays.
Thanks
protected void onPause ()
protected void onSaveInstanceState (Bundle outState)
Just by looking at it, onSaveInstanceState has an Bundle you can put your things you need to save in it. And get it back in onCreate(Bundle) or onRestoreInstanceState(Bundle);
Some important lines in the document:
This method is called before an activity may be killed so that when it
comes back some time in the future it can restore its state. Do not
confuse this method with activity lifecycle callbacks such as
onPause(), which is always called when an activity is being placed in
the background or on its way to destruction, or onStop() which is
called before destruction.
Android can destroy your activity or even kill your process at any given time (not likely when it is visible to the user though :-)). When the user navigates back to the activity, the data/info that was shown on the screen before he or she left it should be shown again.
The onSaveInstanceState callback allows you to do this.
Most of the Views already do this for you automatically. E.g. the current text in an EditText, the current scroll position of a ListView, etc. are all automatically saved for you.
However, there are some things that are not automatically saved for you. E.g. the current text in a TextView, the (changed) background drawable of a particular View.
Say, you show an error message after a user action fails. The error message is then shown in a TextField and this TextField's background becomes red (i'm just making this up here :-)). When the user leaves the activity while this error is shown (e.g. presses Home button), the activity is destroyed, the error message and the red background won't be shown again when the user comes back to the activity.
This is where onSaveInstanceState comes to the rescue.
You can save a String in there that containts the error message. Then when the activity is re-created, the Bundle savedInstanceState of the onCreate is not null and you can query it for the error message. If this message is not null/empty, call setText on the TextView for the error message and make that TextView's background red.
try to use this code to save state
#Override
protected void onSaveInstanceState(Bundle outState) {
State s = new State(yourTextView.getText().toString());
outState.putSerializable(State.STATE, s);
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
State s = (State) savedInstanceState.getSerializable(State.STATE);
yourTextView.setText(s.getYourTextViewText());
}
Related
I have started studying Android programming recently after taking a very long break (half a decade) from programming altogether. It's been going well so far.
I have noticed that Seek Bar's progress survives onDestroy event after a rotation (configuration change) happens and this happened on a fragmented activity. Then I created an empty activity and added the widget in the layout and the result is the same (as I expected but still tried due to some code). I also tried overriding the onCreate method and passing a null SavedBundleInstance into super.onCreate() and the result is the same. So I don't know where the Seek Bar's progress location is saved before onDestroy and then passed back unless I am missing some static variable in the SeekBar class or its super classes. (I just thought about that now, I should check it)
Can someone explain why this happens?
Usually, Android Views save their state upon rotation. The same happens for example with EditText. If you input something and then rotate the device, the text will be kept.
It's done inside the specific View class.
But how is this state persisted through orientation changes?
In Activities, for example, you have onSaveInstanceState(Bundle bundle). That method is called by the system before a configuration change occurs. What you do, to persist the state of the Activity, is to store inside bundle the values you want to save. Then, when the Activity is created again, this bundle is passed back to you, for example in the onCreate method. This way you can restore your state.
But what about Views? They use a similar mechanism. If we want the details we have to look at the source code. Let's look at the source code of ProgressBar. It turns out that the View has a onSaveInstanceState, too (this line). You can see there, that the progress is being saved.
I hope this clarifies the mechanism.
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..
When orientation of screen changes, I have read many a times that in order to save data of edit text and text view or any of the radio button I have to use onSaveInstanceState() method.
But when I'm changing the screen orientation my data of edit text, text view and radio button are not getting erased.
So what is the main purpose of using onSaveInstanceState() method. Why do we have to use it if my data are preserved safely ?
Some Views/properties may be handled by default. You could go rooting through the docs to find out exactly which ones and how they are taken care of but...
I would recommend that you take manual control of these save/loads though to ensure that things are handled as you want them to be to avoid edge case bugs and also so you can then persist certain settings/properties/states if need be.
It really depends on the complexity and contents of your Activity/Fragment/Layout/View/Preference etc etc and if you even really need to remember the state things were a few moments ago.
onSaveInstanceState(Bundle outState)
This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state.
Do not confuse this method with activity lifecycle callbacks such as onPause, which is always called when an activity is being placed in the background or on its way to destruction, or onStop which is called before destruction. One example of when onPause and onStop is called and not this method is when a user navigates back from activity B to activity A: there is no need to call onSaveInstanceState on B because that particular instance will never be restored, so the system avoids calling it. An example when onPause is called and not onSaveInstanceState is when activity B is launched in front of activity A: the system may avoid calling onSaveInstanceState on activity A if it isn't killed during the lifetime of B since the state of the user interface of A will stay intact.
onRestoreInstanceState(Bundle savedInstanceState)
This method is called after onStart() when the activity is being re-initialized from a previously saved state.
Most implementations will simply use onCreate(Bundle) to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation. The default implementation of this method performs a restore of any view state that had previously been frozen by onSaveInstanceState(Bundle).
On the default handling of your views with this pair of methods:
The default implementation takes care of most of the UI per-instance state for you by calling View.onSaveInstanceState() on each view in the hierarchy that has an id, and by saving the id of the currently focused view (all of which is restored by the default implementation of onRestoreInstanceState). If you override this method to save additional information not captured by each individual view, you will likely want to call through to the default implementation, otherwise be prepared to save all of the state of each view yourself.
I have a viewpager holding many fragments. When the activity is sent to background and when resources are needed, the OS will kill the app or at least some fragments. When I return to the activity it crashes because the activity tries to attach new instances of all fragments it held before the clean-up and now some fields are null. This of course could be fixed by properly implementing state saving and restoring using Bundles, but I don't want to do that.
Instead I want to prevent restoring the fragments. Is there a way to tell the OS that once it has sent the GC in and destroyed fragments, it shouldn't bother recreating them at all? Once the cleaning-up happens, I want the activity to be simply recreated on return, as if a user launched it by taping the icon. Any chance of doing that?
The proposed solution here https://stackoverflow.com/a/15683289/552735 does not work. It causes exceptions
java.lang.IllegalStateException: Fragement no longer exists for key f2: index 3
I had a problem just like this. Here's how I fixed it:
#Override
protected void onSaveInstanceState(Bundle savedInstanceState)
{
savedInstanceState.clear();
}
Note that this method will ensure that absolutely no UI state information will be stored when the Activity is killed by the system - this has the effect of also not restoring any Views when onRestoreInstanceState() gets called with the same Bundle after onStart().
You can't disable the save/restore instance state actions. In case you don't want to actually implement this logic, you have to reload the form especially if your form heavily loaded with fragments.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
startActivity(new Intent(this,YOUR_ACTIVITY.class));
finish();
}
After going through an introductory Android programming book, I wanted to alter the example application in order to solidify my understanding of some topics that weren't really covered. In making the change, I made an error, but I'm curious why the error worked in some cases but not in others.
An activity within the application stores a series of questions in a Hashtable<Integer, Question>, where Question is a small class holding an int and two Strings. As originally written, the activity downloads the questions from a server on every onCreate(), so I wanted to implement onSaveInstanceState() to prevent some redundant downloads. onSaveInstanceState() saves the Hashtable into the Bundle using putSerializable().
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// mQuestions is a member variable of
// type Hashtable<Integer, Question>
if (mQuestions != null && mQuestions.size() > 0) {
outState.putSerializable(SAVED_QUESTIONS, mQuestions);
}
}
It worked perfectly for screen orientation changes even before I knew what a Parcelable was or how to implement one. I only knew there was a problem when I pressed the emulator's home key and the app silently, invisibly crashed with no LogCat output. The stack trace led me to look up Parcelable and make Question implement it.
My question isn't what I did wrong. The question is this: When the Question class did not implement Parcelable, why did the app crash only on pressing Home and not on a screen orientation change?
As far as I understand Android doesn't serialize an instance state when recreating an activity after configuration changes. That's why your code works. Persisted objects just don't need to be parcelable because they exist in memory only.
This looks like an optimization. Android knows that the process will not be terminated in this case and there's no need to save the instance state to a file. (In theory the process can be terminated during the configuration change and I don't really know how Android solves this problem).
But when the user presses Home key your app becomes background. And its process can be terminated in case of low memory. Android needs to save activity's state to a file in order to be able to restore your app and its activities in future. In this case the instance state is really serialized and saved to a persistent storage. And that's why your code doesn't work.
Process termination can occur at any moment so you can't rely on some implementation details. Just make instance state parcelable or serializable and you will not face this problem again.
Quoting Steve Moseley
Note that it is NOT safe to use onSaveInstanceState and onRestoreInstanceState, according to the documentation on Activity states in http://developer.android.com/reference/android/app/Activity.html.
The document states (in the 'Activity Lifecycle' section):
Note that it is important to save
persistent data in onPause() instead
of onSaveInstanceState(Bundle)
because the later is not part of the
lifecycle callbacks, so will not be
called in every situation as described
in its documentation.
In other words, put your save/restore code in onPause() and onResume() instead!
The app did not crash. It was simply shut down when the user clicked the Home key. That's why there was no output to LogCat.
Set a breakpoint in Activity.onDestroy() to confirm this. If I'm right onDestroy() will be called but onSaveInstanceState() will not, because onSaveInstanceState() is only called when the app is placed in the background state, not when it's shut down.
If you need to save the app state upon shutdown, place the code in onDestroy() and save it to something more persistent than a Bundle.
Barry