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); } } |
我认为这对于最简单的情况来说已经足够了,但是无论我如何离开应用程序,它总是用第一条消息来响应。
我相信这个解决方案和重写
您需要重写
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("名称-值对")映射的方法,它将被传递到
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"); } |
您通常会使用此技术存储应用程序的实例值(选择、未保存的文本等)。
对于更长寿命的状态,请考虑使用sqlite数据库、文件或首选项。请参见保存持久状态。
请注意,根据http://developer.android.com/reference/android/app/activity.html中有关活动状态的文档,使用
文档说明(在"活动生命周期"部分中):
Note that it is important to save
persistent data inonPause() instead
ofonSaveInstanceState(Bundle)
because the later is not part of the
lifecycle callbacks, so will not be
called in every situation as described
in its documentation.
换句话说,在
编辑:为了进一步澄清,这里是
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 viaonCreate(Bundle) or
onRestoreInstanceState(Bundle) .
我的同事写了一篇文章解释了Android设备上的应用程序状态,其中包括关于活动生命周期和状态信息的解释,如何存储状态信息,以及保存到状态
本文涵盖三种方法:
使用实例状态包为应用程序生存期(即临时)存储本地变量/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; } } |
当系统需要内存并终止应用程序时,调用
这两种方法都是有用和有效的,并且都最适合不同的场景:
如果以持久的方式保存状态数据,则可以在
这并不是说一种方法比另一种更好,就像所有的方法一样,了解您需要什么样的行为并选择最合适的方法是非常重要的。
就我而言,拯救国家充其量只是一种拼凑。如果需要保存持久性数据,只需使用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() 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.
当活动进入后台时,实际的
从文档中引用:"在将活动置于这种后台状态之前,调用方法
同时,我做的一般不再有用
1 | Bundle savedInstanceState & Co |
生命周期对于大多数活动来说太复杂,不必要。
谷歌声称,它甚至不可靠。
我的方法是在首选项中立即保存任何更改:
1 2 | SharedPreferences p; p.edit().put(..).commit() |
在某种程度上,共享引用的工作方式类似于捆绑包。当然,首先,这些值必须从偏好中读取。
在复杂数据的情况下,您可以使用sqlite而不是使用首选项。
应用此概念时,活动将继续使用上次保存的状态,而不管它是初始打开状态,其间重新启动,还是由于后堆栈重新打开。
为了减少样板文件,我使用下面的
首先,创建一个用于注释实例变量的接口:
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将总是破坏活动,并在活动被重新建立时重新创建它们。在开发时,这是一个很好的选择,因为它模拟最坏的情况。(一个低内存设备,可随时回收您的活动)。
其他的答案是很有价值的,因为它们教会了你正确的存储状态的方法,但是我觉得它们并没有真正回答为什么你的代码没有按照你期望的方式工作。
我的问题是,我只需要在应用程序的生命周期(即一次执行,包括在同一个应用程序中启动其他子活动和旋转设备等)中进行持久性。我尝试了上述各种答案的组合,但在所有情况下都没有得到我想要的答案。最后,对我有效的是在创建时获取对savedinstancestate的引用:
1 | mySavedInstanceState=savedInstanceState; |
当我需要的时候,用它来获取变量的内容,沿着以下几行:
1 2 3 | if (mySavedInstanceState !=null) { boolean myVariable = mySavedInstanceState.getBoolean("MyVariable"); } |
如前所述,我使用
虽然接受的答案是正确的,但是有一种更快更简单的方法可以使用一个名为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将与任何使用
创建活动时,会调用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"); } |
基本上有两种方法来实现这个变更。
我真的不建议使用第二种方法。因为在我的一次经历中,它导致一半的设备屏幕变黑,同时从纵向旋转到横向,反之亦然。
使用上面提到的第一种方法,我们可以在方向更改或发生任何配置更改时保留数据。我知道一种在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) }) } |
然后在
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
首先,在
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"> |
保存什么和不保存什么?
有没有想过,当方向改变时,
当一个活动的实例被破坏,系统重新创建一个新实例(例如,配置更改)时。它尝试使用一组保存的旧活动状态(实例状态)数据重新创建它。
实例状态是存储在
By default System saves the View objects in the Bundle for example.
EditText 中的文本ListView 等中的滚动位置。
如果需要另一个变量保存为实例状态的一部分,则应重写
例如,
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对象,所以在哪里编写恢复逻辑并不重要。唯一的区别是,在
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
奖金
只有当用户打算返回活动时,系统才会调用
但如果用户按下后退按钮,就要考虑这个问题。假设用户不打算返回活动,因此在这种情况下,系统不会调用
默认行为演示Android官方文档。