I don't know if this behavior is normal because I suppose a call to finish() would stop the activity from being re-created.
Here is an excerpt of the activity class:
public class MyActivity extends FragmentActivity {
private RetainFragment retainFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
......
retainFragment = getSupportFragmentManager().findFragmentByTag("RetainFragment");
if(retainFragment == null) {
retainFragment = new RetainFragment();
getSupportFragmentManager().beginTransaction().add(retainFragment, "RetainFragment").commitAllowingStateLoss();
}
if(retainFragment.isFinish) {
Log.v("MyActivity", "isFinish == true");
this.finish();
}
}
// a on-click event handler for a finish button
public void onFinishClicked(View view) {
retainFragment.isFinish = true;
this.finish();
}
private class RetainFragment extends Fragment {
private boolean isFinish = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setRetainInstance(true);
}
}
}
The activity is persistent(I store the other persistent variables in the RetainFragment too but I didn't show them) and is closed only after the user has clicked the finish button, at which point onFinishClicked(View) would be called, and the activity should be finished by the system. I don't anticipate it to be recreated by the system after a coincident screen rotation. This happens rarely and I handle it by calling finish() again within the onCreate() method. However, it looks pretty ugly here because a finished activity is supposed to be dead forever and now I have to explicitly handle it. :(
Is there any misunderstanding of the activity lifecycle or the retain fragment on my part?
If you work on Android, whenever you hear yourself saying "this Activity will be closed only after X". Don't. You should never rely on an Activity having a highly-controlled lifecycle. Your Activity should be designed in such a way that no matter when it is destroyed and recreated, it just works.
To retain information across rotations and so on, add code to onSaveInstanceBundle and then check for it on your onCreate and pull out the saved values.
This is a standard Android behavior. Take a look at this paragraph of Android documentation.
Unless you specify otherwise, a configuration change (such as a change in screen orientation, language, input devices, etc) will cause your current activity to be destroyed, going through the normal activity lifecycle process of onPause(), onStop(), and onDestroy() as appropriate. If the activity had been in the foreground or visible to the user, once onDestroy() is called in that instance then a new instance of the activity will be created, with whatever savedInstanceState the previous instance had generated from onSaveInstanceState(Bundle).
To avoid this, you can set android:configChanges attribute in its manifest. For any types of configuration changes you say that you handle there, you will receive a call to your current activity's onConfigurationChanged(Configuration) method instead of being restarted. If a configuration change involves any that you do not handle, however, the activity will still be restarted and onConfigurationChanged(Configuration) will not be called.
I was encountering the same exact problem. That is, I had an Activity A that would start an Activity B using startActivityForResult. B's orientation is locked by the manifest. A's orientation is not locked.
When I rotated my device so the orientation was different, Activity B would not be recreated. This is expected and normal. However, once I performed a UI action that would finish() Activity B, instead of seeing Activity A being reconstructed and shown on the screen, I was shown Activity B again. Additionally, Activity's B's onActivityResult was being called instead of A's onActivityResult.
I figured out what was happening. I was using the same Intent object used to launch A to also launch B. After the orientation change and after B is finished, the Android system detects that Activity A needs to be rebuilt, since A was last built when the orientation was different. The system will reconstruct A using the same Intent object initially used to build A. However, the Intent object was modified to launch B instead, so the system will reconstruct Activity B instead of Activity A.
My code followed logic similar to below:
public class A extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...omitted code for clarity...
Intent i = getIntent();
i.setClass(this, B.class);
startActivityForResult(i, 0);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//...omitted code for clarity...
}
}
And my Activity B looked like:
public class B extends Activity {
//...omitted code for clarity....
public void myFinishingClickHandler(View clickedView) {
//...omitted code for clarity...
finish();
}
}
Quick walkthrough of the scenario:
Device is in portrait mode. User launches Activity A. System will construct Activity A with an Intent I, whose target is set to A.
Activity A modifies its Intent I by changing the target to B.
Activity A starts an activity with Intent I. The system will construct Activity B since I is now pointing to B.
B is now running. User changes the device orientation to landscape.
User clicks button whose click listener calls myFinishingClickHandler. The method calls finish() on Activity B.
Activity B finishes. The system begins the process of restoring Activity A.
The system notes that Activity A was constructed when the device was in portrait orientation, but now the device is in landscape mode. The system will try recreating Activity A instead. The existing Activity A is sent through its destruction flow (calling onDestroy).
The system will reconstruct Activity A using the saved Intent I. However, I's target is still Activity B. The system instead builds Activity B instead of Activity A.
Solution: Don't share Intents for launching activities. Use the Intent copy constructor.
Also, lock your activities' orientations to worry less about orientation changes (other configuration changes will cause your activities to be reconstructed, e.g. changing languages).
Related
I have application and when I navigate back using Intent and startActivity(), views are null, onCreate() is called and activities are re-initialized. Why is that and how to bypass it?
I navigate back to activity like that:
#Override
public void onBackPressed() {
if (this.getClass() == XXX.class) {
Intent i = new Intent(this, YYY.class);
startActivity(i); //<-- activity restarts
return;
}
}
super.onBackPressed();
}
I use ActionbarSherlock, so I have activity with ActionBar initialization and every single activity just extends it. The way I navigate back to activity is described in this activity.
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_screen);
initUIComponents();
setListeners();
resetProgress();
}
and initUI() initializes UI.
EDIT
What I mean, how can I go back to previously created activity (not the one that is called via onBackPressed) and not recreate it? I use startActivity(), but apparently it recreates the whole thing
If you want that when you press back, you want to show the previous screen, then you don't have to do it in your code. Android Runtime internally maintains the stack, and will take care of showing the last-shown-activity when you press back. No need to handle it via onBackPressed()
However, if you want something other than this default action, that is when you should use onBackPressed(). Else, just let Android handle it.
So, in your application, if Activity 1 calls Activity 2, and user presses back, then the default action would be to show Activity 1 again. Don't override the onBackPressed() method
Edit:
For a custom flow of activities, you'll have to build the logic yourself. You need to override onRestart() in Activity 1, and onStop() in Activity 3. That way, onCreate won't be called again. By your logic, I mean, flags to keep track of which activity you're in, checking those flags, and calling the desired activity from there.
Edit 2:
This previous SO question, answers what you need:
Android Activity management , which suggests setting the flag FLAG_ACTIVITY_REORDER_TO_FRONT on the intent, and then calling startActivity()
Check out Android activity stack management using Intent flags for other stack reordering options: Stack management
I have an activity A which shows different screens. I chose to show multiple views in the same activity, by using setContentView.
When you launch the app, Activity A shows the default screen. You then choose an option and the activity shows you another set of options by setting setContentView to another view. This new view now takes you to a new activity(Activity B) when you click it, and Activity A calls finish()
If I now end Activity B, by calling finish() on it, I am being taken back to Activity A, and it calls it's onCreate(). I can't figure out what's wrong here. Why would an activity recreate itself when Activity B never called an intent for Activity A?
view.findViewById(R.id.quit).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
The issue is most likely because the purpose of onCreate() is to create the Activity's state as it was when it left. You can't under any circumstances assume that an Activity will maintain its state once the user leaves it. Views are memory intensive, and the system is going to dump it as soon as it needs more memory for the current top Activity. If this happens then the system must recreate the Activity to its original state before the system dumped it.
You can save states via the onSaveInstanceState(Bundle) method. The bundle that's passed in to onCreate is the same bundle you set in onSaveInstanceState(), so all parameters will be there. Then you can rebuild just as it was.
Sorry for my incomprehension, but I am new in the android development.
I have an application with activity A and activity B in it, and I go from activity A to activity B. When I left activity A, the onSaveInstanceState method was called, but when I went back to activity A (from activity B in the same application), the bundle in the onCreate method was null.
What can I do, to save the activity A's previous state? I only want to store the data for the application lifetime.
Can someone help me with this?
Here is my code for Activity A:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null)
{
Log.v("Main", savedInstanceState.getString("test"));
}
else
{
Log.v("Main", "old instance");
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
Log.v("Main", "save instance");
savedInstanceState.putString("test", "my test string");
super.onSaveInstanceState(savedInstanceState);
}
public void buttonClick(View view)
{
Intent intent = new Intent(this, Activity2.class);
startActivity(intent);
}
Here is my code for Activity B, when I press a button to go back to activity A:
public void onBack(View view)
{
NavUtils.navigateUpFromSameTask(this);
}
To answer your question, have a look at the android doc: https://developer.android.com/reference/android/app/Activity.html#onRestoreInstanceState(android.os.Bundle)
It says that onRestoreInstanceState is called after onStart() method in the activity lifecycle.
Saving and restoring state is meant to save the current temporary data that is obsolete when user exits the application.
When you minimize or leave the Activity by opening next one it might be killed by the system due to lack of resources and restarted with savedInstanceState when you get back to it. So use onSaveInstanceState() only for saving minimize-restore session data or data that should be preserved on rotation.
So if you start a new Activity in front and get back to the previous one (what you are trying to do), the Activity A might not be killed (just stopped) and restarted without going being destroyed. You can force killing it and restoring by checking Don't keep activities in developer options menu.
If you call finish() or remove the Activity from recent task list the savedInstanceState will not be passed to onCreate() since the task was cleared.
If the value must be persistent consider using SharedPreferences.
This happens because you are navigating the hard way. If you used the phone's default back button navigation, your onCreate would get the bundle passed in.
To circumvent this issue, I suggest you save your state to shared preferences as well as a back up. When the bundle is null, restore it from the shared preferences.
reference
onSaveInstanceState
... onPause()-> onSaveInstanceState() -> onStop() -> onDestory()
onRestoreInstanceState
onCreate()-> onStart()-> onRestoreInstanceState()-> onPostCreate(Bundle) ...
Or You can use LiveData. Save the states in it and observe.If the device rotates it'll update the views accordingly.
After onStart() which is after onCreate()
I used in this case a flag and SharedPreferences. This should work, and when you change the screen orientation.
Suppose I have two activities, activity1 and activity2.
I want to navigate from activity1 to activity2, get some info from activity2 and insert back it to activity1 and also I don't want to lose activity1 previous state as I left.
how can I save its state ?
what you are describing is the perfect classic reason to use the Activity.startActivityForResult() method.
this what google wrote in this method documentation:
Launch an activity for which you would like a result when it finished. When this activity exits, your onActivityResult() method will be called with the given requestCode
so what you should do is: from your activity1 start activity for result, and from activity2 use the setResult(int resultCode, Intent data) method with the data you want your activity1 to get back, and call finish() (it will get back to onActivityResult() in the same state activity1 was before..).
Override onSaveInstanceState(Bundle) in activity1 to save whatever data you want then override onRestoreInstanceState(Bundle) in the same activity to get the values back. Using Bundle, you can store pretty much any data that you want. I'd recommend something like this:
public class MainActivity extends Activity {
...
public static final String DATA1_KEY = "data1";
private boolean value1;
...
#Override
protected void onSaveInstanceState (Bundle outState) {
outState.putBoolean(DATA1_KEY, value1);
}
#Override
protected void onRestoreInstanceState (Bundle savedInstanceState) {
value1 = savedInstanceState.getBoolean(DATA1_KEY);
}
}
You should override onSaveInstanceState(Bundle savedInstanceState)
Please check this answer for example code
Or use SharedPreferences. Check this code
If you want to keep your data alive only at runtime, the consider using static members. Then you can access and manipulate these members from any activites. for example:
public class FirstActivity extends Activity
{
public static String data = null;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(...);
data = "This is a test!";
}
}
From your second activity you can access these static variables like
public class SecondActivity extends Activity
{
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(...);
if(FirstActivity.data!=null)
{
//You can use it:
System.out.println(FirstActivity.data);
}
}
}
Of course you can add getter/setter functions to make it safer and more elegant.
If you want to store them for a longer time, the please consider using:
SharedPreferences
SQLite
or store your data in an online database.
The user performs an action in your app that starts a new activity. The current activity is stopped when the second activity is created. If the user then presses the Back button, the first activity is restarted.
When your activity is stopped, the Activity object is kept resident in memory and is recalled when the activity resumes. You don’t need to re-initialize components that were created during any of the callback methods leading up to the Resumed state. The system also keeps track of the current state for each View in the layout, so if the user entered text into an EditText widget, that content is retained so you don't need to save and restore it.
Note: Even if the system destroys your activity while it's stopped, it still retains the state of the View objects (such as text in an EditText) in a Bundle (a blob of key-value pairs) and restores them if the user navigates back to the same instance of the activity.
CommonsWare here says:
When user "press the BACK button", then the Bundle from
onSaveInstanceState() (if any) is discarded, as the user has indicated
they are finished with the activity. The onSaveInstanceState() Bundle
is used in cases where the user has not said they are finished with
the activity (e.g., they accepted an incoming phone call) yet Android
elects to destroy the activity to free up RAM.
And documentation says:
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 for non View objects in onPause() and onResume() instead of onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle).Finally I guess that you don't need to save any state if you only have View objects and if you have any other states you can use preferences,file or sqlite to save them in onPause() and retreive them in onResume().
You can see more details in these pages:
Stopping and Restarting an Activity
Saving Activity state in Android
Android wont save the current state of an activity
From this page of Android SDK
The default implementation takes care of most of the UI per-instance state for you by calling 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(Bundle)).
So is there a mechanism that automatically saves the Activity state without saving value from each element in the activity? I am confused about the above statement.
For an example, Activity A invoked Activity B. In Activity B, I have checboxes, Radio Buttons, etc. User select their choices and click Back button. I am showing the Activity At this point, I want to save the user selection. When user again comes back from Activity A to B, by clicking a button, I would like to see all selections persisted. One way I can think of is, setting the Intent Flag to bring the Activity to fore. But not a recommended method, I think.
So is there a default implementation to save the state, per the above text from SDK? Or may be I am interpreting it wrong?
onSaveInstanceState() and onRestoreInstanceState() are only explicitly called by Android when the Activity needs to be recreated, generally after a configuration change (ex. changing orientation). This doesn't cover the case when you have invoked a new instance of the Activity. When you press the back button, Activity B is destroyed, and you are creating a new instance of it the next time you start that Activity.
If you want to manually save the instance of an Activity, invoke Activity B via startActivityForResult(). Then, in Activity B, override the onDestroy() method, and call these lines of code:
#Override
protected void onDestroy() {
Bundle savedState = new Bundle();
onSaveInstanceState(savedState);
Intent data = new Intent();
data.putExtra("savedState", savedState);
setResult(RESULT_OK, data);
super.onDestroy();
}
In Activity A, override on onActivityResult and save the data:
Bundle activityBData;
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_OK) {
activityBData = data.getBundleExtra("saved_state");
}
}
Then, when starting Activity B again, call it like so:
Intent intent = new Intent(this, ActivityB.class);
if (activityBData != null) {
intent.putExtra("saved_state", activityBData);
}
startActivityForResult(intent, 0);
And lastly, in Activity B's onCreate method, restore the state:
if (savedInstanceState == null) {
Intent intent = getIntent();
Bundle savedState = intent.getBundleExtra("saved_state");
onRestoreInstanceState(savedState);
}
As per the documentation it will bring your activity forward when you start again ,if it is not killed .but to get all other views state back you need to store them in bundle in onSaveInstanceState() and set it again in onRestoreInstanceState().The default implementation works only for your activity not for your subviews in your activity
when an app loses focus to another app onSaveInstanceState() is called but when you navigate back to your app onRestoreInstanceState() may not be called. i.e. if your activity was NOT killed during the period when other activity was in front onRestoreInstanceState() will NOT be called because your activity is pretty much "alive".
All in all, as stated in the documentation for onRestoreInstanceState():
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).
For ex: From B you call startActivity(A). Then from A you call finish() to get back to B. In that case Your first activity, B will not have been destroyed, and neither onCreate() nor onRestoreInstanceState() will be called. These methods are only called when needed, that is when an activity has been destroyed and needs to be recreated by the system.
Use the SharedPreferences mechanism. Check out the documentation:
https://developer.android.com/reference/android/content/SharedPreferences
An example of implementation:
https://www.tutorialspoint.com/android/android_shared_preferences.htm
Also you can make use of PreferenceFragment to make this task easy.