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.
Related
I have inherited some code hence I don't have true freedom to change it. :(
I have a main activity, from which other activities (I will refer to these as sub activities from now on) are called. Whenever one of these completes, it calls finish and returns data to the main activity.
Each activity (including the main one) has a bar on the top that displays a custom view. The custom view contains a canvas which has a drawing that is dependant upon the state of the network.. i.e. wifi/mobile etc...
Since that 'state' data never changes, it's held within a singleton and the view gets data from the singleton to define what it draws. That is working with no issues, i.e. the data is always as I expect it.
When I first launch the MainActivity, as the network changes, the data changes and each call to 'invalidate' the view receives a system call to 'onDraw' as I would expect.
In each of the sub activities the same is again true.
Upon finishing a sub activity and returning to the mainActivity, calls to invalidate no longer cause a call to onDraw to occur.
I have looked at this for quite a while now and just cannot figure out what is going wrong.
In my constructor I have:
setWillNotDraw(false);
Whenever the data changes the following methods are called:
invalidate();
requestLayout();
Now, there's one more thing... upon returning to the activity at that immediate point, I refresh and this DOES draw correctly, i.e. invalidate does trigger an onDraw call... any subsequent network changes (which are propogated) fail to result in the onDraw call.
I'm wondering if this is to do with the view somehow being detached. I can see that 'onDetachedFromWindow' is called, however the trigger for this is the destruction of the subactivity, hence I don't see why that should affect the MainActivity but it's the only thing I can think of.
I'm hoping I've provided enough information for someone to help me...
Well, in the end my answer has very little to do with the question and I guess this is an example of how an issue can be solved by going back to absolute basics and checking for the obvious.
My activities all inherit from an abstract activity. Within that activity there is an instance of the view. The views in which I was having trouble were using that declaration as opposed to having their own instance, hence behaviour from one activity was then affecting another inadvertently.
So, if I'd been able to post up all the code, I'm sure someone else would have spotted this but, unfortunately I couldn't in this instance.
Still, whilst this posting doesn't provide a resolution that will help others, maybe it does say... step back and check the obvious first!
So I have been looking at tutorials and messing with things for hours. I have my main activity which creates a map of object that it gets from the database. To avoid unnecessary database queries, I am trying to save that map using the onSaveInstanceState method and then restore it with the onRestoreInstanceState. I can see that it gets saved correctly using the debugger, but if I set break points in the onRestoreInstaceState method the program never breaks. Then I thought I would just do it with the onCreate() method but it's not breaking there either. The program should break in both of those places when returning from a different activity right?
Those two methods are only called if the activity is killed and restarted.
The good news is, if your activity was not killed, there's no need to restore your state. It should still be just the way it was.
If you do need to do something when switching back from another Activity, that's what onResume() is for.
See the chart for more.
Should I let go of views and other data i'm holding on to in onStop() or onDestroy()?
If I release my application's data in onDestroy() it won't be very memory-friendly towards android, am I correct? Since i'm still holding on to a couple of views after onStop(). Plus it's not guaranteed to be called and my Activity is purged from memory anyway.
If I release it in onStop(), I have to add do my setContentView() etc. in onStart() which doesn't get the Bundle that onCreate(Bundle) is handed.
Note that I have a very large application which consists of dozens of Views, most of which are custom and added by code rather than layout-files. This is largely due to the fact that I had to create a custom pager to flip through pages, since none of the built-in Views could serve my purposes (I've tried… hard…).
I have read through all the relevant Android docs but I still have no real clue on what about the view-hierarchy Android saves/recreates by itself and what I have to do myself. Or when all of that happens, meaning when Android removes the view-hierarchy from memory.
Update Question:
The android docs says this: Note: Because the system retains your Activity instance in system memory when it is stopped, it's possible that you don't need to implement the onStop() and onRestart() (or even onStart() methods at all.
If it's ok to hold on to everything, why should I care about memory-leaks when my application is being stopped like this article says? If it's destroyed and re-created, for example after a screen-rotation, I'm starting from scratch anyway?
No, you do not have to let go of anything in onStop() or onDestroy() if you only hold it in your activity (in its non-static fields). When Android let's go of your activity, the rest of your stuff is automatically thrown away (along with the activity), because there is no way to reach it from any thread (this is how Garbage Collectors work, it is not in any way special or specific to activities).
The article you refer to describes the problem where a reference to a view (or a drawable, or - broadly speaking - activity context) survives the activity that created it. Since there is a reference pointing back to the already dead activity, it becomes a zombie; what's more, it clings to all its fields, effectively zombifying them too. So if you have a view or a drawable and put it in a static field (or in any other place that might survive your activity object), then yes, you have to let it go in onStop() or onDestroy() of the relevant activity.
If Android destroys your Activity and forgoes calling onDestroy(), it means that the whole process was taken down, and this means that no memory leak can occur anyway (they are local to a single process)
Bonus answers:
views inflated from XML files take exactly as much memory as ones built in code. Why should they be heavier?
(update, after a comment) before an activity gets thrown away, Android walks its whole view hierarchy and gives each view a chance to store its state (any parcelable data) into a bundle; when recreating view Android walks the tree again and gives the data back. This is why when you recreate an activity the state of the view is saved (focus, position, content of text fields etc.). Observe how the state is only saved for elements that have an id (does not matter if they are inflated or created dynamically).
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());
}
Here's the scenario:
Account login page
Clicking on "Sign-in" triggers a login AsyncTask
To block the UI during network access, a ProgressDialog pops up
Upon returning, the ProgressDialog is dismissed and the user forwarded on
This flow works very well.
Here's the problem:
The user may rotate the screen while the AsyncTask is logging him/her in
Presently, the ProgressDialog is referenced by a class field, and dismissed using that pointer and call to .dismiss().
If the screen is rotated, though, everything crashes.
Probably because the Activity is re-created? My suspicion is that the closure around that field reference points to an object that is unreachable. What's your take?
How can I solve it reliably and elegantly? Just add if (... != null) checks?
More generally, I must confess I don't understand the "best practice" to apply in cases like this:
Activity A triggers an AsyncTask
The user navigates away from Activity A (back button? rotate screen? onClick that starts an Intent?)
The AsyncTask returns when Activity A is not the topmost one anymore yet its onPostExecute() has a UI effect , note: the original delegate observer is not available anymore.
Confused * (note: I am a beginner, so a thorough explanation would help me a lot)
Yes on changing the orientation, the activity is destroyed then recreated it again.
When a configuration change occurs at runtime, the activity is shut down and restarted by default, but declaring a configuration with this attribute will prevent the activity from being restarted. Instead, the activity remains running and its onConfigurationChanged() method is called.
Add this line android:configChanges="orientation|keyboardHidden" to your manifest file
<activity
android:name=""
android:label=""
android:configChanges="orientation|keyboardHidden" />
I recommend looking at Handling Runtime Changes. For a detailed explanation of the details of the methods available to you.
android:configChanges="orientation..." tells android your application will take care of resizing the current view hierarchy. As such, when you specify that in your manifest, your activity will not be destroyed and recreated, instead the system will just call your activity's `onConfigurationChanged()` method. As it so happens, most of the stock widgets will resize themselves when their container changes, so if you are using basic layouts, this usually "just works" by redrawing the view hierarchy in the new format. For custom widgets, this trick may not work.
The approved method is to save some configuration instance information when you are being destroyed in the onSaveInstanceState() method, and then recreate your state in onCreate()
In your case, the dialog is dismissed when then screen changes orientation, so you can either leave it that way, or reopen it in your onCreate().