69

I have two activities, A and B. When activity A is first started, it accesses the Intent passed to it (because the Bundle is null, as it should be the first time through), and displays information accordingly:

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
        ...
}

This works fine the first time through. The EditText controls and ListView are filled out correctly.

Now, when an item in the list is clicked, activity B is started to show the details:

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

Right before acivity B is started, the framework calls A's overridden onSaveInstanceState():

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

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

In activity B, when the Up button is pressed in the action bar, I want to return to activity A and have it be in the same state as it was before:

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

Herein lies the problem, when in onCreate() of activity A the second time, the Bundle parameter is null and getExtras() returns null. Since onSaveInstanceState() was called, I would have expected the Bundle parameter to be non-null.

I've read about this issue on other web sites, have tried the suggestions, but nothing works.

4

5 回答 5

133

如果您希望您的应用程序以这种方式做出反应,您应该将活动 A 的启动模式声明为:

android:launchMode="singleTop"

在您的 AndroidManifest.xml 中。

否则android使用标准启动模式,这意味着

“系统总是在目标任务中创建一个新的活动实例”

并且您的活动被重新创建(请参阅Android 文档)。

使用 singleTop 系统会返回到您现有的活动(带有原始额外活动),如果它位于任务的后堆栈的顶部。在这种情况下不需要实现 onSaveInstanceState。

在您的情况下,savedInstanceState 为 null,因为您的活动以前没有被操作系统关闭。

注意(感谢 android 开发人员指出这一点

虽然这是对问题的一种解决方案,但如果返回的活动不在后堆栈的顶部,则它将不起作用。考虑Activity A启动B的情况,也就是启动C,C的父Activity配置为A。如果NavigateUpFromSameTask()从C调用,会重新创建Activity,因为A不在栈顶。

在这种情况下,可以改用这段代码:

Intent intent = NavUtils.getParentActivityIntent(this); 
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP); 
NavUtils.navigateUpTo(this, intent);
于 2013-04-22T12:27:23.083 回答
21

发生这种情况是因为NavUtils.navigateUpFromSameTask()基本上只是调用startActivity(intentForActivityA). 如果活动 A 使用默认值android:launchMode="standard",则创建活动的新实例并且不使用保存的状态。有三种方法可以解决此问题,各有优缺点。

选项1

覆盖活动 B 中的getParentActivityIntent()并指定活动 A 所需的适当附加功能:

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

注意:如果您使用的是 AppCompatActivity 和 androidx 或支持库中的工具栏,那么您需要重写getSupportParentActivityIntent()

我觉得这是最优雅的解决方案,因为无论用户如何导航到活动 B,它都可以正常工作。如果用户直接导航到活动 B 而不通过活动 A,下面列出的两个选项将无法正常工作,例如,如果活动B 从通知或 URL 处理程序启动。我认为这种情况是“向上”导航 API 很复杂并且不能简单地充当愚蠢的后退按钮的原因。

此解决方案的一个缺点是使用标准的开始新活动过渡动画而不是返回到先前活动的动画。

选项 2

通过覆盖onNavigateUp()拦截向上按钮并将其视为愚蠢的后退按钮:

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

注意:如果您使用 AppCompatActivity 和来自 androidx 或支持库的工具栏,那么您需要覆盖onSupportNavigateUp()

这个解决方案有点hacky,只适用于“up”和“back”应该表现相同的应用程序。这可能很少见,因为如果“向上”和“返回”应该表现相同,那么为什么还要麻烦有一个向上按钮呢?此解决方案的一个优点是使用了标准的返回上一活动动画。

选项 3

根据 yonojoy 的建议,设置android:launchMode="singleTop"活动 A。有关详细信息,请参阅他的答案。请注意,这singleTop并不适用于所有应用程序。您必须尝试一下和/或花一些时间阅读文档以自己决定。

于 2014-11-06T22:00:51.780 回答
4

而不是打电话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);
}

使父 Activity SINGLE_TOP 可能适用于一些简单的应用程序,但如果该 Activity 不是直接从其父 Activity 启动的呢?或者您可能合法地希望父级存在于后堆栈中的多个位置。

于 2014-12-19T05:09:06.320 回答
0

我认为这是一个更通用的解决方案,可以添加到您的基本 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 的每个 Activity,请在清单上使用它:

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

而且,如果您的 minSdk 来自 API 16,请删除“元数据”标签,这样您就可以在清单中使用它:

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

更多信息在这里

于 2018-12-26T08:02:42.480 回答
-1

你提到:

在活动 B 中,当在操作栏中按下向上按钮时,我想返回到活动 A 并使其处于与以前相同的状态。

如果您在 Activity A 上的内容驻留在 Fragment 中,则在 Fragment 的 onCreate() 中调用 setRetainInstance(true)。

然后 Fragment 实例将在 Activity 重新创建期间保留。

于 2014-02-17T22:00:17.190 回答