如何使用保存实例状态保存Android活动状态?

How do save an Android Activity state using save instance state?

我在android sdk平台上工作过,目前还不清楚如何保存应用程序的状态。因此,考虑到"hello,android"示例的这一次要重新调整工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.android.hello;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    if (savedInstanceState == null) {
       mTextView.setText("Welcome to HelloAndroid!");
    } else {
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

我认为这对于最简单的情况来说已经足够了,但是无论我如何离开应用程序,它总是用第一条消息来响应。

我相信这个解决方案和重写onPause或类似的方法一样简单,但是我已经在文档中搜索了30分钟左右,没有发现任何明显的问题。


您需要重写onSaveInstanceState(Bundle savedInstanceState)并将要更改的应用程序状态值写入Bundle参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(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.
}

bundle本质上是一种存储nvp("名称-值对")映射的方法,它将被传递到onCreate()onRestoreInstanceState()中,然后在其中提取如下值:

1
2
3
4
5
6
7
8
9
10
@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");
}

您通常会使用此技术存储应用程序的实例值(选择、未保存的文本等)。


savedInstanceState仅用于保存与活动的当前实例相关联的状态,例如当前导航或选择信息,以便如果Android破坏并重新创建活动,它可以像以前一样恢复。见onCreateonSaveInstanceState文件。

对于更长寿命的状态,请考虑使用sqlite数据库、文件或首选项。请参见保存持久状态。


请注意,根据http://developer.android.com/reference/android/app/activity.html中有关活动状态的文档,使用onSaveInstanceStateonRestoreInstanceState进行持久性数据是不安全的。

文档说明(在"活动生命周期"部分中):

Note that it is important to save
persistent data in onPause() instead
of onSaveInstanceState(Bundle)
because the later is not part of the
lifecycle callbacks, so will not be
called in every situation as described
in its documentation.

换句话说,在onPause()onResume()中为持久数据保存/恢复代码!

编辑:为了进一步澄清,这里是onSaveInstanceState()文件:

This method is called before an activity may be killed so that when it
comes back some time in the future it can restore its state. For
example, if activity B is launched in front of activity A, and at some
point activity A is killed to reclaim resources, activity A will have
a chance to save the current state of its user interface via this
method so that when the user returns to activity A, the state of the
user interface can be restored via onCreate(Bundle) or
onRestoreInstanceState(Bundle).


我的同事写了一篇文章解释了Android设备上的应用程序状态,其中包括关于活动生命周期和状态信息的解释,如何存储状态信息,以及保存到状态BundleSharedPreferences中,并在此查看。

本文涵盖三种方法:

使用实例状态包为应用程序生存期(即临时)存储本地变量/ui控制数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Code sample – Store state in state bundle]
@Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
  // Store UI state to the savedInstanceState.
  // This bundle will be passed to onCreate on next call.  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  savedInstanceState.putString("Name", strName);
  savedInstanceState.putString("Email", strEmail);
  savedInstanceState.putBoolean("TandC", blnTandC);

  super.onSaveInstanceState(savedInstanceState);
}

使用共享首选项在应用程序实例(即永久)之间存储本地变量/ui控制数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[Code sample – store state in SharedPreferences]
@Override
protected void onPause()
{
  super.onPause();

  // Store values between instances here
  SharedPreferences preferences = getPreferences(MODE_PRIVATE);
  SharedPreferences.Editor editor = preferences.edit();  // Put the values from the UI
  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  editor.putString("Name", strName); // value to store
  editor.putString("Email", strEmail); // value to store
  editor.putBoolean("TandC", blnTandC); // value to store
  // Commit to storage
  editor.commit();
}

使用保留的非配置实例在应用程序生存期内的活动之间在内存中保持对象实例活动

1
2
3
4
5
6
7
8
9
[Code sample – store object instance]
private cMyClassType moInstanceOfAClass; // Store the instance of an object
@Override
public Object onRetainNonConfigurationInstance()
{
  if (moInstanceOfAClass != null) // Check that the object exists
      return(moInstanceOfAClass);
  return super.onRetainNonConfigurationInstance();
}


这是Android开发的经典"gotcha"。这里有两个问题:

  • 有一个微妙的Android框架错误,它在开发过程中极大地复杂化了应用程序堆栈管理,至少在旧版本上(不完全确定是否/何时/如何修复)。我将在下面讨论这个bug。
  • 管理此问题的"正常"或预期方法本身相当复杂,具有onpause/onresume和onsaveinstancestate/onrestoreinstancestate的双重性。

