Static variables life cycle in android - android

What is the Life cycle of static variables in Android?
I found that sometimes some static variables bound to activities happened to be uninitialized even though they've previously been initialized! I thought that when a static variable is initialized it stays so for the entire life of the application, but this doesn't seem to be the case.

Android may kill you app if it runs out of memory and all it's activities are in background (not visible). But you activity remains in history and user may activate it again. In this case system will recreate your app and restore it's state.
To deal with this case you should store state of your app and activities in persistant memory (files, database, etc).
More info you may find here http://developer.android.com/reference/android/app/Activity.html#SavingPersistentState

It depends were you define it, for example :
If the process is killed then all static variables will be reinitialized to their default values.
So whatever value you have set in Activity A will not persist. The same goes when an activity is destroyed.

Related

Can I trust my global variables to always be preserved during screen rotation? (Android / Kotlin)

Is it bad practice to use Global variables instead of bundles to preserve state between screen rotations?
I really like just using global variables to keep state. But since everything suggests using bundles, should I do that? I don't know what pitfalls I could be thinking of.
Here is a snippet of code that demonstrates global variables dont change.
I have to check that the LEVEL_DEFS list is 0 before adding to it, otherwise every screen rotation adds duplicate levels (where I originally assumed I would have to re-add the levels after rotation)
`
val LEVEL_FILES = arrayOf("levels/lv1.txt", "levels/lv2.txt", "levels/lv3.txt")
val LEVEL_DEFS = mutableListOf<LevelDefinition>()
fun getAllLevels(context: Context): MutableList<Level> {
if (LEVEL_DEFS.size == 0) {
for (filename in LEVEL_FILES) {
LEVEL_DEFS.add(LevelDefinition(getTextFileString(context, filename)))
}
}
}
`
When I google screen rotation, all the results suggest that you have to use a bundle to save state.
I also asked chat gpt-3, which insisted that I must use a bundle to save the state during rotation.
Yet, it is clear to me that global variables do not change during rotation. I have used many simulated and real devices and they all behave the same way.
Your global variables will live as long as their scope is in memory, so it depends where you're storing them - if they're at the top level (e.g. defined in the top level of a file, or in an Application component) that state will exist until the process is destroyed.
If your state is stored in an Activity, then that Activity will be destroyed on rotation, so typically you'd use the Bundle passed into onSaveInstanceState to store any data you need, and then restore it when appropriate.
If your app is in the background, its process can be destroyed at any time by the system - in that case, you'd lose your global variable state. For an Activity, those save state lifecycle methods would be called, so you'd still store in the Bundle and receive that Bundle when the app is opened again. The process will have been destroyed, but if you're set up to store and restore that state, you'll handle it just like with a rotation. (This is a different situation to the user actually closing the app, which considers the next time it's opened to be a "fresh start" and doesn't provide any stored state.)
You can also use View Models to store state, which survive rotation but require using SavedStateHandle to behave like the Bundle and survive process death (but again, not actual restarts). More info about your options for storing this transient state here.
So what this means is it depends exactly what you're storing, how long it needs to stick around, and if it's allowed to be destroyed when the app is in the background. That last one is usually a "no" which makes global variables a bad fit in most cases.
You'll need some way to persist that data (e.g. SharedPreferences) and at that point you may as well use that as your state, rather than storing it there and in a variable. Up to you though! Persisting data is also how you'd handle state surviving fresh start app restarts too, e.g. storing a high score that shouldn't disappear when the user closes the app.
(Also I'm not sure what asking a chatbot for advice is worth, no matter how much it "insists"! But that's a whole other topic)
Global variables persist as long as your application is running - however when it is in the background you have no guarantee that the Android OS won't stop and re-start your application (which would lose any global or static variables). Once your application goes to the background, you can no longer be certain they will stick around.
What you have posted looks like it would be fine though if you only access it through the getAllLevels method (since it re-creates it when needed). However, if you are adding levels somewhere else, those could be lost.
If you want to be able to add levels, one possible solution would be to load the data from SharedPreferences inside getAllLevels when the list is empty - and update the SharedPreferences any time levels are added.

