Let's imagine I have an Activity that performs an asynchronous operation as follows:
smtpClient.sendEmail(new EmailSendListener(){
#override
public void OnEmailSendSuccess(){
// Do stuff here
}
});
And before the callback method is called, for whatever reason the Android system decides to destroy my Activity. What happens to the callback?
Does it never run?
Does it run anyway because the instance of the Activity is technically still alive despite the Activity.onDestroy() method been called? If this is the case, should I in the callback code check if the Activity has not been destroyed to avoid accessing fields that are no longer available?
What happens if the entire process, not just the Activity, gets destroyed? That is, even the instance of my Activity is gone. Can someone give me the inside scoop of what's going on behind the scenes?
Related
When an Activity goes in pause state, an instance of it remains in the Activity stack that is managed by operating system. And I know after a while, it destroys the instance. Now my question is whether the onDestroy() method is called when operating system destroys the Activity instance after a long while?
Like if I put a Toast inside onDestroy() method, that will be shown when the Activity instance gets destroyed by OS? (I know it will be shown by pressing back button).
The nature of this question makes it hard to test because sometimes it takes a day or more for OS in order to destroy an Activity instance in stack.
There is no guarantee that it will be called. You can see Activity#onDestroy for that.
Note: do not count on this method being called as a place for saving data! For example, if an activity is editing data in a content provider, those edits should be committed in either onPause() or onSaveInstanceState(Bundle), not here. This method is usually implemented to free resources like threads that are associated with an activity, so that a destroyed activity does not leave such things around while the rest of its application is still running. There are situations where the system will simply kill the activity's hosting process without calling this method (or any others) in it, so it should not be used to do things that are intended to remain around after the process goes away.
By simply killing it could be e.g. a System.exit call or something similar where it skips the lifecycle hooks.
I have an Activity that creates a retained Fragment in order to persist a long running network operation in the event that the Activity goes through any configuration changes during the long running operation. So basically I'm trying to use the retained Fragment like a Singleton that lives until Activity's onDestroy() is called.
My goal is to have Activity's onCreate() trigger the long running network operation because I only want it to run when the activity is created not every time it starts again (otherwise I'd put it in onStart()).
To do this I first create a retained fragment in Activity's onCreate, then use FragmentManager to add the retained fragment then I kick off the network call in Activity's onCreate method and pass the networking object to the retained Fragment to hold onto.
This works, however I'm concerned because if I log what's going on I can see that the Activity first sets data on the retained Fragment and then the retained Fragment's onCreate() method is called. This looks wrong and seems out of order, however it works.
Is it bad practice to utilize the retained Fragment instance before the fragment has run its onCreate() method?
EDIT
After reading the responses and thinking a bit more about this, I'm realizing that initiating the network call from Activity onCreate() although convenient, is risky to do. As noted in the responses there's a chance that the long running operation returns very quickly and attempts to manipulate the Activity's view which may not have been initialized yet. So for my specific case I am resorting to initiating the long running operation from Activity's onStart() method and then using the retainedFragment to cache the response. This way even if onStart() is called multiple times and attempts to kick off the long running operation again, the result from the first attempt will be cached and can be returned.
I will admit that's unusual, but I can't think of a reason off the top of my head why it would be bad. The advantage of a retained Fragment is the reference to the Fragment is not destroyed so the references it holds on to are also retained. onCreate() and onDestroy() are called once throughout the lifetime of the Fragment (when the Fragment is added and removed respectively).
The danger might be that the asynchronous operation finishes before onCreate() is called. Likewise, the operation could finish after onDestroy() is called and when you expected the Fragment to be running. There are some methods like Fragment#setArguments() that can not be called during some parts of the Fragment's lifecycle. If you were to call these methods when you expected the Fragment to be running, then you will get an Exception thrown. So you end up having to put in a bunch of checks like if(isAttached()) { /* do this */ }. Putting the operation in onCreate() will ensure that it at least started before the operation finished.
But if you are not actually relying on any of the Fragment's functionality, then it should be fine. The lifecycles are only meant to tell you what's going on with it.
As long as you are not relying on Activity/Fragment views being available while Fragment's onCreate, you will be fine:
Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point.
(https://developer.android.com/reference/android/app/Fragment.html#onCreate(android.os.Bundle)
Still, you need to secure possible edge case that long-running operation may be finished before Activity and Fragment are fully created (if you depend on their views, this might be an issue).
Therefore, think about what you will do when long-running operation has finished and the results need to be presented somewhere.
I feel like this should have been answered by the documentation in the Activity class, but I'm still not positive -- when is it actually safe to update the UI of an Activity (or a Fragment)? Only when the activity is resumed, or any point between being started and stopped?
For example, the Activity docs state:
The visible lifetime of an activity happens between a call to onStart() until a corresponding call to onStop(). During this time the user can see the activity on-screen, though it may not be in the foreground and interacting with the user. Between these two methods you can maintain resources that are needed to show the activity to the user. For example, you can register a BroadcastReceiver in onStart() to monitor for changes that impact your UI, and unregister it in onStop() when the user no longer sees what you are displaying. The onStart() and onStop() methods can be called multiple times, as the activity becomes visible and hidden to the user.
From that reading, I would assume that even if a foreground dialog is displaying, I can safely update UI elements behind it.
EDIT
To clarify: the reason I ask is having been bitten by errors when firing off an AsyncTask, and attempting to update the UI in the onPostExecute method. Even though it runs on the UI thread, the user has navigated away from that view and I would receive an exception. I'm starting a new project now and trying to establish some guidelines around better AsyncTask idioms.
I guess this comes down to what you mean by "safe" to update the UI. For elements on the Activity screen, they can be updated whenever, even if your Activity is not in the foreground (but make sure to update from the UI Thread).
However, the issue that will trip you up is saving state: onSaveInstanceState.
As you may know, the Activity that is in the background may be destroyed by the OS to free up memory. It will then be re-created when you come back to it. During this process, the onSaveInstanceState method will be called. If the OS does destroy the Activity, any changes you made to the UI State after the call to onSaveInstanceState will not be persisted.
For Fragments, you will actually get an IllegalStateException if you try to commit a FragmentTransaction after onSaveInstanceState. More info on that.
To summarize, you can update the UI of your activity at any point and try to gracefully handle the Fragment issues, but you may lose these updates when the Activity is restored.
So, you could say that it is only truly safe to update the Activity while it is in the foreground, or rather, before the call to onSaveInstanceState.
Edit in regards to Async Task onPostExectue
This is likely related to the issue I am referring to above with Fragment State Loss. From the blog post I linked to:
Avoid performing transactions inside asynchronous callback methods. This includes commonly used methods such as AsyncTask#onPostExecute() and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with performing transactions in these methods is that they have no knowledge of the current state of the Activity lifecycle when they are called. For example, consider the following sequence of events:
An activity executes an AsyncTask.
The user presses the "Home" key, causing the activity's onSaveInstanceState() and onStop() methods to be called.
The AsyncTask completes and onPostExecute() is called, unaware that the Activity has since been stopped.
A FragmentTransaction is committed inside the onPostExecute() method, causing an exception to be thrown.
In my app, In an special situation I need to start an Activity and put it behind the current showing Activity, I mean if the current Activity, which is being shown by the user was closed, the user now can see the Activity which I just started. how to do that?
You cannot do this.
Activities will always start right away. You cannot manipulate the back stack in the way you want.
You would have to detect the activity closing and then start your new activity then.
You can implement your case by using onDestroy ()
Perform any final cleanup before an activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method.
Note: do not count on this method being called as a place for saving data! For example, if an activity is editing data in a content provider, those edits should be committed in either onPause() or onSaveInstanceState(Bundle), not here. This method is usually implemented to free resources like threads that are associated with an activity, so that a destroyed activity does not leave such things around while the rest of its application is still running. There are situations where the system will simply kill the activity's hosting process without calling this method (or any others) in it, so it should not be used to do things that are intended to remain around after the process goes away.
Derived classes must call through to the super class's implementation of this method. If they do not, an exception will be thrown.
Source : http://developer.android.com
When I start my android-activity first onCreate() is called, then onResume().
When I then press the back-button to return to the home-screen and tap the app-icon again, first onCreate() is called, then onResume() and then onDestroy().
My app is still active then, but doing some action result in error since onDestroy() sets a few members to null.
Why is that?
Update: When I wait 30 seconds after pressing back everything works fine. I'm not doing anything heavy in onDestroy except setting a few variables to null and .interrupt()ing a background-thread.
Figured this out by myself. My understanding of what an Activity is was flawed. Of course Android calls onDestroy() on the old Activity instance, which can happen several seconds after the new one has been constructed.
onDestroy gets called because, by default, pressing back key results in your activity calling finish() which initiates the destroying of the activity which calls onDestroy().
To prevent doing some action in case the activity is being destroyed do like this:
if(!isFinishing()) {
// do your action here
}
isFinishing is a method of the Activity.
are you doing some heavy operations in onDestroy(). I think your activity view is destroyed, but not the activity object. And you tap on the App icon even before the previous Activity object is actually destroyed.
I think there is something in addition to what you are describing. Android doesn't just keep activity from being destroyed, something MUST be happening on the main thread.
The symptoms sound exactly as if you had either:
a service doing a longish HTTP or database operation. Are you sure there are no suxg things?
another thread (perhaps managed by an AsyncTask?) calling a synchronized method