浏览所有这些线程时,我怀疑开发人员大部分时间都在同时讨论这两个不同的问题…因此,"这对我不管用"的所有困惑和报道。

首先,要澄清"预期"行为:OnSaveInstance和OnRestoreRestance是脆弱的,并且只适用于瞬时状态。预期用途(AFAIT)是在手机旋转(方向改变)时处理活动娱乐。换句话说,当您的活动在逻辑上仍处于"顶层"状态,但仍必须由系统重新启动时,您就可以使用它了。保存的包不会在进程/memory/gc之外持久化,因此如果您的活动转到后台,您就不能真正依赖于它。是的,也许您的活动的内存将在其后台访问和退出GC之后继续存在,但这不可靠(也不可预测)。

因此,如果您有一个场景,其中有意义的"用户进度"或状态应该在应用程序的"启动"之间保持,那么指导是使用onpause和onresume。您必须自己选择并准备一个持久存储。

但是-有一个非常令人困惑的bug,它使所有这些复杂化了。详情如下:

http://code.google.com/p/android/issues/detail?ID=2373

http://code.google.com/p/android/issues/detail?ID=5277

基本上,如果您的应用程序是用singletask标志启动的,然后从主屏幕或启动程序菜单启动它,那么随后的调用将创建一个新的任务…实际上,你的应用程序有两个不同的实例驻留在同一个堆栈中…很奇怪很快。当你在开发过程中启动你的应用程序时(比如从Eclipse或Intellij),开发人员会经常遇到这种情况。但也可以通过应用商店的一些更新机制(因此它也会影响您的用户)。

在我意识到我的主要问题是这个bug,而不是预期的框架行为之前,我花了几个小时的时间在这些线程中挣扎。在这个答案中,一个伟大的书面记录和workaround(更新:见下文)似乎来自user@kaciula:

主按键按下行为

2013年6月更新:几个月后,我终于找到了"正确"的解决方案。您不需要自己管理任何有状态的StartedApp标记,您可以从框架中检测到这一点,并适当地保释。我在我的启动程序活动的开头使用这个。创建:

1
2
3
4
5
6
7
8
if (!isTaskRoot()) {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
        finish();
        return;
    }
}


当系统需要内存并终止应用程序时,调用onSaveInstanceState。当用户刚关闭应用程序时,不会调用它。因此,我认为应用程序状态也应该保存在onPause中,它应该保存到一些持久存储中,如PreferencesSqlite


