Source code is available here: https://github.com/novemberox/NavigationTest It\'s modified version of this sample: http://developer.android.com/training/implementing-navigat
First things first, if you are targeting devices as high as API 17 (Android 4.2), set the targetSdkVersion
to 17 in your manifest. This doesn't break support for old devices, it just makes things work properly for newer devices. Of course, that doesn't fix your problem -- it's just good to do it.
I assume you are basing your code off of the Ancestral Navigation example on this page. You've used the second example:
Intent upIntent = new Intent(this, MyParentActivity.class);
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
// This activity is not part of the application's task, so create a new task
// with a synthesized back stack.
TaskStackBuilder.from(this)
.addNextIntent(new Intent(this, MyGreatGrandParentActivity.class))
.addNextIntent(new Intent(this, MyGrandParentActivity.class))
.addNextIntent(upIntent)
.startActivities();
finish();
} else {
// This activity is part of the application's task, so simply
// navigate up to the hierarchical parent activity.
NavUtils.navigateUpTo(this, upIntent);
}
For the behavior you want, however, you would need to replace NavUtils.navigateUpTo
with:
upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(upIntent);
finish();
In general, the support library is trying to approximate the behavior introduced in later APIs. In the case of NavUtils
, the support library is trying to approximate the bahavior introduced in API 16 (a.k.a. Android 4.1). For pre-API 16 platforms, NavUtils uses:
@Override
public void navigateUpTo(Activity activity, Intent upIntent) {
upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
activity.startActivity(upIntent);
activity.finish();
}
This looks through the back stack for an instance of the activity specified in upInent
. If it finds one, it clears everything up to it and restores it. Otherwise it just launches the activity.
On API 16+ later platforms, the support library hands things off to the native call in the Activity
API. According to the docs:
public boolean navigateUpTo (Intent upIntent)
Navigate from this activity to the activity specified by upIntent, finishing this activity in the process. If the activity indicated by upIntent already exists in the task's history, this activity and all others before the indicated activity in the history stack will be finished.
If the indicated activity does not appear in the history stack, this will finish each activity in this task until the root activity of the task is reached, resulting in an "in-app home" behavior. This can be useful in apps with a complex navigation hierarchy when an activity may be reached by a path not passing through a canonical parent activity.
From that, it is unclear whether or not it will launch the activity specified in upIntent
if it is not in the back stack. Reading the source code doesn't help clarify things either. However, judging from the behavior your app is showing, it appears that it does not attempt to launch the upIntent
activity.
Regardless of which implementation is right or wrong, the end result is that you want the FLAG_ACTIVITY_CLEAR_TOP
behavior, not the native API 16 behavior. Unfortunately this means that you will have to duplicate the support library approximation.
Disclaimer: I don't work for Google, so this is my best guess.
My guess is that the API 16+ behavior is the intended behavior; it's a builtin implementation that has access to the Android internals and can do things that are not possible through the API. Pre-API 16, I don't think it was possible to unwind the backstack in this fashion other than by using intent flags. Thus, the FLAG_ACTIVITY_CLEAR_TOP
flag is the closest approximation of the API 16 behavior available to pre-API 16 platforms.
Unfortunately the result is that between the native implementation and the support library implementation there is a violation of the principle of least astonishment in your scenario. That leads me to wonder if this is an unanticipated use of the API. I.e., I wonder if Android expects that you to traverse the full navigation path to an activity, not jump directly to it.
Just to avoid one possible misconception, it is possible that someone might expect shouldUpRecreateTask
to magically determine that the parent isn't in the back stack and go through the process of synthesizing everything for you using the TaskStackBuilder
.
However, shouldUpRecreateTask
basically determines if your activity was launched directly by your app (in which case it returns false
), or if it was launched from another app (in which case it returns true
). From this book, the support library checks if the intent's "action" is not ACTION_MAIN
(I don't fully understand that), whereas on API 16 platforms it performs this check based on task affinity. Still, in the case of this app, things work out to shouldUpRecreateTask
returning false.
After reading @cyfur01's answer, I came up with very simple solution that fixes the behavior.
Override this method in your Activity (or ideally in some BaseActivity to have it fixed for all subclasses) and it should work as expected:
@Override
public boolean navigateUpTo(Intent upIntent) {
boolean result = super.navigateUpTo(upIntent);
if (!result) {
TaskStackBuilder.create(this)
.addNextIntentWithParentStack(upIntent)
.startActivities();
}
return result;
}
It leaves all work to Android implementation and then just checks return value of navigateUpTo(upIntent)
which is false
when an instance of the indicated activity could not be found and this activity was simply finished normally. Which is exactly the case when we want to create and start the parent Activities manually. Also note we don't have to call finish()
here as it was called by the super method.