Возврат из действия с помощью 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 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() активности А во второй раз Bundle параметр null а также getExtras() возвращается null, поскольку onSaveInstanceState() был вызван, я бы ожидал Bundle параметр, который не являетсяnull,

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

4 ответа

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

android:launchMode="singleTop"

в вашем AndroidManifest.xml.

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

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

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

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

saveInstanceState имеет значение 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);

Это происходит потому, что 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" на деятельность А. Смотрите его ответ для деталей. Обратите внимание, что singleTop подходит не для всех приложений. Вам придется попробовать и / или потратить некоторое время на чтение документации, чтобы решить для себя.

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

Я думаю, что это более общее решение, которое нужно добавить в базовый класс Activity:

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    if (item.getItemId() == android.R.id.home) {
        final Intent upIntent = NavUtils.getParentActivityIntent(this);
        if (upIntent == null)
            onBackPressed();
        else
            //optionally add this flag to the intent: Intent.FLAG_ACTIVITY_SINGLE_TOP
            NavUtils.navigateUpTo(this, upIntent);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

И для каждого действия, которое вы хотите переместить к определенному родительскому действию, используйте это в манифесте:

    <activity android:parentActivityName="pathToParentActivity" ...>
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="pathToParentActivity"/>
    </activity>

И, если ваш minSdk из API 16, удалите тег "meta-data", так что вместо этого у вас есть это в манифесте:

    <activity android:parentActivityName="pathToParentActivity" ...>
    </activity>

Больше информации здесь

Вы упоминаете:

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

Если ваш контент в Деятельности А находится во Фрагменте, тогда вызовите setRetainInstance(true) в onCreate() Фрагмента.

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

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