Навигация сломана на JellyBean?

Исходный код доступен здесь: https://github.com/novemberox/NavigationTest Это модифицированная версия этого примера: http://developer.android.com/training/implementing-navigation/ancestral.html

У меня есть три вида деятельности:

  • Main Activity просто основной вход в приложение
  • Category Activity это родитель для детализации деятельности
  • Detail Activity

Main Activity имеет кнопку, которая открывается Category Activity а также Detail Activity, Category Activty имеет только одну кнопку, которая открывается Detail Activity, И наконец Detail Activity это показывает некоторый текст и имеет кнопку вверх, которая имитирует нажатие на ActionBar вверх.

Мой "кликающий" путь:

  • открыть Основная деятельность
  • открыть Детальная активность
  • нажмите кнопку "вверх"
  • Категория Activty должна появиться
  • обратный щелчок перемещает нас в главное действие с восстановленным состоянием

И это поток, который можно было бы ожидать, и он прекрасно работает на каждом Android до Jelly Bean (тестировано на Galaxy Nexus 4.1.1 и эмуляторе 4.2 Google exp pack). Работает даже на ICS. Я использую поддержку lib и такие классы, как NavUtils и TaskStackBuilder, как в примере, который я указал в начале.

На JB, когда я нажимаю кнопку "вверх", он возвращается к основному действию с правильно восстановленным состоянием. Я посмотрел в исходный код библиотеки поддержки, и я увидел, что NavUtils.navigateUpTo метод вызывает родной код JB, как Activity#navigateUpTo, Я пробовал оба NavUtils#navigateUpTo() а также NavUtils.navigateUpFromSameTask() с таким же неудовлетворительным результатом.

Есть ли у вас какие-либо предложения, чтобы сделать этот хороший поток?

2 ответа

Перво-наперво, если вы нацеливаетесь на устройства с API 17 (Android 4.2), установите targetSdkVersion до 17 в вашем манифесте. Это не нарушает поддержку старых устройств, оно просто заставляет вещи работать должным образом для новых устройств. Конечно, это не решает вашу проблему - просто хорошо это сделать.

Что вы должны использовать?

Я предполагаю, что вы основываете свой код на примере Ancestral Navigation на этой странице. Вы использовали второй пример:

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);
 }

Однако для поведения, которое вы хотите, вам необходимо заменить NavUtils.navigateUpTo с:

upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(upIntent);
finish();

Почему это работает на ICS и ранее?

В общем, библиотека поддержки пытается приблизить поведение, представленное в более поздних API. В случае NavUtils библиотека поддержки пытается приблизиться к поведению, введенному в API 16 (он же Android 4.1). Для платформ, предшествующих API 16, NavUtils использует:

@Override
public void navigateUpTo(Activity activity, Intent upIntent) {
    upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    activity.startActivity(upIntent);
    activity.finish();
}

Это просматривает задний стек для экземпляра действия, указанного в upInent, Если он его находит, он все очищает и восстанавливает. В противном случае он просто запускает активность.

На более поздних платформах API 16+ библиотека поддержки передает исходный вызов в Activity API. Согласно документам:

public boolean navigateUpTo (Intent upIntent)

Перейдите от этого действия к действию, указанному в upIntent, и завершите это действие в процессе. Если действие, указанное с помощью upIntent, уже существует в истории задачи, то это действие и все другие действия до указанного действия в стеке истории будут завершены.

Если указанное действие не отображается в стеке истории, оно завершит каждое действие в этой задаче до тех пор, пока не будет достигнуто корневое действие задачи, что приведет к поведению "внутри приложения". Это может быть полезно в приложениях со сложной иерархией навигации, когда действие может быть достигнуто путем, не проходящим через каноническое родительское действие.

Исходя из этого, неясно, запустит ли он действие, указанное в upIntent если это не в заднем стеке. Чтение исходного кода также не помогает прояснить ситуацию. Однако, судя по поведению вашего приложения, кажется, что оно не пытается запустить upIntent деятельность.

Независимо от того, какая реализация является правильной или неправильной, конечным результатом является то, что вы хотите FLAG_ACTIVITY_CLEAR_TOP поведение, а не нативное поведение API 16. К сожалению, это означает, что вам придется дублировать приближение библиотеки поддержки.

Что правильно?

Отказ от ответственности: я не работаю на Google, так что это мое лучшее предположение.

Я предполагаю, что поведение API 16+ является предполагаемым поведением; это встроенная реализация, которая имеет доступ к внутренним компонентам Android и может делать то, что невозможно через API. До API 16, я не думаю, что было возможно развернуть backstack таким способом, кроме как с помощью намеренных флагов. Таким образом FLAG_ACTIVITY_CLEAR_TOP Флаг является наиболее близким приближением поведения API 16, доступным для платформ до API 16.

К сожалению, в результате между собственной реализацией и реализацией библиотеки поддержки в вашем сценарии нарушается принцип наименьшего удивления. Это заставляет меня задаться вопросом, является ли это непредвиденным использованием API. Т.е. мне интересно, ожидает ли Android того, что вы пройдете полный путь навигации к действию, а не перейдете непосредственно к нему.

Так, на всякий случай

Просто чтобы избежать одного возможного заблуждения, возможно, что кто-то может ожидать shouldUpRecreateTask волшебным образом определить, что родитель не находится в заднем стеке, и пройти через процесс синтеза всего для вас, используя TaskStackBuilder,

Тем не мение, shouldUpRecreateTask в основном определяет, была ли ваша деятельность запущена непосредственно вашим приложением (в этом случае она возвращает false) или если он был запущен из другого приложения (в этом случае он возвращает true). Из этой книги библиотека поддержки проверяет, не является ли "действие" намерения ACTION_MAIN (Я не совсем понимаю это), тогда как на платформах API 16 эта проверка выполняется на основе соответствия задач. Тем не менее, в случае с этим приложением все получается shouldUpRecreateTask возвращая ложь.

Прочитав ответ @cyfur01, я пришел к очень простому решению, которое исправляет поведение.

Переопределите этот метод в своей деятельности (или, в идеале, в некоторой BaseActivity, чтобы он был исправлен для всех подклассов), и он должен работать как положено:

@Override
public boolean navigateUpTo(Intent upIntent) {
    boolean result = super.navigateUpTo(upIntent);
    if (!result) {
        TaskStackBuilder.create(this)
            .addNextIntentWithParentStack(upIntent)
            .startActivities();
    }
    return result;
}

Это оставляет всю работу для реализации Android, а затем просто проверяет возвращаемое значение navigateUpTo(upIntent) который false когда экземпляр указанного действия не может быть найден, и это действие было просто нормально завершено. Это как раз тот случай, когда мы хотим создать и запустить родительские действия вручную. Также обратите внимание, что нам не нужно звонить finish() здесь, как это было вызвано методом супер.

Другие вопросы по тегам