这两种方法都是有用和有效的,并且都最适合不同的场景:

  • 用户终止应用程序并在以后重新打开它,但是应用程序需要从上一个会话重新加载数据——这需要一种持久存储方法,如使用sqlite。
  • 用户切换应用程序,然后返回到原来的位置,并希望在它们离开的位置恢复—在onSaveInstanceState()onRestoreInstanceState()中保存和恢复捆绑数据(如应用程序状态数据)通常是足够的。
  • 如果以持久的方式保存状态数据,则可以在onResume()onCreate()中重新加载(或在任何生命周期调用中实际加载)。这可能是或可能不是所期望的行为。如果您将它存储在InstanceState中的一个包中,那么它是暂时的,只适合存储数据,以便在同一用户"会话"(我松散地使用术语session)中使用,而不适合在"会话"之间使用。

    这并不是说一种方法比另一种更好,就像所有的方法一样,了解您需要什么样的行为并选择最合适的方法是非常重要的。


    就我而言,拯救国家充其量只是一种拼凑。如果需要保存持久性数据,只需使用sqlite数据库。Android让它变得很简单。

    像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    import java.util.Date;
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;

    public class dataHelper {

        private static final String DATABASE_NAME ="autoMate.db";
        private static final int DATABASE_VERSION = 1;

        private Context context;
        private SQLiteDatabase db;
        private OpenHelper oh ;

        public dataHelper(Context context) {
            this.context = context;
            this.oh = new OpenHelper(this.context);
            this.db = oh.getWritableDatabase();
        }

        public void close() {
            db.close();
            oh.close();
            db = null;
            oh = null;
            SQLiteDatabase.releaseMemory();
        }


        public void setCode(String codeName, Object codeValue, String codeDataType) {
            Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName +"'", null);
            String cv ="" ;

            if (codeDataType.toLowerCase().trim().equals("long") == true){
                cv = String.valueOf(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("int") == true)
            {
                cv = String.valueOf(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("date") == true)
            {
                cv = String.valueOf(((Date)codeValue).getTime());
            }
            else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
            {
                String.valueOf(codeValue);
            }
            else
            {
                cv = String.valueOf(codeValue);
            }

            if(codeRow.getCount() > 0) //exists-- update
            {
                db.execSQL("update code set codeValue = '" + cv +
                   "' where codeName = '" + codeName +"'");
            }
            else // does not exist, insert
            {
                db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" +
                       "'" + codeName +"'," +
                       "'" + cv +"'," +
                       "'" + codeDataType +"')" );
            }
        }

        public Object getCode(String codeName, Object defaultValue){

            //Check to see if it already exists
            String codeValue ="";
            String codeDataType ="";
            boolean found = false;
            Cursor codeRow  = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName +"'", null);
            if (codeRow.moveToFirst())
            {
                codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue"));
                codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType"));
                found = true;
            }

            if (found == false)
            {
                return defaultValue;
            }
            else if (codeDataType.toLowerCase().trim().equals("long") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return (long)0;
                }
                return Long.parseLong(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("int") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return (int)0;
                }
                return Integer.parseInt(codeValue);
            }
            else if (codeDataType.toLowerCase().trim().equals("date") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return null;
                }
                return new Date(Long.parseLong(codeValue));
            }
            else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
            {
                if (codeValue.equals("") == true)
                {
                    return false;
                }
                return Boolean.parseBoolean(codeValue);
            }
            else
            {
                return (String)codeValue;
            }
        }


        private static class OpenHelper extends SQLiteOpenHelper {

            OpenHelper(Context context) {
                super(context, DATABASE_NAME, null, DATABASE_VERSION);
            }

            @Override
            public void onCreate(SQLiteDatabase db) {
                db.execSQL("CREATE TABLE IF  NOT EXISTS code" +
               "(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");
            }

            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            }
        }
    }

    之后打个简单的电话

    1
    2
    3
    4
    5
    dataHelper dh = new dataHelper(getBaseContext());
    String status = (String) dh.getCode("appState","safetyDisabled");
    Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
    dh.close();
    dh = null;


    我想我找到了答案。让我简单地说一下我做了什么:

    假设我有两个活动,activity1和activity2,我正在从activity1导航到activity2(我在activity2中做了一些工作),然后单击activity1中的按钮,再次返回activity 1。现在,在这个阶段,我想回到活动2,当我上次离开活动2时,我想看到我的活动2处于相同的状态。

    对于上面的场景,我所做的是在清单中我做了一些这样的更改:

    1
    2
    3
    4
    <activity android:name=".activity2"
              android:alwaysRetainTaskState="true"      
              android:launchMode="singleInstance">
    </activity>

    在活动1的按钮点击事件中,我这样做了:

    1
    2
    3
    4
    Intent intent = new Intent();
    intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
    intent.setClassName(this,"com.mainscreen.activity2");
    startActivity(intent);

    在Activity2 on Button Click事件中,我这样做了:

    1
    2
    3
    Intent intent=new Intent();
    intent.setClassName(this,"com.mainscreen.activity1");
    startActivity(intent);

    现在将要发生的是,无论我们在活动2中所做的更改是什么,都不会丢失,并且我们可以以与以前相同的状态查看活动2。

    我相信这就是答案,这对我来说很好。如果我错了就纠正我。


    onSaveInstanceState()表示瞬态数据(在onCreate()中恢复/onRestoreInstanceState()中恢复),onPause()表示持久数据(在onResume()中恢复)。来自Android技术资源:

    onSaveInstanceState() is called by Android if the Activity is being stopped and may be killed before it is resumed! This means it should store any state necessary to re-initialize to the same condition when the Activity is restarted. It is the counterpart to the onCreate() method, and in fact the savedInstanceState Bundle passed in to onCreate() is the same Bundle that you construct as outState in the onSaveInstanceState() method.

    onPause() and onResume() are also complimentary methods. onPause() is always called when the Activity ends, even if we instigated that (with a finish() call for example). We will use this to save the current note back to the database. Good practice is to release any resources that can be released during an onPause() as well, to take up less resources when in the passive state.


    当活动进入后台时,实际的onSaveInstance状态调用

    从文档中引用:"在将活动置于这种后台状态之前,调用方法onSaveInstanceState(Bundle)"


    同时,我做的一般不再有用

    1
    Bundle savedInstanceState & Co

    生命周期对于大多数活动来说太复杂,不必要。

    谷歌声称,它甚至不可靠。

    我的方法是在首选项中立即保存任何更改:

    1
    2
     SharedPreferences p;
     p.edit().put(..).commit()

    在某种程度上,共享引用的工作方式类似于捆绑包。当然,首先,这些值必须从偏好中读取。

    在复杂数据的情况下,您可以使用sqlite而不是使用首选项。

    应用此概念时,活动将继续使用上次保存的状态,而不管它是初始打开状态,其间重新启动,还是由于后堆栈重新打开。


    为了减少样板文件,我使用下面的interfaceclass来读/写Bundle以保存实例状态。

    首先,创建一个用于注释实例变量的接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({
            ElementType.FIELD
    })
    public @interface SaveInstance {

    }

    然后,创建一个类,其中反射将用于将值保存到包中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    import android.app.Activity;
    import android.app.Fragment;
    import android.os.Bundle;
    import android.os.Parcelable;
    import android.util.Log;

    import java.io.Serializable;
    import java.lang.reflect.Field;

    /**
     * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
     * SaveInstance}.
    </p>
     */
    public class Icicle {

        private static final String TAG ="Icicle";

        /**
         * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
         *
         * @param outState
         *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
         *         Fragment#onSaveInstanceState(Bundle)}
         * @param classInstance
         *         The object to access the fields which have the {@link SaveInstance} annotation.
         * @see #load(Bundle, Object)
         */
        public static void save(Bundle outState, Object classInstance) {
            save(outState, classInstance, classInstance.getClass());
        }

        /**
         * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
         *
         * @param outState
         *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
         *         Fragment#onSaveInstanceState(Bundle)}
         * @param classInstance
         *         The object to access the fields which have the {@link SaveInstance} annotation.
         * @param baseClass
         *         Base class, used to get all superclasses of the instance.
         * @see #load(Bundle, Object, Class)
         */
        public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
            if (outState == null) {
                return;
            }
            Class<?> clazz = classInstance.getClass();
            while (baseClass.isAssignableFrom(clazz)) {
                String className = clazz.getName();
                for (Field field : clazz.getDeclaredFields()) {
                    if (field.isAnnotationPresent(SaveInstance.class)) {
                        field.setAccessible(true);
                        String key = className +"#" + field.getName();
                        try {
                            Object value = field.get(classInstance);
                            if (value instanceof Parcelable) {
                                outState.putParcelable(key, (Parcelable) value);
                            } else if (value instanceof Serializable) {
                                outState.putSerializable(key, (Serializable) value);
                            }
                        } catch (Throwable t) {
                            Log.d(TAG,"The field '" + key +"' was not added to the bundle");
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            }
        }

        /**
         * Load all saved fields that have the {@link SaveInstance} annotation.
         *
         * @param savedInstanceState
         *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
         * @param classInstance
         *         The object to access the fields which have the {@link SaveInstance} annotation.
         * @see #save(Bundle, Object)
         */
        public static void load(Bundle savedInstanceState, Object classInstance) {
            load(savedInstanceState, classInstance, classInstance.getClass());
        }

        /**
         * Load all saved fields that have the {@link SaveInstance} annotation.
         *
         * @param savedInstanceState
         *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
         * @param classInstance
         *         The object to access the fields which have the {@link SaveInstance} annotation.
         * @param baseClass
         *         Base class, used to get all superclasses of the instance.
         * @see #save(Bundle, Object, Class)
         */
        public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
            if (savedInstanceState == null) {
                return;
            }
            Class<?> clazz = classInstance.getClass();
            while (baseClass.isAssignableFrom(clazz)) {
                String className = clazz.getName();
                for (Field field : clazz.getDeclaredFields()) {
                    if (field.isAnnotationPresent(SaveInstance.class)) {
                        String key = className +"#" + field.getName();
                        field.setAccessible(true);
                        try {
                            Object fieldVal = savedInstanceState.get(key);
                            if (fieldVal != null) {
                                field.set(classInstance, fieldVal);
                            }
                        } catch (Throwable t) {
                            Log.d(TAG,"The field '" + key +"' was not retrieved from the bundle");
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            }
        }

    }

    示例用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class MainActivity extends Activity {

        @SaveInstance
        private String foo;

        @SaveInstance
        private int bar;

        @SaveInstance
        private Intent baz;

        @SaveInstance
        private boolean qux;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Icicle.load(savedInstanceState, this);
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            Icicle.save(outState, this);
        }

    }

    注:此代码改编自一个名为AndroidAutoWire的库项目,该项目在麻省理工学院许可证下获得许可。


    直接回答原始问题。savedinstanceState为空,因为从未重新创建您的活动。

    只有在以下情况下,才能使用状态包重新创建您的活动:

    • 配置更改,例如更改方向或电话语言,这可能需要创建新的活动实例。
    • 在操作系统破坏了活动之后,您将从后台返回应用程序。

    Android会在内存压力下或长时间处于后台后破坏后台活动。

    在测试Hello World示例时,有几种方法可以离开并返回活动。

    • 当你按下后退按钮,活动就结束了。重新启动应用程序是一个全新的实例。你根本没有从后台恢复。
    • 当您按下主页按钮或使用任务切换器时,活动将进入后台。当导航回应用程序时,只有在必须销毁活动时才会调用OnCreate。

    在大多数情况下,如果你只是按下Home键,然后再次启动应用程序,则不需要重新创建活动。它已经存在于内存中,因此不会调用onCreate()。

    在"设置"->"开发人员选项"下有一个名为"不保留活动"的选项。当它被启用时,Android将总是破坏活动,并在活动被重新建立时重新创建它们。在开发时,这是一个很好的选择,因为它模拟最坏的情况。(一个低内存设备,可随时回收您的活动)。

    其他的答案是很有价值的,因为它们教会了你正确的存储状态的方法,但是我觉得它们并没有真正回答为什么你的代码没有按照你期望的方式工作。


    onSaveInstanceState(bundle)onRestoreInstanceState(bundle)方法仅在旋转屏幕(方向改变)时对数据持久性有用。在应用程序之间切换时,它们甚至不好(因为调用了onSaveInstanceState()方法,但不再调用onCreate(bundle)onRestoreInstanceState(bundle)。要获得更多持久性,请使用共享首选项。阅读这篇文章


    我的问题是,我只需要在应用程序的生命周期(即一次执行,包括在同一个应用程序中启动其他子活动和旋转设备等)中进行持久性。我尝试了上述各种答案的组合,但在所有情况下都没有得到我想要的答案。最后,对我有效的是在创建时获取对savedinstancestate的引用:

    1
    mySavedInstanceState=savedInstanceState;

    当我需要的时候,用它来获取变量的内容,沿着以下几行:

    1
    2
    3
    if (mySavedInstanceState !=null) {
       boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
    }

    如前所述,我使用onSaveInstanceStateonRestoreInstanceState,但我想我也可以或者使用我的方法在变量发生变化时保存变量(例如使用putBoolean)。


    虽然接受的答案是正确的,但是有一种更快更简单的方法可以使用一个名为icepick的库在Android上保存活动状态。Icepick是一个注释处理器,负责处理保存和恢复状态时使用的所有样板代码。

    用冰镐做类似的事情:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class MainActivity extends Activity {
      @State String username; // These will be automatically saved and restored
      @State String password;
      @State int age;

      @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icepick.restoreInstanceState(this, savedInstanceState);
      }

      @Override public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icepick.saveInstanceState(this, outState);
      }
    }

    与此相同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class MainActivity extends Activity {
      String username;
      String password;
      int age;

      @Override
      public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putString("MyString", username);
        savedInstanceState.putString("MyPassword", password);
        savedInstanceState.putInt("MyAge", age);
        /* remember you would need to actually initialize these variables before putting it in the
        Bundle */
      }

      @Override
      public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        username = savedInstanceState.getString("MyString");
        password = savedInstanceState.getString("MyPassword");
        age = savedInstanceState.getInt("MyAge");
      }
    }

    Icepick将与任何使用Bundle保存其状态的对象一起工作。


    创建活动时,会调用onCreate()方法。

    1
    2
    3
    4
       @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }

    savedinstanceState是bundle类的对象,第一次为空,但在重新创建时包含值。要保存活动的状态,必须重写OnSaveInstanceState()。

    1
    2
    3
    4
    5
       @Override
        protected void onSaveInstanceState(Bundle outState) {
          outState.putString("key","Welcome Back")
            super.onSaveInstanceState(outState);       //save state
        }

    将您的值放入"outstate"bundle对象中,如outstate.putstring("key","welcome back"),然后通过调用super保存。当活动将被破坏时,它的状态将保存在bundle对象中,并且可以在onCreate()或onRestoreStanceState()中重新创建后恢复。OnCreate()和OnRestoreInstanceState()中接收的束是相同的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
       @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

              //restore activity's state
             if(savedInstanceState!=null){
              String reStoredString=savedInstanceState.getString("key");
                }
        }

    1
    2
    3
    4
    5
      //restores activity's saved state
     @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
          String restoredMessage=savedInstanceState.getString("key");
        }

    基本上有两种方法来实现这个变更。

  • 使用onSaveInstanceState()onRestoreInstanceState()
  • 在舱单上。
  • 我真的不建议使用第二种方法。因为在我的一次经历中,它导致一半的设备屏幕变黑,同时从纵向旋转到横向,反之亦然。

    使用上面提到的第一种方法,我们可以在方向更改或发生任何配置更改时保留数据。我知道一种在savedinstance状态对象中存储任何类型数据的方法。

    示例:如果要持久化JSON对象,请考虑一个案例。用getter和setter创建一个模型类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class MyModel extends Serializable{
    JSONObject obj;

    setJsonObject(JsonObject obj)
    {
    this.obj=obj;
    }

    JSONObject getJsonObject()
    return this.obj;
    }
    }

    现在,在onCreate和onSaveInstanceState方法中的活动中,执行以下操作。它看起来像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @override
    onCreate(Bundle savedInstaceState){
    MyModel data= (MyModel)savedInstaceState.getSerializable("yourkey")
    JSONObject obj=data.getJsonObject();
    //Here you have retained JSONObject and can use.
    }


    @Override
    protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    //Obj is some json object
    MyModel dataToSave= new MyModel();
    dataToSave.setJsonObject(obj);
    oustate.putSerializable("yourkey",dataToSave);

    }


    这里是Steve Moseley的回答(由toolmakersteve提供)中的一条评论,它将事情放在了透视图中(从整体上看,OnSaveInstanceState与OnPause、East Cost与West Cost Saga)

    @VVK - I partially disagree. Some ways of exiting an app don't trigger
    onSaveInstanceState (oSIS). This limits the usefulness of oSIS. Its
    worth supporting, for minimal OS resources, but if an app wants to
    return the user to the state they were in, no matter how the app was
    exited, it is necessary to use a persistent storage approach instead.
    I use onCreate to check for bundle, and if it is missing, then check
    persistent storage. This centralizes the decision making. I can
    recover from a crash, or back button exit or custom menu item Exit, or
    get back to screen user was on many days later. – ToolmakerSteve Sep
    19 '15 at 10:38


    科特林代码:

    保存:

    1
    2
    3
    4
    5
    6
    7
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState.apply {
            putInt("intKey", 1)
            putString("stringKey","String Value")
            putParcelable("parcelableKey", parcelableObject)
        })
    }

    然后在onCreate()onRestoreInstanceState()

    1
    2
    3
        val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
        val restoredString = savedInstanceState?.getString("stringKey") ?:"default string"
        val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

    如果不希望有选项,请添加默认值


    简单快速解决这个问题的方法是使用Icepick

    首先,在app/build.gradle中设置库

    1
    2
    存储库{maven url"https://clojars.org/repo/<div class="suo-content">[collapse title=""]<ul><li>这对Fragment有用吗?是否保存和还原自定义视图的状态?</li><li>@拉尔夫斯彭是的,它适用于片段和自定义视图。请检查示例代码。我编辑了我的答案。我建议你在github.com/frankiesardo/icepick上找到更多的代码示例。</li><li>我将尝试一下,希望嵌套片段也能得到支持。</li></ul>[/collapse]</div><hr><P>不确定我的解决方案是否被拒绝,但我使用绑定服务来持久化ViewModel状态。无论是将它存储在服务的内存中,还是持久化并从sqlite数据库中检索它,都取决于您的需求。这就是任何风格的服务所做的,它们提供诸如维护应用程序状态和抽象公共业务逻辑等服务。</P><P>由于移动设备固有的内存和处理限制,我以类似于网页的方式处理Android视图。页面不维护状态,它只是一个表示层组件,其唯一目的是显示应用程序状态并接受用户输入。Web应用程序体系结构的最新趋势采用了古老的模型、视图、控制器(MVC)模式,其中页面是视图,域数据是模型,控制器位于Web服务后面。同样的模式也可以在Android中使用,因为视图是…视图中,模型是您的域数据,控制器是作为绑定到Android的服务实现的。每当您希望视图与控制器交互时,在开始/继续时绑定到它,在停止/暂停时取消绑定。</P><P>这种方法为您提供了实施关注点分离设计原则的额外好处,因为您的所有应用程序业务逻辑都可以转移到您的服务中,从而减少了跨多个视图的重复逻辑,并允许视图实施另一个重要的设计原则,即单一责任。</P><hr><P>要获取存储在<wyn>onCreate()</wyn>中的活动状态数据,首先必须通过重写<wyn>SaveInstanceState(Bundle savedInstanceState)</wyn>方法将数据保存在savedinstancestate中。</P><P>当调用activity destroy <wyn>SaveInstanceState(Bundle savedInstanceState)</wyn>方法并在那里保存要保存的数据时。当活动重新启动时,在<wyn>onCreate()</wyn>中也会得到相同的结果。(savedinstancestate不会为空,因为在活动被破坏之前,您已经在其中保存了一些数据)</P><p><center>[wp_ad_camp_5]</center></p><hr><P>现在android提供了保存状态的视图模型,您应该尝试使用它而不是saveInstanceState。</P><div class="suo-content">[collapse title=""]<ul><li>这不是真的。来自文档:"与保存的实例状态不同,视图模型在系统启动的进程死亡期间被销毁。这就是为什么您应该将viewModel对象与onSaveInstanceState()(或其他一些磁盘持久性)结合使用,将标识符存储在savedinstanceState中,以帮助视图模型在系统死亡后重新加载数据。"</li><li>只是在后台更改权限时遇到了这个问题。</li></ul>[/collapse]</div><hr><P>我有个更好的主意。保存数据时最好不要再次调用oncreate。当方向改变时,可以将其从活动中禁用。</P><P>在您的舱单中:</P>[cc]<activity android:name=".MainActivity"
            android:configChanges="orientation|screenSize">

    保存什么和不保存什么?

    有没有想过,当方向改变时,EditText中的文本为什么会自动保存?好吧,这个答案是给你的。

    当一个活动的实例被破坏,系统重新创建一个新实例(例如,配置更改)时。它尝试使用一组保存的旧活动状态(实例状态)数据重新创建它。

    实例状态是存储在Bundle对象中的键值对的集合。

    By default System saves the View objects in the Bundle for example.

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

    如果需要另一个变量保存为实例状态的一部分,则应重写onSavedInstanceState(Bundle savedinstaneState)方法。

    例如,int currentScore在游戏活动中

    保存数据时有关OnSavedInstanceState(Bundle SaveDinstanceState)的详细信息

    1
    2
    3
    4
    5
    6
    7
    8
    @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);
    }

    So by mistake if you forget to call
    super.onSaveInstanceState(savedInstanceState);the default behavior
    will not work ie Text in EditText will not save.

    恢复活动状态时选择哪一个?

    1
     onCreate(Bundle savedInstanceState)

    1
    onRestoreInstanceState(Bundle savedInstanceState)

    这两种方法都得到相同的bundle对象,所以在哪里编写恢复逻辑并不重要。唯一的区别是,在onCreate(Bundle savedInstanceState)方法中,在后一种情况下不需要空检查时,必须进行空检查。其他答案已经有代码片段了。你可以参考它们。

    有关OnRestoreStanceState(bundle savedinstanestate)的详细信息

    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        // Always call the superclass so it can restore the view hierarchy
        super.onRestoreInstanceState(savedInstanceState);

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

    Always call super.onRestoreInstanceState(savedInstanceState); so that System restore the View hierarchy by default

    奖金

    只有当用户打算返回活动时,系统才会调用onSaveInstanceState(Bundle savedInstanceState)。例如,你正在使用应用程序X,突然你接到一个电话。您移动到调用方应用程序并返回到应用程序X。在这种情况下,将调用onSaveInstanceState(Bundle savedInstanceState)方法。

    但如果用户按下后退按钮,就要考虑这个问题。假设用户不打算返回活动,因此在这种情况下,系统不会调用onSaveInstanceState(Bundle savedInstanceState)。关键是在保存数据时,您应该考虑所有的场景。

    相关链接:

    默认行为演示Android官方文档。