Android: is onDestroy the new onStop?
Conventional Android development logic dictates that if there is some
action you want to perform (or rather, stop performing) when your
Activity
is no longer visible to the user, do it in onStop()
.
Likewise, if there is some action you want to restart performing when
the user restarts interacting with your Activity
, do it in
onStart()
. The disadvantage of this approach, of course, is that it
wouldn’t play well with device orientation changes.
This post explores a couple of solutions to this problem, and concludes
that there are cases where one has no choice but to postpone the actions
that would be ideally taken in onStop()
, to onDestroy()
.
A trivial (incorrect) example
public TrivialIncorrectActivity extends Activity{
//onCreate() and other life-cycle overrides like onResume() go here ...
@Override public void onStart(){
super.onStart();
startMakingThatPeriodicRestCall();
}
@Override public void onStop(){
super.onStop();
stopMakingThatPeriodicRestCall();
}
// ... Other life-cycle overrides like onDestroy() go here
}
This example is incorrect. Every time the user rotates the device, your app would stop making a REST call and then again start making the call. Not good at all.
setRetainInstance
to the rescue …
API 11 introduced the Fragment API, and along with it, the
setRetainInstance
method, which is also usable with older versions of Android by means of
the support
library.
You can go through the documentation to understand the effect of a
setRetainInstance(true)
. Essentially, when a configuration change is
happening, even though the hosting Activity
is being re-created, the
Fragment
instance is not destroyed.
So, this allows us to improve upon our previous example.
public IncorrectRotationTolerantActivity extends FragmentActivity{
private static final String TAG_RETAIN_FRAGMENT = "RetainFragment";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null){
getSupportFragmentManager().beginTransaction()
.add(IncorrectRetainFragment.newInstance(), TAG_RETAIN_FRAGMENT).commit();
}
}
}
public class IncorrectRetainFragment extends Fragment{
public IncorrectRetainFragment(){}
public static IncorrectRetainFragment newInstance(){
IncorrectRetainFragment frag = new IncorrectRetainFragment();
frag.setRetainInstance(true);
return frag;
}
@Override
public void onStart() {
super.onStart();
startMakingThatPeriodicRestCall();
}
@Override
public void onStop() {
super.onStop();
stopMakingThatPeriodicRestCall();
}
}
This code snippet still doesn’t do what we want it to do. It does not prevent re-making that REST call during orientation changes. Why?
Because, setRetainInstance
doesn’t prevent a Fragment’s onStop()
from being called - it just prevents onDestroy()
from being called.
So, even if you ask for a Fragment instance to be retained across
configuration changes, the onStop()
method of the Fragment is always
still called when the device is rotated.
onDestroy()
is the new onStop()
To fix the problem, postpone stopping the REST call to the onDestroy()
of the Fragment. Similarly, start making the call in onCreate()
instead of in onStart()
, since onCreate()
is not called when the
device is rotated, but onStart()
is.
public RotationTolerantActivity extends FragmentActivity{
private static final String TAG_RETAIN_FRAGMENT = "RetainFragment";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null){
getSupportFragmentManager().beginTransaction()
.add(RetainFragment.newInstance(), TAG_RETAIN_FRAGMENT).commit();
}
}
}
public class RetainFragment extends Fragment{
public RetainFragment(){}
public static RetainFragment newInstance(){
RetainFragment frag = new RetainFragment();
frag.setRetainInstance(true);
return frag;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onStart(savedInstanceState);
startMakingThatPeriodicRestCall();
}
@Override
public void onDestroy() {
super.onStop();
stopMakingThatPeriodicRestCall();
}
}
This seems so semantically wrong though. onDestroy()
represents the
end of the entire lifetime of an Activity/Fragment and what we really
wanted to do was monitor the visible lifetime. Also, there is no
guarantee that onDestroy()
will ever be called. If you really try out
this example on a phone or emulator, chances are that you’ll never see
the Rest call being stopped - at least not right away.
A more correct, more restrictive solution:
There exists another solution to this problem - but it works only on API
11 and later, because it uses methods introduced in API 11 -
isChangingConfigurations()
and getChangingConfigurations()
.
public RotationTolerantActivity extends FragmentActivity{
private boolean mRotated;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Boolean nonConfigState =
(Boolean)getLastCustomNonConfigurationInstance();
if (nonConfigState == null) {
mRotated = false;
} else {
mRotated = nonConfigState.booleanValue();
}
}
@Override
public void onStart(){
super.onStart();
if(!mRotated){
startMakingThatPeriodicRestCall();
}
}
@Override
public void onStop(){
super.onStop();
mRotated = false;
if (isChangingConfigurations()) {
int changingConfig = getChangingConfigurations();
if ((changingConfig & ActivityInfo.CONFIG_ORIENTATION) == ActivityInfo.CONFIG_ORIENTATION) {
mRotated = true;
}
}
if(!mRotated){
stopMakingThatPeriodicRestCall();
}
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
return mRotated ? Boolean.TRUE : Boolean.FALSE;
}
}
This solution is semantically correct, and works as expected. However,
it only works on API 11 and higher, even though we extend
FragmentActivity
from the support library .
Bonus: Why onStop()
and not onPause()
?
The keen reader would have observed that this post talks about stopping
un-needed tasks in onStop()
and not onPause()
- even though
onPause()
is the only one of these methods that is guaranteed to be
called. Remember that after onPause()
is called, the process could be
killed in order to reclaim memory and thus onStop()
and onDestroy()
might never be called.
Yet, this entire post insists on using onStop()
to stop un-needed
tasks. The reason for this lies in the technique used in my library
android-app-pause.
Unfortunately, this library in its current form does not handle device
orientation changes correctly. This will be fixed in a future release
though.