旋转屏幕时丢失数据

44

我的应用程序有一个有趣的小bug。当用户旋转屏幕时,我的活动中会丢失一些数据。是否有人知道为什么会出现这种情况?


我找到的最快速的解决方案是这个:<a>http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange</a> - Theodor Paulus
4
一个有趣的回答:你可以强制固定设备方向,这样它就不能旋转了。如果坚持不让设备旋转比允许旋转更重要,那就建议采取这种方式。 - user1212212
12个回答

77
//Use onSaveInstanceState(Bundle) and onRestoreInstanceState

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {

  // Save UI state changes to the savedInstanceState.   
  // This bundle will be passed to onCreate if the process is  
  // killed and restarted.

  savedInstanceState.putBoolean("MyBoolean", true);  
  savedInstanceState.putDouble("myDouble", 1.9);  
  savedInstanceState.putInt("MyInt", 1);  
  savedInstanceState.putString("MyString", "Welcome back to Android");  

  // etc.  

  super.onSaveInstanceState(savedInstanceState);  
}  

//onRestoreInstanceState  

@Override  
public void onRestoreInstanceState(Bundle savedInstanceState) {  

  super.onRestoreInstanceState(savedInstanceState);  

  // Restore UI state from the savedInstanceState.  
  // This bundle has also been passed to onCreate.  

  boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");  
  double myDouble = savedInstanceState.getDouble("myDouble");  
  int myInt = savedInstanceState.getInt("MyInt");  
  String myString = savedInstanceState.getString("MyString");  
}

当系统旋转时删除数据,以下是保存数据的方法。


1
我从这里得到的...https://dev59.com/MnVC5IYBdhLWcg3w51hv - jaisonDavis
注意!在存储值之前,应先调用super.onSaveInstanceState(savedInstanceState)。 - Donovan Keating
onRestoreInstanceState并不总是被调用。 - Alberto M

44

默认情况下,当屏幕旋转时,您的活动将被销毁并重新启动。为确保不会丢失任何数据,您需要使用生命周期方法正确保存和恢复数据。请参见保存持久状态


2
尽管Android会保存一些数据,例如EditText的内容。 - RivieraKid
3
@RivieraKid,我不知道你是否可以依赖于这一点。你看过有官方声明说是这样的吗?如果不是规范中真正的内容,我会担心某些非品牌手机稍微改变了实现方式。最安全的做法是假定没有任何东西被保存。 - Cheryl Simon
1
@CherylSimon,EditText有一个名为freezesText的属性,默认值为true。官方文档链接:https://developer.android.com/reference/android/widget/TextView.html#attr_android:freezesText。 - stone
我觉得这很不可思议,它最初只是一个简单的旋转问题,可以用一些“技巧”来解决,几年后我们就有了一个全新的设计模式(MVVM)来解决这个简单的问题。 - Ricardo Yubal

15

更多细节

其他人已经提供了代码。所以我将在此基础上添加更多细节。

屏幕旋转时会发生什么?

当屏幕旋转时,Activity的配置会发生变化,因此系统会寻找更适合该Activity的资源。为此,系统会杀死Activity的实例并重新创建一个新的Activity实例。

系统如何创建一个新实例?

系统尝试使用称为实例状态的旧Activity实例的一组保存数据来重新创建实例。InstanceState是存储在Bundle对象中的键值对集合。

系统会自动保存一些数据

默认情况下,系统会将View对象保存在Bundle中,例如:

  • EditText中的文本
  • ListView中的滚动位置等

如果您想要存储更多数据以在方向改变后继续使用,您应该重写onSaveInstanceState(Bundle savedInstanceState)方法。

正确使用onSaveInstanceState

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

如果您不小心忘记调用super.onSaveInstanceState(savedInstanceState),默认行为将不起作用, 即EditText中的文本不会保存。如果您不相信我,请查看这个

使用哪种方法来恢复状态?

很多人包括我都感到困惑。我应该选择

  • onCreate(Bundle savedInstanceState)

还是

  • onRestoreInstanceState(Bundle savedInstanceState)

没有关系。两种方法都在参数中接收相同的bundle。唯一的区别是您需要在onCreate(Bundle savedInstanceState)方法中提供一个null检查。但是如果您要使用onRestoreInstanceState(Bundle savedInstanceState),请小心使用

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}

始终调用super.onRestoreInstanceState(savedInstanceState),以便系统默认地恢复View层次结构


8

这是对@jaisonDavis的实用回答的一个变化版本:

int myInt;
String myString;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_verses);


    if (savedInstanceState == null) {

        Intent intent = getIntent();
        myInt = intent.getIntExtra("MyIntIntentKey", DEFAULT_INT);
        myString = intent.getStringExtra("MyStringIntentKey", DEFAULT_STRING);

    } else { // savedInstanceState has saved values

        myInt = savedInstanceState.getInt("MyIntKey");
        myString = savedInstanceState.getString("MyStringKey");
    }

}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {

    savedInstanceState.putInt("MyIntKey", myInt);
    savedInstanceState.putString("MyStringKey", myString);

    super.onSaveInstanceState(savedInstanceState);
}

在这个例子中,变量在第一次创建活动时从Intent进行初始化,但之后它们将从savedInstanceState进行初始化(例如当屏幕方向改变时)。

8

在这种情况下,它会保留所有内容,包括布局。我希望布局随着方向的改变而改变,但对象(例如TextView)仍然显示旋转之前的值。 - user846316
1
尽管这可能适合某些需求,但除非它是最后的选择,否则应避免使用此解决方案。甚至在Android文档的开头就有一个警告。 - lpdavis13

7
AndroidManifest.xml 文件中添加以下代码:

<activity android:name=".MainActivity" android:configChanges="keyboardHidden|orientation|screenSize" >

这段代码可以使应用程序在屏幕旋转或者键盘弹出时不会重新启动。

4

2

In the AndroidManifest.xml add the following code

<activity android:name=".MainActivity"
            android:configChanges="keyboardHidden|orientation|screenSize"
            >

It works for me


2
这将解决您保留状态的问题,但不幸的是它只适用于 Fragment 而不是标准的 Activity
setRetainInstance(true);

onCreate() 方法中调用此方法。

你从哪里得到那个方法的? - c-an
2
OP的问题标签应该是“activity”,而不是“fragment”。 - S.R

1

configChanges解决了我的问题

<activity android:name=".MyActivity"
      android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
      android:label="@string/app_name">

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