savedInstanceState始终为空。

81

这是我的savedInstanceState代码:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) 
{
    savedInstanceState.putStringArrayList("todo_arraylist", Altodo);
    Log.v("bundle", "Saved");
    super.onSaveInstanceState(savedInstanceState);
}


public void onCreate(Bundle savedInstanceState) 
{
    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) 
    {
        Altodo = savedInstanceState.getStringArrayList("todo_arraylist");
        Log.v("bundle", "Restored");
    }
    else
    {
        Log.v("bundle", "null");
    }

    setContentView(R.layout.main);
}

日志始终显示“bundle save”标记。

但是在onCreate方法中,SavedInstanceState始终为null。


2
在将您的值添加到Bundle之前,您需要调用super.onSaveInstanceState(savedInstanceState),否则它们将在该调用中被清除(Droid X Android 2.2)。 - Zar E Ahmer
2
我遇到了同样的问题,并确认这个方法不起作用。有人已经解决了吗? - Jim Clermonts
13个回答

57

我在一个拥有两个继承自ActionBarActivity的活动(AB)的项目中观察到了完全相同的症状(作为问题133394报告)。 Activity A 是主活动,在从详细视图活动B返回时,我总是在其列表片段的onCreate中收到nullsavedInstanceState。经过多个小时的调试,这个问题最终表现为一个伪装成导航问题。

以下与我的设置相关且来自本页其他答案:

  • 根据答案,我确保每个片段和活动都设置了唯一的ID。
  • 没有在调用父函数的情况下重写onSaveInstanceState
  • AndroidManifest.xml中指定活动A为活动B的父级,使用android:parentActivityName属性和早期版本的Android对应的meta-data标签(参见“提供向上导航”)。

即使没有任何相应的创建代码,例如getActionBar().setHomeButtonEnabled(true),活动B在其操作栏中具有一个可用的后退按钮(<)。当点击该按钮时,活动A会重新出现,但是其所有之前的实例状态都会丢失(a)、始终调用onCreate(b),并且savedInstanceState始终为null(c)。

有趣的是,当我点击模拟器显示器底部提供的返回按钮(指向左侧的打开三角形),活动A会以原样重新出现(即其实例状态完全保留),而不会调用onCreate。所以也许导航存在问题?

经过更多阅读,我执行了自己的导航指令,以响应在活动B中的返回按钮上的轻按:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == android.R.id.home)
        NavUtils.navigateUpFromSameTask(this);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

与恢复活动状态无关的内容并没有改变。 NavUtils 还提供了一个方法 getParentActivityIntent(Activity)navigateUpTo(Activity, Intent) ,允许我们修改导航意图以明确指示活动A不会作为新任务启动(因此不提供保存的实例状态),通过设置FLAG_ACTIVITY_CLEAR_TOP标志来达到这个目的:

如果已设置,并且要启动的活动已在当前任务中运行,则不会启动该活动的新实例,而是关闭其上面的所有其他活动,并将此 Intent 作为新 Intent 传递给(现在处于顶部的)旧活动。

在我的手中,这就解决了丢失实例状态的问题,看起来像:

public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId()== android.R.id.home) {
        Intent intent = NavUtils.getParentActivityIntent(this);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        NavUtils.navigateUpTo(this, intent);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

请注意,在其他情况下,用户可以直接从不同的任务中切换到活动B,因此这可能不是完整的解决方案(请参见此处)。此外,一种可能与不使用NavUtils的行为相同的解决方案是仅调用finish()

public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId()== android.R.id.home) {
        finish();
        return true;
    }
    return super.onOptionsItemSelected(item);
}

这两种解决方案在我的手中都有效。我只是推测最初的问题是后退按钮有些不正确的默认实现,可能与该实现调用某种未包含FLAG_ACTIVITY_CLEAR_TOPnavigateUp有关。


2
太棒了。我使用你提供的最后一个解决方案将其实现到我的基本活动中,但是我用onBackPressed();替换了finish(); - Andrea Lazzarotto
2
这个答案帮了我很多!虽然我仍然不确定为什么ActionBar返回按钮的行为与设备返回按钮不同,但是一旦我在AndroidManifest.xml中添加了“android:launchMode= 'singleTop'”到我的Activity的XML定义中,它就开始始终正常工作。根据文档,这本质上与您所做的添加“FLAG_ACTIVITY_CLEAR_TOP”相同。 - JHS
每次启动另一个活动时调用finish();是否高效?有更好的选择吗? - Elijah Mock
2
2020年了,我仍然遇到这个错误。太荒谬了。Google忽略了133394问题长达5年,直到2020年4月才将其关闭为“不再修复过时的问题”。感谢您的全面回答;最后的onOptions...finish()代码块解决了我的问题。看起来你的方法在相当长的一段时间内将是唯一的解决方案。再次感谢,你让我在抓狂数天后得以解脱。 - Yurelle

