Возврат из действия с использованием navigateUpFromSameTask()

У меня есть два действия: A и B. Когда действие A сначала запускается, оно обращается к переданному ему Intent (поскольку Bundle - null, как и должно быть в первый раз), и соответственно отображает информацию:

CustInfo m_custInfo;
...
protected void onCreate(Bundle savedInstanceState)
{
    ...
    Bundle bundle = (savedInstanceState == null) ? getIntent().getExtras() : savedInstanceState;
    m_custInfo = (CustInfo) m_bundle.getSerializable("CustInfo");
    if (m_custInfo != null
        ...
}

Это отлично работает в первый раз. Элементы управления EditText и ListView заполнены правильно.

Теперь, когда щелкнут элемент в списке, начнется действие B, чтобы показать детали:

m_custInfo = m_arrCustomers.get(pos);

Intent intent = new Intent(A.this, B.class);
intent.putExtra("CustInfo", m_custInfo); // CustInfo is serializable
// printing this intent, it shows to have extras and no flags

startActivityForResult(intent, 1);

Сразу же перед началом работы B каркас вызывает A overridden onSaveInstanceState():

protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);

    outState.putSerializable("CustInfo", m_custInfo);
}

В действии B, когда на панели действий нажата кнопка "Вверх", я хочу вернуться к активности A и быть в том же состоянии, что и раньше:

public boolean onOptionsItemSelected(MenuItem item)
{
    if (item.getItemId() == android.R.id.home)
    {
        Intent intent = NavUtils.getParentActivityIntent(this);
        // printing this intent, it shows to have flags but no extras

        NavUtils.navigateUpFromSameTask(this); // tried finish() here but that created an even bigger mess
        return true;
    }
    ...
}

В этом заключается проблема, когда в onCreate() активности A второй раз параметр Bundle null и getExtras() возвращает null. Поскольку onSaveInstanceState() был вызван, я бы ожидал, что параметр Bundle будет не null.

Я читал об этой проблеме на других веб-сайтах, пробовал предложения, но ничего не работает.

Ответ 1

Если вы хотите, чтобы ваше приложение реагировало таким образом, вы должны объявить режим запуска своей деятельности A как:

android:launchMode="singleTop"

в вашем AndroidManifest.xml.

В противном случае андроид использует стандартный режим запуска, что означает

"Система всегда создает новый экземпляр действия в целевая задача"

и ваша деятельность воссоздана (см. Документация по Android).

С помощью singleTop система возвращается к вашей существующей активности (с оригинальной дополнительной), если она находится в верхней части задней части задачи. В этой ситуации нет необходимости реализовывать onSaveInstanceState.

savedInstanceState имеет значение null в вашем случае, поскольку ваша активность ранее не была отключена ОС.

Обратите внимание (спасибо, разработчик Android для указания этого) :

В то время как это решение вопроса, оно не будет работать, если действие, которое возвращается, не находится в верхней части заднего стека. Рассмотрим случай активности A, начинающийся с B, который начинается с родительской активности C и C, настроен на A. Если вы вызываете NavigateUpFromSameTask() из C, активность будет воссоздаваться, потому что A не находится поверх стека.

В этом случае вместо этого можно использовать этот кусок кода:

Intent intent = NavUtils.getParentActivityIntent(this); 
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP); 
NavUtils.navigateUpTo(this, intent);

Ответ 2

Это происходит потому, что NavUtils.navigateUpFromSameTask() в основном просто вызывает startActivity(intentForActivityA). Если в действии A используется значение по умолчанию android:launchMode="standard", то создается новый экземпляр действия, и сохраненное состояние не используется. Есть три способа исправить это, с различными преимуществами и недостатками.

Вариант 1

Переопределите getParentActivityIntent() в действии B и укажите соответствующие дополнительные функции, необходимые для активности A:

@Override
public Intent getParentActivityIntent() {
    Intent intent = super.getParentActivityIntent();
    intent.putSerializable("CustInfo", m_custInfo);
    return intent;
}

Примечание. Если вы используете ActionBarActivity и панель инструментов из библиотеки поддержки, вам необходимо переопределить getSupportParentActivityIntent(), вместо этого.

Я чувствую, что это самое элегантное решение, так как оно работает независимо от того, как пользователь перешел к активности B. Две приведенные ниже опции не работают правильно, если пользователь непосредственно переходит к активности B, не проходя через активность A, для пример, если действие B запускается из обработчика уведомления или URL-адреса. Я думаю, что этот сценарий является причиной того, что API-интерфейс "вверх" является сложным и не просто действует как кнопка "немой".

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

Вариант 2

Перехватите кнопку "вверх" и обработайте ее как тупую кнопку "Назад", переопределив onNavigateUp():

@Override
public boolean onNavigateUp() {
    onBackPressed();
    return true;
}

Примечание. Если вы используете ActionBarActivity и панель инструментов из библиотеки поддержки, вам нужно переопределить onSupportNavigateUp(), вместо этого.

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

Вариант 3

Как было предложено yonojoy, установите android:launchMode="singleTop" на активность A. Подробнее см. его ответ. Обратите внимание, что singleTop не подходит для всех приложений. Вам придется попробовать и/или потратить некоторое время на чтение документации, чтобы решить для себя.

Ответ 3

Вместо вызова NavUtils.navigateUpFromSameTask

Вы можете получить намерение родителя и изменить его перед навигацией. Например:

Intent intent = NavUtils.getParentActivityIntent(this);
intent.putExtra("CustInfo", m_custInfo); // CustInfo is serializable
NavUtils.navigateUpTo(this, intent);

Это будет вести себя точно так же, как NavUtils.navigateUpFromSameTask, но позволит вам добавить некоторые дополнительные свойства к намерению. Вы можете просто взглянуть на код для NavUtils.navigateUpFromSameTask.

public static void navigateUpFromSameTask(Activity sourceActivity) {
    Intent upIntent = getParentActivityIntent(sourceActivity);

    if (upIntent == null) {
        throw new IllegalArgumentException("Activity " +
                sourceActivity.getClass().getSimpleName() +
                " does not have a parent activity name specified." +
                " (Did you forget to add the android.support.PARENT_ACTIVITY <meta-data> " +
                " element in your manifest?)");
    }

    navigateUpTo(sourceActivity, upIntent);
}

Создание родительской активности SINGLE_TOP может работать для некоторых простых приложений, но что, если эта активность не была запущена непосредственно из нее родительской? ИЛИ вы можете законно хотеть, чтобы родитель существовал в нескольких местах в фоновом стеке.

Ответ 4

Вы упомянули:

В действии B, когда на панели действий нажата кнопка Up, я хочу вернуться к активности A и быть в том же состоянии, что и раньше.

Если ваш контент в действии A находится во фрагменте, тогда вызовите setRetainInstance (true) в onCreate() фрагмента.

Затем экземпляр фрагмента будет сохранен во время активного отдыха.