onDestroy() while "waiting" for onActivityResult()

2,152 views
Skip to first unread message

Tamás Kovács

unread,
Apr 23, 2012, 7:10:58 PM4/23/12
to android-platform
I've an app with two activities: "A" and "B".
"A" uses startActivityForResult() to spawn "B" i.e. it waits for "B".
Now, assume that "B" is in foreground. Assume the Android system
decides to free up memory. In theory, can it destroy (onDestroy())
activity "A" without also destroying "B"? I suppose yes. So when "B"
is finished e.g. after user input, activity "A" must be recreated
(onCreate()) and put to the foreground again. The question is below:

In "A", actually, I have a menu item that opens "B". Assume that "A"
was killed (but not my process -- if this was the case, "B" would have
been killed too, because they ran in the same process). When "B"
finishes, "A" must be recreated, i.e. it will start from onCreate().
But "A" has a starting screen, i.e. its initial screen is not the menu
that opened "B". Consequently, all in all, do I actually need to
*persist* the state of "A" such that when "A" is recreated, it must
detect that we're waiting for the result of "B"? (In the UI experience
respect, of course. I do know that the docs say that "You will receive
the onActivityResult() call immediately before onResume() when your
activity is re-starting.")

Note: I do know that my app's persistent values must be properly
persisted (and I use persistence in the proper lifecycle events such
as onPause()). The question relates to the UI (User Interface) state
(i.e. exactly what menu was open when "B" was started). So far, I
didn't need to persist my UI state because I thought that that "A"
always re-starts from onStart(), and forgot that it may restart from
scratch (onCreate). My default UI state is set in onCreate().

Christopher Tate

unread,
Apr 23, 2012, 7:23:16 PM4/23/12
to android-...@googlegroups.com
On Mon, Apr 23, 2012 at 4:10 PM, Tamás Kovács <falcon.f...@gmail.com> wrote:
I've an app with two activities: "A" and "B".
"A" uses startActivityForResult() to spawn "B" i.e. it waits for "B".
Now, assume that "B" is in foreground. Assume the Android system
decides to free up memory. In theory, can it destroy (onDestroy())
activity "A" without also destroying "B"? I suppose yes.

No, it cannot.  To free up memory, the operating system kills whole processes, not individual components.  Assuming that you are not declaring A and B to run in different processes, you will never see A killed for memory without B also going away.

What *can* happen is that the orientation of the device is changed on the fly, or some other aspect of the global configuration (such as the locale).  Unless you take steps to handle such configuration changes manually, the default behavior is for activities to be paused, stopped, and destroyed; then a new activity instance created, started, and resumed with the new configuration in place.  (This is one of very few circumstances when onDestroy() is actually called, by the way.  When processes are killed to reclaim memory, the onDestroy() method is not called.  This is why the documentation tells you that you cannot rely on onDestroy() as an opportunity to save your activity state.)

If you are asking what an appropriate opportunity to save state is, the onSaveInstanceState() callback is a good place to look.  Also, I suggest that you review the core documentation about the activity lifecycle, which discusses this aspect of Android software.

--
christopher tate
android framework engineer

Tamás Kovács

unread,
Apr 23, 2012, 8:19:33 PM4/23/12
to android-platform
Thank you for the answer, it's invaluable. I've 3 quick questions, I
would even appreciate even "Yes-no" answers if you can afford the
time.