15

用这种方式保存的状态不是持久化的。如果整个应用程序像你在调试期间所做的那样被杀死,则在 onCreate 中,bundle 始终为 null。

在我看来,这又是糟糕的 Android 文档的另一个例子。这也是为什么市场上大多数应用程序根本不正确地实现了状态保存。


2
这只是用于屏幕方向更改等吗? - Fabian Zeindl
8
我非常同意“可怕的文档”这一点。 - Fabian Zeindl
1
在理论上,当活动被销毁然后重新创建时,保存的状态仍将被创建,但是无法测试这种情况。IME,这种使用情况确实会发生。 Translated text: Fabian,在理论上,当活动被销毁然后重新创建时,保存的状态仍将被创建,但是无法测试这种情况。IME,这种使用情况确实会发生。 - mxcl
3
看起来像是一个 bug,因为文档明确表示即使销毁活动,状态也会被保存。http://developer.android.com/guide/components/activities.html#SavingActivityState 我创建了这个问题 https://code.google.com/p/android/issues/detail?id=133394 - Simon
1
我们如何保存持久状态呢? - David Callanan
显示剩余2条评论

14

你检查一下那个视图有没有设置ID(如果它是/有视图...)。否则 onSaveInstanceState() 不会被调用。

请查看这个 链接


3
这个回答似乎与所问的问题无关。Med 表示 onSaveInstanceState() 总是会被调用,因此问题不在于 onSaveInstanceState() 没有被调用。 - P1x

8

在清单文件中为活动添加以下行:

android:launchMode="singleTop"

例如:

<activity
        android:name=".ActivityUniversity"
        android:label="@string/university"
        android:launchMode="singleTop"
        android:parentActivityName="com.alkhorazmiy.dtm.ActivityChart">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.alkhorazmiy.dtm.ActivityChart" />
    </activity>

1
指定此行对 Activity 有什么作用? - David Read
@DavidRead 很抱歉回复晚了,将活动标记为“singleTop”会使得如果后台中存在现有的活动,则使用该活动。 “如果在启动活动时,前台已经有与用户交互的相同活动类的实例,则重用该实例。这个现有的实例将接收到一个新的Intent调用onNewIntent()。” 之后你只需要在活动中覆盖“onNewIntent()”即可。 使用“standard”时我的uri为空,而“singleInstance”由于进程死亡而重新创建了它。 - CyberShark

3

你如何测试它?

我认为最好的方法是在“设置”>“开发人员选项”中使用“不保留活动”标志。如果在设置中没有“开发人员选项”,请参见启用设备上的开发人员选项

  1. 打开你的活动
  2. 长按主页键
  3. 转到另一个应用程序
  4. 长按主页键
  5. 返回到你的应用程序

DevTools只能在模拟器中使用。如果你想要使用同样的方法在真实设备上进行测试,可以使用SetAlwaysFinish工具。请参考此答案获取更多详情:https://dev59.com/J2435IYBdhLWcg3wqB8x#8621269 - Theo
现在所有设备都可以使用开发者选项。 - Jade

2
在你的重写方法中,super.onSaveInstanceState(savedInstanceState); 应该是第一行吗?
编辑:War_Hero在评论中指出,有关该主题的文档表明不应该是第一行。

这对我没有解决问题。 - Doug Amos
3
不应该这样做,请查看谷歌的文档,解释了为什么:http://developer.android.com/training/basics/activity-lifecycle/recreating.html#SaveState - Mightian

1
检查您在AndroidManifest.xml中的活动,并删除android:noHistory属性,如果为真。
<activity
    // ....
    android:noHistory="false" />

0
实现一个 onRestoreInstanceState 方法并将以下代码放在里面。
Altodo = savedInstanceState.getStringArrayList("todo_arraylist");

我已经尝试过了。我在这里遇到了同样的问题:https://dev59.com/Z2855IYBdhLWcg3w5Igb - med

0

要进行调试,请考虑实现onRestoreInstanceState方法并在此方法中调用Log.d。然后,在模拟器中按ctrl-F11或其他键旋转手机。您的Log.d调用应该会被触发。


1
我已将log.v更改为log.d,但它从未触发。 - med
@med,如果在单个活动中,在方向更改时未调用onRestoreInstanceState,则您的情况可能比我们所理解的更复杂。我在这里有一些代码:https://sites.google.com/site/jalcomputing/home/mac-osx-android-programming-tutorial/saving-instance-state - JAL

0
我发现当我重写 onSaveInstanceState() 并在 Bundle 中实际保存一些数据时,实例状态会被恢复。否则就不会。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接