Навигационная система взломана на 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 вверх.

Мой путь к клику:

  • открыть Главная Деятельность
  • открыть подробное описание
  • нажмите кнопку "вверх"
  • Категория должна появиться
  • обратный клик переводит нас на главную активность с восстановленным государством.

И это поток, который будет ожидаться, и он отлично работает на всех 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() с тем же неудовлетворительным результатом.

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

Ответ 1

Прежде всего, если вы нацеливаете устройства на уровень 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 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 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 библиотека поддержки пытается аппроксимировать bahavior, введенную в API 16 (a.k.a. Android 4.1). Для платформ pre-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+ более поздняя версия поддерживает библиотеку поддержки, которая передает информацию в свой собственный вызов в API Activity. Согласно документам:

public boolean navigateUpTo (Intent upIntent)

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

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

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

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

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

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

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

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

Только в случае

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

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

Ответ 2

После прочтения ответа @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() здесь, поскольку он был вызван методом super.