> On Mon, Apr 23, 2012 at 4:10 PM, Tamás Kovács
> <falcon.firebre...@gmail.com>wrote:
>
> > I've an app with two activities: "A" and "B".
> > "A" uses startActivityForResult() to spawn "B" i.e. it waits for "B".
> > Now, assume that "B" is in foreground. Assume the Android system
> > decides to free up memory. In theory, can it destroy (onDestroy())
> > activity "A" without also destroying "B"? I suppose yes.
>
> No, it cannot.  To free up memory, the operating system kills whole
> processes, not individual components.  Assuming that you are not declaring
> A and B to run in different processes, you will never see A killed for
> memory without B also going away.
But, due to the activity stack, the opposite must be true (i.e. B gets
killed but A doesn't when A has finished), am I right? You mentioned
one of the very few scenarios of onDestroy(). I'm experiencing
another: when "B" finishes, I see B.onDestroy() in logcat. Exactly as
the "Tasks and Back Stack" of the docs says.

You mention that the OS kills processes for memory, not components. My
initial confusion was because of this (from the docs): "An empty
process is one hosting no activities or other application components."

This implies that it's possible that a process hosts zero Activities
(but still retains the state of my static variables, as they are not
tied to any Activity).
Based on your answer, I would think that the following statement is
correct: "if all my Activities (of the same app) are destroyed by
Android, then my process will be destroyed too". Is this true?


> What *can* happen is that the orientation of the device is changed on the
> fly, or some other aspect of the global configuration (such as the locale).
My app is a complex game, which forces "landscape" orientation, so I
treat it manually, as you mention. It will be probably ignore all
other configuration changes too, but it can't see into the future, so
I've an interesting problem: assume the player is in the middle of a
strategic battle, and a configuration change occurs (that my app can't
ignore because it doesn't know it exists). This will cause a restart,
as you've explained. In the game, our philosophy is that if the game
starts with onCreate() (for any reason) then we start with the main
menu and the user can resume the game there; but if the interrupted
game only returns with onStart() or onResume(), then we jump back to
the strategic battle (with gamepaused state, of course) without
displaying the main menu. Question: Is there a way to detect that my
game was restarted due to a config change? (and e.g. not because it
was killed by the system or the phone was suddenly shut down).

Tamás Kovács

unread,
Apr 23, 2012, 8:22:01 PM4/23/12
to android-platform
Sorry, a typo:

"(i.e. B gets killed but A doesn't when ***A*** has finished)"

The correct version is:

(i.e. B gets killed but A doesn't when ***B*** has finished)

Christopher Tate

unread,
Apr 23, 2012, 8:37:34 PM4/23/12
to android-...@googlegroups.com
On Mon, Apr 23, 2012 at 5:19 PM, Tamás Kovács <falcon.f...@gmail.com> wrote:
Thank you for the answer, it's invaluable. I've 3 quick questions, I
would even appreciate even "Yes-no" answers if you can afford the
time.

Okay...
 
> On Mon, Apr 23, 2012 at 4:10 PM, Tamás Kovács
> <falcon.firebre...@gmail.com>wrote:
>
> > I've an app with two activities: "A" and "B".
> > "A" uses startActivityForResult() to spawn "B" i.e. it waits for "B".
> > Now, assume that "B" is in foreground. Assume the Android system
> > decides to free up memory. In theory, can it destroy (onDestroy())
> > activity "A" without also destroying "B"? I suppose yes.
>
> No, it cannot.  To free up memory, the operating system kills whole
> processes, not individual components.  Assuming that you are not declaring
> A and B to run in different processes, you will never see A killed for
> memory without B also going away.
But, due to the activity stack, the opposite must be true (i.e. B gets
killed but A doesn't when B has finished), am I right? You mentioned

one of the very few scenarios of onDestroy(). I'm experiencing
another: when "B" finishes, I see B.onDestroy() in logcat. Exactly as
the "Tasks and Back Stack" of the docs says.

I think you are unclear on "killed" vs onDestroy().  When I say "killed" I am talking about the OS delivering a SIGKILL to the process, which of course terminates it immediately with no code in that process having the opportunity to react.  This is what happens when the OS needs memory and decides to tear down your application's process.  It is unrelated to the Activity lifecycle.

When B is finished, it is cleanly torn down (including, as you say, the full lifecycle including onDestroy()), after which it does not exist any more; the Activity instance is dereferenced and allowed to be garbage collected.
 
You mention that the OS kills processes for memory, not components. My
initial confusion was because of this (from the docs): "An empty
process is one hosting no activities or other application components."

This implies that it's possible that a process hosts zero Activities
(but still retains the state of my static variables, as they are not
tied to any Activity).

That is indeed possible.  The OS does not automatically tear down processes that have no live components because it is a significant optimization to keep them present, inactive, in case some application component is relaunched a short time later.  In that case, the component in question (whether a service, an activity, a broadcast receiver, etc) is simply instantiated in the existing process [and VM instance].
 
Based on your answer, I would think that the following statement is
correct: "if all my Activities (of the same app) are destroyed by
Android, then my process will be destroyed too". Is this true?

No.  The process is kept around, inactive, in case some component of your application is needed again.  Think of this as your app process being *cached*.  The process itself will only be torn down if the OS needs memory.
 
> What *can* happen is that the orientation of the device is changed on the
> fly, or some other aspect of the global configuration (such as the locale).
My app is a complex game, which forces "landscape" orientation, so I
treat it manually, as you mention. It will be probably ignore all
other configuration changes too, but it can't see into the future, so
I've an interesting problem: assume the player is in the middle of a
strategic battle, and a configuration change occurs (that my app can't
ignore because it doesn't know it exists).

If your app forces landscape orientation then you'll basically never see such a configuration change while the app is running in the foreground.  You can also declare in your manifest that your activity wishes to handle configuration changes directly, in which case you will get a callback rather than having your activity instance destroyed & replaced.  See the docs for details, in particular http://developer.android.com/guide/topics/manifest/activity-element.html#config .

Tamás Kovács

unread,
Apr 23, 2012, 8:55:55 PM4/23/12
to android-platform
>  You can also declare in your manifest that your activity wishes to handle
> configuration changes directly, in which case you will get a callback
> rather than having your activity instance destroyed & replaced.  See the
> docs for details [..]

The docs state that the callback is called only for the config changes
that I defined in the manifest. What about future config changes that
I can't know? It seems that onRetainNonConfigurationInstance seems to
be the only option (as it's called regardless of the manifest). (The
minSdkLevel for our game is 8.)
I read about SDK Levels and their additivity, but the following
scenario is still unclear:
1. I have API level 8, and I set all config change types in the
manifest to be treated manually
2. But newer API levels have new config change types (=> they cannot
be in my manifest) -- what happens if my game encounters such a new
config change? (I know the most probable answer is that it gets
restarted, per the documentation. But in theory, it would also be
logical if it ignored it, because the new config change type was not
yet present in *its* targeted API level. I'm sorry if I overlooked it,
but I can't find an explicit answer anywhere to this.)
This is why I think there is no way to manually handle (e.g. ignore)
all future config change events too (except tricking with
onRetainNonConfigurationInstance()).

Christopher Tate

unread,
Apr 23, 2012, 9:02:58 PM4/23/12
to android-...@googlegroups.com
On Mon, Apr 23, 2012 at 5:55 PM, Tamás Kovács <falcon.f...@gmail.com> wrote:
>  You can also declare in your manifest that your activity wishes to handle
> configuration changes directly, in which case you will get a callback
> rather than having your activity instance destroyed & replaced.  See the
> docs for details [..]

The docs state that the callback is called only for the config changes
that I defined in the manifest. What about future config changes that
I can't know? It seems that onRetainNonConfigurationInstance seems to
be the only option (as it's called regardless of the manifest). (The
minSdkLevel for our game is 8.)

That is true; there may be configuration changes added in the future.  You must be prepared to have your activity torn down and rebuild in these cases.  Speaking very broadly, any such future config changes are going to be kinds of changes that cannot be ignored safely in the general case.  A truly future-proof app will need to accept the need to properly handle the defined configuration-change flow of activity destruction and relaunch.

Consider the case of a new configuration change types defined in API 13:   screenSize.  The physical size of the available screen has changed.  This is probably not something that your game's current UI can safely be preserved through if it is unaware of the change.

Tamás Kovács

unread,
Apr 23, 2012, 9:41:16 PM4/23/12
to android-platform
> Consider the case of a new configuration change types defined in API 13:
> screenSize.  The physical size of the available screen has changed.  This
> is probably not something that your game's current UI can safely be
> preserved through if it is unaware of the change.
My correct relaunch is guaranteed, but for optimal user experience,
it's good to have a *hint* that it was due to a config change (that
is, I don't want to prevent the relaunch, just learn about its
reason). Because if there was a configchange-triggered relaunch, I
want to return the player to a paused active gameplay, bypassing the
company logo & mainmenu (unlike in case of other onCreate() cases).
I'm thinking of using onRetainNonConfigurationInstance() with any
object, and if it returns null, it means the launch wasn't because of
a config change (I do know this method isn't guaranteed to be called,
it's OK, as the game loads correctly to mainmenu).

Finally, just to get rid of my last tad bit of doubt:

> > In theory, can it destroy (onDestroy())
> > activity "A" without also destroying "B"? I suppose yes.
>
> No, it cannot. To free up memory, the operating system kills whole
> processes, not individual components. Assuming that you are not declaring
> A and B to run in different processes, you will never see A killed for
> memory without B also going away.

My bad, I initially confused two questions into one question (as I
confused process kills with Activity lifecycle and onDestroy),
therefore your answer can also be interpreted in two ways ("killed for
MEMORY"). So let's forget about freeing up process memory, and focus
on Activity lifecycle only. Assuming the same scenario ("A" started
"B", and now "B" is in the foreground), is it possible that A gets
gets an onDestroy while (the foreground) B doesn't? (let's disregard
the possible config change -- it would restart both A and B).

Thank you
Reply all
Reply to author
Forward
0 new messages