Does Fragments with setRetainInstance(true) survive process shutdowns? - android

Considering this scenario: If I created an activity and it moves to the background and this activity contains a Fragment which is set to setRetainInstance(true) then the Android OS might at some point still decide to shut down the activity's hosting process in order to free memory.
Then the Activity's state is saved via onSaveInstanceState(Bundle) where - as far as I understood - the related Bundle is written and to the file system to survive the process shut down. (thus the requirement of objects in the bundle being Serializable). Later, the applications state can be retrieved in a new process via onRestoreInstanceState(Bundle).
In contrast, my Fragment is allowed to contain variables which are not necessarily Serializable. Therefore, I figured, the Fragment cannot be stored on disk like the Bundle is. So what happens to my fragment when the process gets killed?
I was wondering about this reading the developer's guide (http://developer.android.com/guide/components/processes-and-threads.html):
A process holding an activity that's not currently visible to the user
(the activity's onStop() method has been called). These processes have
no direct impact on the user experience, and the system can kill them
at any time to reclaim memory for a foreground, visible, or service
process. Usually there are many background processes running, so they
are kept in an LRU (least recently used) list to ensure that the
process with the activity that was most recently seen by the user is
the last to be killed. If an activity implements its lifecycle methods
correctly, and saves its current state, killing its process will not
have a visible effect on the user experience, because when the user
navigates back to the activity, the activity restores all of its
visible state.
I understood the above killing such that the VM instance is shut down and the state of the process is written to the file system (here comes the Bundle into play). Later the bundles are read for resuming the process. Since the retaining of fragments is not concerned with life cycle methods and since I would not know how to retain e.g. a pointer to a network connection (you should of course never have such a pointer in a fragment anyhow), I was wondering if the fragments are still restored if the process is shut down in the meantime. I concluded that they surely needed to be recreated and that the life cycle methods are therefore to be preferred over setRetainInstance(true) whenever possible.
Does this assumption make any sense?

Sounds like you're mixing up two concepts here.
Saving state across Configuration Changes does not involve serialization. If you request setRetainInstance() for a Fragment then that means it will fully stay in memory and not be re-created only for configuration changes. A similar mechanism is available for Activity objects but they need to explicitly define an Object which is going to be saved. This works via Activity.onRetainNonConfigurationInstance(), not via onSaveInstanceStae().
The other mechanism involves serialization and possibly (maybe not always, not sure) file system I/O to be able to reproduce state information even if an Activity/Fragment is destroyed (which happens independently of its hosting Process, btw). This works via Activity.onSaveInstanceState() and Fragment.onSaveInstanceState().
Of course, you can use the second mechanism for the purpose of the first, thus slowing down the way your app deals with configuration changes. Depending on your internal state, the slowdown could me marginal of significant.
Regarding your questions.
"My Fragment in contrast, is allowed to contain variables which are not serializable." Well, the same is true for your Activity. It can contain non-serializable objects which can be saved across config changes as described above.
"the fragment cannot be stored to disk when a process is shut down and must be recreated when an activity was restored." No, both mechanisms are available for both object types.
Hope I could contribute to clarifying this a bit.
Edit after your first comment.
Regarding the comment:
"onRetainNonConfigurationInstance is deprecated": Yes. I mentioned it for demonstration purposes because of a specific wording in your question. Also, with Android 2 devices having a 46% market share as per today (official Google figures), this method will definitely stay around for a very long time, deprecated or not.
"My main concern is about what will happen to the fragment instance when my hosting process is killed and removed from the memory": Your fragment instance will be removed from memory and there's of course no way it is restored as-is with its complete internal state automatically. This is only done when you setRetainInstanceState in the case of config changes. (But note that this relates to the Instance, in other words, the full object.)
Regarding your edit:
Once more, yes, your Fragment's Bundle will be stored and restored to/from the Bundle regardless of setRetainInstanceState if you use Fragment.onSaveInstanceState() for this purpose, for everything that makes sense.
It is not true that "all of its visible state" will be saved as the text you refer to claims; for example, the visibility attribute will not be saved. Whether that's supposed to be a bug or a feature I don't know, but it's a fact. But this is only a side remark; UI elements will save most of their relevant state.
"the state of the process is written to the file system": No! The state of objects which are able to save their state to a Bundle and actually implement saving their state will be saved in a Bundle, this means that you must provide such information yourself if you want your Fragment to save some state information. Also, again: No, this does not only relate to killing the process but also to deleting Activity and Fragment objects which are not visible; like the last Activity shown -- the Process may well stay alive.
"bundles are read for resuming the process": No, the Bundle will be read to pass it to the re-construction of Activity and/or Fragment objects, there is nothing done automatically in this process (except library objects which save their state also restore their state), but Android does not "resume" the "Process" from these Bundles.
"Since the retaining of fragments is not concerned with life cycle methods": Again, I think you're mixing up the two concepts. The "retaining" of a Fragment is only performed upon configuration changes _IF_ you request it via setRetainInstance, but we're mostly talking about the re-creation of Fragment objects from a Bundle here, which does involve the life cycle methods as documented by Google.
"I would not know how to retain e.g. a pointer to a network connection": Again, this must be a statement based on your mix-up. Of course you can keep a reference to a network connection upon config change (as requested per setRetainInstance) because when that happens, everything is simply kept in memory. Also, even if your Fragment gets deleted (because it became invisible) and your process is still there (because it shows the next Activity), you can (and should) keep references to objects which are expensive to re-create, such as a network connection, in your Application object, which exists as long as your process lives (more or less). It is only when your whole app is killed by Android that you lose everything, but the serialization we're discussing happens much more often.
Your conclusion:
I concluded that they surely needed to be recreated and that the life cycle methods are therefore to be preferred over setRetainInstance(true) whenever possible. Does this assumption make any sense?
Unfortunately not, since you are mixing up completely independent concepts.
I'll give it a final try:
You will want to keep a network connection reference which you need throughout your app in your Application object because it would be a lousy user experience if you created it from scratch on a regular basis throughout your app.
Your Application object will only die if Android kills your app.
Your Activity and Fragment objects will be deleted from your app regularly when the user moves forward within your app.
When the user presses "back", Android will re-create Activity and Fragment objects from Bundles using lifecycle methods. Saving something in a Bundle makes sense if you have expensive computations to do to re-create the internal state. You can live without the Bundle mechanism because Android will always save the Intent so if you don't do anything then you'll start without saved state.
When a configuration change occurs, Android lets you optimize user experience by keeping objects in memory across the config change. Here, Activity life cycle methods get involvwed and it's up to your implementation to use the saved data effectively. For Fragments, this is where setRetainInstance' comes into play: YourFragment` will survive the config change in memory if you set it.

Related

Use-case of Android Lifecycle functions onRestoreInstanceState, onSaveInstanceState

I recently wrote a demo app, which just needed to display some data temporarily --- I meant for the data to disappear once the app was properly destroyed by the user. Toward this, I read the page
The Activity Lifecycle , which seems to recommend overriding the Activity methods
onRestoreInstanceState() and onSaveInstanceState().
It worked great! The data was preserved through screen rotations, and sending the app to the background.
But then I would leave the app running and walk away, and when I looked at it again, the data was gone.
I spent hours trying to de-bug my app, and re-reading that page.
Finally, I read
Saving UI States. It refers to overriding these methods as "ViewModel" approach, and explicitly states that data saved this way does not survive system-initiated process death --- which explains my observation.
My main question is: what on earth is the practical application of this "ViewModel" persistence approach? What is the use-case for a persistence mechanism that randomly disposes of data when the user isn't looking?
(I guess this is an old API left over from the times when apps didn't run in the background. But I don't see that reflected in the documentation.)
A second question is, reading the first page, how on earth was I supposed to understand this unfortunate behavior? Did I miss something? (It is very long.)
what on earth is the practical application of this "ViewModel" persistence approach?
It is not a persistence approach. A ViewModel is a way of holding onto state across configuration changes. Using a SavedStateHandle with ViewModel — which maps to onSaveInstanceState() and onRestoreInstanceState() — is also useful for a fairly narrow use case:
User is in your app and does something that you don't want to save to disk or the server (e.g., the user didn't click "Save" yet)
User turns off their phone screen or switches to another app (e.g., via system HOME navigation or the overview screen)
Time passes
Android terminates your app process to free up system RAM for other apps
Within ~30 minutes of having left your app, the user returns to your app
At this point, Android wants to pretend that your app had been around all along, despite the fact that your process had been terminated. So, Android will not only start up a fresh process for you, but it will recreate the last activity the user had been on... and you get your saved instance state back as part of this.
However, this is not a persistence approach. For data you want to have survive long term, you need to save it to disk (SQLite, SharedPreferences, JSON file, etc.) or to some server. Notably, if the user leaves your app for an extended period (over ~30 minutes), Android will not attempt to restore the instance state, and your app will be started normally.
You need to use a SavedStateHandle with a ViewModel to get data persistence when the system terminates your app in the background. Otherwise it's more about sharing data between components, and surviving Activity destruction e.g. on screen rotation without having to do a lot of boilerplate handling.
Just like with onSaveInstanceState, this is purely about persisting data when the system kills your running app to recover memory, so that when the user switches to the "running" app again, it can be recreated and restored exactly as it was. It doesn't save any data when the app is intentionally stopped, e.g. calling finish(), the user backing out or swiping it away etc.
This stuff should always just work - if you were seeing your data "go missing" and the app wasn't crashing in the background, it's possible your save/restore logic wasn't working. A good way to test that is going to Developer Options on your device (if you don't know how to get that do a search, it depends on your device) and enable Don't keep Activities. That will destroy them as soon as they go to the background and it should help you test how that's handled. The fact you were handling rotations ok suggests it was a background crash though, but that depends on how you were handling configuration changes

Architecture components ViewModel vs. savedInstanceState bundle

Trying to understand what is the difference with using the ViewModel to keep some of the state of the activity or fragment, and saving them with the savedInstanceState bundle.
Got a impression that the ViewModel instance is kept alive when the activity/fragment is destroyed by os in the case like configuration change so that when os recreate the activity/fragment could get the data from the ViewModel instance which is still valid.
Does it apply to minimize the app and re-open it?
Did some test, seems minimize the app and re-open the app, the os will recreate the activity/fragment with the stavedInstanceState bundle in the onCreate() not null (whatever is saved when the onSaveInstanceStae() is called). But the ViewModel has been cleared so a new instance is created without previous ones data.
Does it it mean although is in this case the os can retrieve the saved instance state and pass to activity/fragment's onCreate(), but the ViewModel has to be a new instance without previous instance's data, or the viewModel needs do to some extra step inorder to store/restore the data cross the instances?
If someone is still looking to understand difference between onSavedState vs ViewModel here is the detailed explanation:
onSavedInstanceState : Primary usage of onSavedInstance was not to handle orientation change but to provide a mechanism to retrieve data if app/activity is destroyed by Android System. Example case when app is in background and Android system decide to kill this as it needs memory for some other high priority process then in this case before activity is destroyed onSavedInstanceState will be called.
onSavedInstanceState only stores the Parcelable data, that provides hint to restore the state for the user when activity restarts. It saves data in System server that is a separate process.
onSavedInstanceState has data limit. Only small amount of Parcelable data can be saved.
While for ViewModel
ViewModel object is part of Applications process memory and hence it is able to survive configuration changes. Once a process dies, ViewModel goes away and all the saved state will be lost. Hence when activity restarts, ViewModel has nothing in it.
It works as a cache for heavy objects.
There are no restrictions in ViewModel.
Important: Always remember ViewModel and SavedState work together. They are not replacement or alternative to each other.
A good explanation (and a solution to your problem) can be found in this blogpost.
TLDR: the viewmodel is hosted inside a persisted fragment, which gets recreated together with the hosting activity.
From: Kristin Marsicano's Book “Android Programming: The Big Nerd Ranch Guide, 4th Edition.” :
ViewModel vs Saved Instance State
While saved instance state stores an activity record across process death, it also stores an activity record across a configuration change. When you first launch the activity, the saved instance state bundle is null. When you rotate the device, the OS calls onSaveInstanceState(Bundle) on your activity. The OS then passes the data you stashed in the bundle to onCreate(Bundle?).
ViewModel really shines when you use it to orchestrate dynamic data for the activity
ViewModel makes continuing a download operation across a configuration change simple. It also offers an easy way to keep data that was expensive to load in memory across a configuration change. And, as you have seen, ViewModel gets cleaned up automatically once the user finishes the activity.
ViewModel does not shine in the process death scenario since it gets wiped away from memory along with the process and everything in it. This is where saved instance state takes center stage. But saved instance state has its own limitations. Since saved instance state is serialized to disk, you should avoid stashing any large or complex objects.
lifecycle-viewmodel-savedstate is a new library that was just released to allow ViewModels to save their state across process death. This should alleviate some of the difficulties of using ViewModels alongside saved instance state from your activities.
Use saved instance state to store the minimal amount of information necessary to re-create the UI state (for example, the current question index). Use ViewModel to cache the rich set of data needed to populate the UI in memory across configuration changes for quick and easy access.
When the activity is re-created after process death, use the saved instance state information to set up the ViewModel as if the ViewModel and activity were never destroyed.
As of this writing, there is no easy way to determine whether an activity is being re-created after process death versus a configuration change. Why does this matter? A ViewModel stays in memory during a configuration change. So if you use the saved instance state data to update the ViewModel after the configuration change, you are making your app do unnecessary work. If the work causes the user to wait or uses their resources (like battery) unnecessarily, this redundant work is problematic.
One way to fix this problem is to make your ViewModel a little smarter. When setting a ViewModel value might result in more work, first check whether the data is fresh before doing the work to pull in and update the rest of the data.

Saving global state in an Android app

What's the best way to save any changes to global state, that would be lost if the process was killed due to low memory etc?
For activities we have onSaveInstanceState() which allows state to be saved when needed.
I would like something similar for global state. Our Application class holds a references to many objects that can be simply reloaded in onCreate() when the app next starts. But we also change the state of some of those objects, and require these changes to be maintained through the app being killed due to low memory etc.
We could of course persist every time changes are made to the objects in memory. But this would be very wasteful.
My current thought is to use onActivitySaveInstanceState() and keep a dirty flag to know only to call it once. But this adds complexity and probably isn't what this method was intended for.
This cannot be a particularly uncommon requirement, but most of the resources on the net focus on just Activity lifecycle. I'd be pleased to learn of a better approach.
There is no concept of Global State and I actually think that you don't need it at all. An app consists of several independent parts which have their own "state". Thus each part is responsible for its own state. For instance, a fragment which shows particular data knows where to get this data and what to do if there is no such data. An activity which shows user settings knows where to get user settings, how to update it and so on. Both of these parts know what to do when lifecycle methods are calling.
You can always store values in your Application class. On a high level, this is very simple, you create a custom MyCustomApplication, that extends the Android Application class. The Applicaiton class only lives, while the App is in scope (including when it's in the background, which is somewhat unpredictable in Android).
Then you would can access this using the getContext().getApplication()
The implementation details are more complex, and you really should extend the Applicaiton into a Singleton, as described in this SO post: Singletons vs. Application Context in Android?

Activity Recreation on Android

I am reading the official Android tutorial on Managing Activity Lifecycle. Please have a look at the following points as I quote.
(Normal app behavior)
"When your activity is destroyed because the user presses Back or the activity finishes itself, the system's concept of that Activity instance is gone forever because the behavior indicates the activity is no longer needed."
(System induced destruction)
"If the system destroys the activity due to system constraints (rather than normal app behavior), then althought the actual Activity instance is gone, the system remembers that it existed such that if the user navigates back to it, the system creates a new instance of the activity using a set of saved data that describes the state of the activity when it was destroyed."
"The saved data that the system uses to restore the previous state is a collection of key value pairs stored in a Bundle object. By default the saved information is about the activity's view hierarchy, such as the text entered in a widget, the scroll position, etc. To save additional state information for your activity, such as member variables, you must implement onSaveInstanceState() and add key-value pairs to the Bundle object."
Please see if I can sum up correctly from the above:
The system is resource conscious and can destroy an activity to recover memory. If the system does that then it leaves an option of recovering the destroyed activity to its previous state if needed later.
State = view-state (default, done automatic) + member-variable-state (manual, up to the programmer).
My question is, if the system is destroying an activity in order to free some memory, then doesn't it defeat the purpose if it allows us to keep a "copy" of the activity so that the activity can be restored
to the point exactly as it was prior to destruction?
No, it does not defeat the purpose:
A Bundle is optimized for being serializable, so the Android framework could just write it to disk.
The data you need to represent your state is much smaller than the amount of RAM you need for a running copy of your app. For example, if you show an image to the user, you'd probably just have to save the image's location, not all of its data. Also, every Android app is a whole new Dalvik VM, so if all the activities are from different applications, killing an activity means one Dalvik VM less – and every Dalvik VM probably needs a relatively big amount of RAM.

OnCreate onResume and onRestart issue

I have a single activity app that uses a PagerAdapter. In the OnCreate event I trigger the inflation of all 7 of the pages in the PagerAdapter. The various pages have standard widgets, one page has a google Map. It all works well when testing in the AVD.
But what if my app is paused or stopped and then restarted or resumed? Does the inflation of all my pages in the pageradapter get deleted? Do I have to reinflate all the pages again?
More generally...
I've read a lot of articles about what happens with my app gets stopped or paused and what I should do upon restart or resume but I haven't seen a precise accounting of what needs to be rebuilt and what does not need to be rebuilt. I read that "this is a great place to refresh the UI" but I'm not sure exactly what that means.
Begging questions might be...
is my internal state restored i.e. values of my variables, sqlLite db, file contents.
are the values in my widgets restored i.e. characters in a textbox.
How can I prevent corruption if the pause or stop can happen in the middle of a for loop or a code block?
Thanks,
Gary
But what if my app is paused or stopped and then restarted or resumed?
Does the inflation of all my pages in the pageradapter get deleted? Do
I have to reinflate all the pages again?
If by app you mean Activity, when it is paused this just means it is not the focused Activity right now. All the state is still there. This basically means that you cannot accept any foreground events until onResume(). If the OS decides to call, onSaveInstanceState(), you can can actually store things like values of instance variables or just flags for a new instance to read. You can't store complex things here though. So things like Thread or Cursor instances will not be appropriate. Basically anything that is not "data."
In the case where the activity is destroyed or even the process killed, then yes you will need to rebind and reinflate everything. However the beauty of this, in most cases you don't have to do anything special. Only in the cases that you may have written to a bundle in onSaveInstanceState() will you have to do some extra work.
is my internal state restored i.e. values of my variables, sqlLite db, file contents.
If the transition was only between onPause and onResume then yes. Everything should be fine. If there was a destroy or process kill, the activity will restart with onCreate() and have to reset all the state based on the Bundle savedInstanceState. I will address sqlite and files later down.
are the values in my widgets restored i.e. characters in a textbox.
Usually if they have saveEnabled(true) (most do) and for TextView there is a freezesText property that will make them remember the last text set on them. However, most of the time if you save your state correctly during the onSaveInstanceState() call, it's probable that you are storing the state not just for UI but other means as well. In which case you might as well sync them when you go through the next onCreate()
How can I prevent corruption if the pause or stop can happen in the
middle of a for loop or a code block?
So onPause() and onStop() occur on the main UI thread. If you are currently running code on the UI thread, it must complete before these other callbacks can occur. If you are running on some other thread, then yes you have to try to make outputs of task complete on the UI thread. It simplifies a lot of this.
Kills on the other hand, can happen. This usually affects things outside your program's memory though when we talk about corruption. Like if you had a file handle open or a sqlite cursor open, or a network socket open. With that, you sort of have to check the state before you use it.
i.e
Make sure directories really exist before you read or write to them
Make sure the contents of files were properly written to previously (simple expected check-sums usually work here or version metadata).
Use transactions when using sqlite to make sure you only write data to your table(s) in complete atomic chunks so that you don't have tables with rows referencing state in other tables that is not there.
Network connections will be reset, and things like your network protocol of choice should take care of sanity checking for you, along with the application being connected with.
hopefully this helps, while it's not fully complete, it's a good starting approach.
In status Pause and Stop all your objects are preserved and you don't need to worry about interruption in a loop, unless it takes more than 5 seconds to complete which may generate ANR's.
However, is good practice when entering these status to realease the objects that are not needed or can be easly recreated, specially if they are using big amount of memory, and recreate them when reentring the status Resume or Restart.
When your appliaction goes in Destroy status, then all your objects will be lost.

Categories

Resources