Life of public static variable in Android

Let's assume that I have an Android App with two Activities (Activity1 and Activity2). In Activity1 I declare a public static Boolean foo = true.
When I finish() Activity1 and move to Activity2, I am able to see that "foo" variable has value true
But when the System has low memory (e.g. because there are many apps running on the device) then, when I am on Activity2 I see that the value of "foo" variable is null.
How is this explained?
It's important to note that the life of a static variable is tied to the process not the activity. Even if your activity is destroyed, the static variable will still be alive (which is why you see it's value set to true). It's only when the process is destroyed that the static variable will be freed properly.
This is also one of the reasons you shouldn't use static variables to hold references to activities, contexts, or views. Huge memory leaks waiting to happen.
For your particular scenario, this means that:
Act1 created & set the variable; You've moved from Act1 to Act2
The processes is killed in the BG
When the system attempts to restore you, you end up back at Act2
However, since the initialization of that variable happened in Act1, (which hasn't been initialized) the variable doesn't get set.
If the process is killed then all static variables will be reinitialized to their default values.
So whatever value you have set in Activity1 will not persist

Saving the transient state DIY + Has the activity been called or recreated?

In my application, an activity A calls the activity B (via an explicit intent). Lifecycle, B may get killed and recreated etc.
When B is called (from A) it initializes some things. But when recreated it needs to just pick up from where it left. It will finish() some time, then I'm back to A, which might later call B again...
B needs to save a very small amount of data, usually just a few int values. (The rest that I need to restore it is in getIntent() which seems to be still there after the activity has been killed and recreated.) I've heard that onSaveInstanceState and onRestoreInstanceState are expensive and that they are not guaranteed. Plus, I don't need to save the state of views. (For that reason, should I override these two methods with blank ones, preventing the parent ones to get called?)
What's the efficient way to store just a few int values? Should I store the values as static fields of the activity B itself, or of the application itself, or of another class written just for that?
Also, how does the activity B know whether it's been recreated rather than called?
I've heard that onSaveInstanceState and onRestoreInstanceState are expensive and that they are not guaranteed.
They are not particularly expensive. And, for your scenario, they will be used most of the time. The exception would be if your task is not being restarted, such as:
the user got rid of your task by swiping it off the recent-tasks list
at least pre-Android 5.0, your app has not be run in ages and falls off the end of the recent-tasks list
the user force-stops you from Settings
But in those cases you will not be restarting at B, since I assume B is not your launcher activity.
For that reason, should I override these two methods with blank ones, preventing the parent ones to get called?
Not unless you are experiencing actual problems, not just rumors of hints of potential problems.
What's the efficient way to store just a few int values?
For your case, probably use the saved instance state Bundle.
If you cannot afford to lose those values even if your task goes away (and you will not be restarting at anyway), store them in a file, database, or SharedPreferences.
Should I store the values as static fields of the activity B itself, or of the application itself, or of another class written just for that?
No, because none of those will work. You specifically state that your concern is "Lifecycle, B may get killed and recreated etc.". Part of that lifecycle is that your process may be terminated, and in that case, static data members go "poof".
Also, how does the activity B know whether it's been recreated rather than called?
See if the saved instance state Bundle passed into onCreate() is not null. Or, see if a retained fragment exists, if you're using one of those.

clarification about Android documentation

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 (the next lesson talks more about
using a Bundle to save other state data in case your activity is
destroyed and recreated).
the same instance of the activity
how it could be exact instance when it is destroyed and recreated , isn't it going to be a new memory block (another instance ), could anyone help me to clear this point ?
Unfortunately the documentation isn't very clear in a lot of areas. This is one of them.
In theory, if Android were to destroy your activity while it is stopped, then it would call onDestroy() and the GC would reclaim the memory. In actual fact, Android never destroys individual activities to free up memory. What it actually does is it kills the entire OS process instead. In this case, onDestroy() never gets called on any of the activities in the process. The GC doesn't bother to clean up anything, because the VM (Virtual Machine) just dies immediately along with everything else in the process and the entire amount of memory in use by the process is reclaimed by the operating system.
When your user navigates back to your application, Android creates a completely new process for your application and then it will create a new instance of your activity. In this case it will, of course, get a completely new block of memory for the instance. You will also see the constructor get called and onCreate() will also be called. However, since Android saves the state of the activity's views, that state will be restored by the activity by the call to super.onCreate().
There are a few cases where Android will destroy an instance of an activity and create a new one automatically. This is done, for example, during a configuration (ie: orientation) change. In that case, Android calls onDestroy() on the old instance and creates a new instance of the activity. The new instance gets the saved state of the old instance and can therefore restore the state of the views as able. In this case, since a new instance is being created, it will, of course, have a different address in memory.
Once a component is destroyed, it is effectively dead and the memory it is using can be reclaimed by the GC. Android will never reanimate a dead instance.
Hopefully this clarifies the situation for you.
EDIT Add more details
Android keeps track of tasks and the activities in those tasks in its own internal data structures. For each activity, it keeps a copy of the Intent that started the activity, the most recent Intent sent to that activity and it keeps a Bundle containing the saved state of that activity. Android calls onSaveInstanceState() on an activity to give the activity a chance to save anything that it will need to restore the activity if Android decides to kill it. The default implementation of onSaveInstanceState() saves the state of all the activity's views. Your implementation needs to save anything else that the activity will need to restore itself if it is killed and recreated by Android (for whatever reason). Your activity's private member variables and static variables are not automatically saved and restored in the Bundle, so if you need these to be able to recreate your activity properly you must save them yourself using the Bundle provided by onSaveInstanceState(). Static variables will stay around for the lifetime of the application's process, but since Android can kill the process (to reclaim resources) at any time, without warning, you also cannot rely on static variables always having the expected values (in the case where Android has killed and recreated your process).
If your activity (or process) is killed by Android and the user later navigates back to your activity, Android uses the information it has in its internal data structures to create a new instance of the activity. It then calls onCreate() on the new instance passing the saved instance state Bundle as a parameter. This can then be used to restore the activity to the state it had before the original instance was killed. Android will also call onRestoreInstanceState() after calling onStart() so that you can also do your restoration of state in that method if you don't want to do it in onCreate().
Note: Remember that anything you want to save/restore in a Bundle must be either a primitive (int, boolean, etc.) or it must implement Serializable or Parcelable.
The documentation of onSaveInstanceState() contains some useful information about this as well.
The state is stored, but the activity is destroyed to save memory. So, when the user is in an Activity A and goes to the Activity B, the Activity A is destroyed, but the state is stored. When the user presses the back button, the state will be loaded and everything will be restored.
The system does it when the activity call onStop and onStart. I didn't make any test, I'm just saying what I understood.

How can I manage a the lifecycle of an Application instance

I've subclass Application to keep a global state in static variables. But if the application is leaved in the background, it is eventually getting killed, meaning my global state is lost.
I've tried to overcome this problem by subclassing Activity and override onSaveInstanceState to save the global state each time an Activity is destroyed and then recreate it when an Activity is created. This however, gives an non-negligible overhead each time an activity is created.
One could argue that if the application is killed the correct behaviour would be to let user start again from the first Activity, but as the application is killed quite often I don't find this acceptable.
Is there a better approach? I've seen some suggestion to save the global state in shared preferences instead of static variables, but for some reason I don't find that solution compelling.
onSaveInstanceState is for "interruptions" where the activity / is destroyed while the user is interacting. You should save small state information there, e.g. entered text for visible views (EditText, ...). Examples when this happens are: rotating the device (the activity is destroyed and recreated), incoming calls, ...
If the user does not interact with the application for some time you should save the state permanently. I recommend to use SharedPreferences or a database in this case. It's a bit more work, but it is the best solution in my opinion.

Categories

Resources