如何在 Android 中进行屏幕旋转时保存由我的类组成的状态?

58

我在研究Android应用程序处理方向更改的方式(我发现它会在方向更改时重启MainActivity)。我看到您可以覆盖该方法

protected void onSaveInstanceState(Bundle outState)

在onStart()方法中保存数据。问题是我有一个带有自定义对象的视图和使用自定义适配器的ListView。所有内容都在这些对象的ArrayList中,但我发现无法将任意对象放入Bundle中! 那么我该如何保存状态?

6个回答

139

你尝试使用过以下方法吗:它是一个解决问题的方法,

<activity name= ".YourActivity" android:configChanges="orientation|screenSize"/>

在清单文件中吗?

默认情况下不起作用,因为当你改变方向时,onCreate会被再次调用并重新绘制你的视图。

如果你写了这个参数,就不需要在Activity中处理了,框架会处理其余的事情。 它会保留屏幕或布局的状态,如果方向发生变化。

注意: 如果你在横屏模式下使用了不同的布局,则添加这些参数后,横屏模式下的布局将不会被调用。

另一种方法另外一种方法


4
非常感谢,这将对我非常有帮助! - Marius
11
为什么这不是默认的工作方式?谁希望他们的数据在旋转时丢失? - CodeSlinger512
1
@Davidguygc,它默认情况下不起作用,因为当您更改方向时,“onCreate”将再次被调用并重新绘制您的视图。 - Rahul Patil
40
一个大而无用的 -1。"也许最不专业且最广泛被滥用的解决方法是通过在Android清单文件中设置android:configChanges属性来禁用默认的销毁和重建行为。" 来源 - Simon Forsberg
2
@SimonAndréForsberg,这篇文章是更新的。大多数开发者都明白,我的回答只是一个变通方法,如果他们不想在清单文件中使用参数,也可以使用onSaveInstanceStateonRestoreInstanceStateonConfigurationChanged等方法。解决问题有不同的方式,并且每种方式都有不同的解决问题的角度。 - Rahul Patil
显示剩余4条评论

48

编辑:在新一代 Android 系统中,使用兼容库和保留片段通常是处理在活动销毁/创建过程中维护昂贵的可重建数据的最佳方式。正如 Dianne 指出的那样,保留非配置数据是为了优化诸如缩略图生成之类的性能好处而保存,但如果需要重新执行,则不会影响活动功能。它不能替代正确的保存和恢复活动状态。

但是,当我在2010年首次回答这个问题时:
如果您想保留自己的(非视图状态)数据,则可以使用 onRetainNonConfigurationInstance() 在屏幕旋转期间传递任意对象。请参见此Android Developers博客文章。只需小心,不要将任何视图或其他对旋转前上下文/活动的引用放在您传递的对象中,否则您将阻止这些对象被垃圾回收并可能最终耗尽内存(这称为上下文泄漏)。


8
请注意,onRetainNonConfigurationInstance() 应仅用作优化。即使没有这种情况,您的应用程序仍应该正常工作……因为不能保证它会被调用,事实上,在许多情况下它肯定不会被调用。您应首先通过完整的保存状态机制使应用程序正确运行,然后再实现此方法以进行优化。 - hackbod
以上链接不可用。你能给我其他的链接吗? - Debopam Mitra
1
是的,自从这个问题被回答以来,近三年来情况发生了变化!如果你正在使用Android 3.x+或兼容库,现在几乎总是更好地调用setRetainInstance Fragment。 - Yoni Samlan

18

首先,您需要确定您的应用程序中实际上是什么"状态"。虽然您没有具体说明您正在做什么,但让我假设对象ArrayList是用户正在使用的状态。

其次,决定此状态实际上的生命周期。它是否真的与该活动相关联?或者,如果用户的电池电量不足、设备关闭,并且稍后返回您的应用程序,则用户是否不会失去它?如果是前者,则onSaveInstanceState()是正确的;如果是后者,则您将需要在onPause()中保存到持久性存储。

对于自定义对象的onSaveInstanceState(),关键是实现Parcelable接口。这涉及实现Parcelable上的方法以及在类中创建静态CREATOR对象。下面是典型简单的Parcelable类的代码:

https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/content/ComponentName.java

关键函数是Parcelable实现:

https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/content/ComponentName.java#317

和CREATOR静态类:

https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/content/ComponentName.java#355

(静态的writeToParcel()readFromParcel()只是为该类完成的方便,不是必需的。)

现在,您可以使用Bundle.putParcelableArrayList将整个对象的ArrayList放入已保存的状态Bundle中:

http://developer.android.com/reference/android/os/Bundle.html#putParcelableArrayList(java.lang.String, java.util.ArrayList)

Activity.onCreate()中,检查是否有savedState Bundle,如果有,则尝试从其中检索ArrayList并在找到时使用它,在新活动中创建一个新适配器和列表视图来显示它。

3
使用Google的Gson将您的对象写入JSON字符串,然后将它们保存为一个字符串。在重新构建Activity/Fragment时,从保存的JSON字符串中重新构建它们。
import com.google.gson.Gson;

public class MyClass {
    private int a;
    private String s;
    private OtherSerializableClass other;
    private List<String> list;

    public String toJson() {
        return new Gson().toJson(this);
    }

    public static ChatAPI_MessagesArray fromJson(String json){
       return new Gson().fromJson(json, YourClass.class);
    }

    // Getters
    ...

    // Setters
    ...
}

我在MainActivity中使用程序化方式创建了许多布局、TextView和Button。您提供的解决方案是否适用于我的情况,还是我需要采用不同的方法?谢谢。 - Eftekhari

3

但我注意到你不能将任意对象放入Bundle中!

首先将自定义对象实现 Parcelable 接口。
然后,您可以将它们放入 Bundle 中。

一切都在这些对象的 ArrayList 中

您可以使用 putParcelableArrayList 方法将自定义 "parcelable" 对象的 ArrayList 存储在 Bundle 中。


0

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