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

我一直在研究Android SDK平台,如何保存应用程序的状态有点不清楚。所以给出了“你好,Android”示例的这个小重新工具:

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. */@Overridepublic 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分钟左右,还没有发现任何明显的东西。

885992 次浏览

savedInstanceState仅用于保存与活动的当前实例关联的状态,例如当前导航或选择信息,以便如果Android销毁并重新创建活动,它可以像以前一样回来。请参阅#1#2的留档

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

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

@Overridepublic 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(),然后您将从活动中提取值,如下所示:

@Overridepublic 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");}

或者从一个片段。

@Overridepublic void onViewStateRestored(@Nullable Bundle savedInstanceState) {super.onViewStateRestored(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");}

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

onSaveInstanceState是在系统需要内存并杀死应用程序时调用的。当用户刚刚关闭应用程序时,它不会被调用。所以我认为应用程序状态也应该保存在onPause中。

它应该保存到一些持久存储,如Preferences或SQLite。

当活动进入后台时,实际onSaveInstanceState()被调用。

引用文档:“此方法在活动可能被杀死之前调用,以便在将来某个时候返回时可以恢复其状态。”来源

请注意,根据活动的留档,使用onSaveInstanceStateonRestoreInstanceState用于持久数据<强>不安全的。

该文件指出(在“活动生命周期”部分):

请注意,重要的是要保存onPause()中的持久数据的onSaveInstanceState(Bundle)因为后者不属于生命周期回调,所以不会在所描述的任何情况下留档

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

为了进一步澄清,这里是onSaveInstanceState()留档:

此方法在活动被杀死之前调用,以便当它被杀死时在未来的某个时候回来,它可以恢复它的状态。因为例如,如果活动B在活动A之前启动,并且在某些情况下点活动A被杀死以回收资源,活动A将有有机会保存其用户交互界面的当前状态方法,以便当用户返回活动A时,用户交互界面可以通过onCreate(Bundle)onRestoreInstanceState(Bundle).

我的同事写了一篇文章解释Android设备上的应用程序状态,包括解释活动生命周期和状态信息、如何存储状态信息以及保存到状态BundleSharedPreferences在这里看一下

本文涵盖三种方法:

使用实例状态包为应用程序生命周期(即暂时)存储局部变量/UI控制数据

[Code sample – Store state in state bundle]@Overridepublic 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控制数据

[Code sample – store state in SharedPreferences]@Overrideprotected void onPause(){super.onPause();
// Store values between instances hereSharedPreferences preferences = getPreferences(MODE_PRIVATE);SharedPreferences.Editor editor = preferences.edit();  // Put the values from the UIEditText 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 storeeditor.putString(“Email”, strEmail); // value to storeeditor.putBoolean(“TandC”, blnTandC); // value to store// Commit to storageeditor.commit();}

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

[Code sample – store object instance]private cMyClassType moInstanceOfAClass; // Store the instance of an object@Overridepublic Object onRetainNonConfigurationInstance(){if (moInstanceOfAClass != null) // Check that the object existsreturn(moInstanceOfAClass);return super.onRetainNonConfigurationInstance();}

就我而言,保存状态充其量是一个杂乱无章的东西。如果您需要保存持久数据,只需使用SQLite数据库。Android让它SOOO变得简单。

像这样的东西:

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 existsString 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);}
@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("CREATE TABLE IF  NOT EXISTS code" +"(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");}
@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}}}

之后的一个简单电话

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

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

  1. 用户终止应用程序并在稍后重新打开它,但应用程序需要重新加载上次会话中的数据-这需要使用持久存储方法,例如使用SQLite。
  2. 用户切换应用程序,然后回到原来的应用程序,并希望从他们离开的地方开始——在onSaveInstanceState()onRestoreInstanceState()中保存和恢复捆绑数据(例如应用程序状态数据)通常就足够了。

如果您以持久方式保存状态数据,它可以在onResume()onCreate()中重新加载(或者实际上在任何生命周期调用中)。这可能是也可能不是期望的行为。如果您将其存储在InstanceState中的捆绑包中,那么它是暂时的,只适合存储数据以供在同一个用户“会话”中使用(我松散地使用术语会话),而不是在“会话”之间使用。

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

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

如果活动正在停止并可能在恢复之前被杀死,则Android会调用关于保存实例状态()!这意味着它应该存储在活动重新启动时重新初始化为相同条件所需的任何状态。它是onCreate()方法的对应物,实际上传递给onCreate()的SavedInstance State Bundle与您在onSaveInstance State()方法中作为outState构造的Bundle相同。

onPost()回调暂停onResume()个人简历也是互补的方法。onP暂停()总是在活动结束时被调用,即使我们煽动了这一点(例如通过完成()调用)。我们将使用它来将当前注释保存回数据库。良好的做法是释放任何可以在onP暂停()期间释放的资源,以便在被动状态下占用更少的资源。

我想我找到了答案。让我用简单的话告诉你我做了什么:

假设我有两个活动,活动1和活动2,我正在从活动1导航到活动2(我在活动2中做了一些工作),并通过点击活动1中的一个按钮再次回到活动1。现在在这个阶段,我想回到活动2,我想看到我的活动2处于与上次离开活动2时相同的条件。

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

<activity android:name=".activity2"android:alwaysRetainTaskState="true"android:launchMode="singleInstance"></activity>

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

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

在actiity2按钮点击事件我这样做:

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

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

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

与此同时我做一般没有更多的使用

Bundle savedInstanceState & Co

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

谷歌自己说,它甚至不可靠。

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

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

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

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

当应用这个概念时,活动只是继续使用最后保存的状态,不管它是在中间重新启动的初始打开还是由于返回堆栈而重新打开。

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

这是Android开发的一个经典“陷阱”。这里有两个问题:

  • 有一个微妙的Android框架bug它在开发过程中极大地复杂化了应用程序堆栈管理,至少在遗留版本上是这样(不完全确定它是否/何时/如何修复)。
  • 管理此问题的“正常”或预期方式本身就相当复杂,因为它具有二元性onP暂停/onResume和onSaveInstance State/onRecovery oreInstance State

浏览所有这些线程,我怀疑开发人员大部分时间都在同时讨论这两个不同的问题……因此所有的混乱和报告“这对我不起作用”。

首先,澄清“预期”行为:onSaveInstance和onRecovery oreInstance是脆弱的,仅用于瞬态。预期用途(据我所知)是在手机旋转(方向改变)时处理活动娱乐。换句话说,预期用途是当你的活动仍然在逻辑上处于“顶部”,但仍然必须由系统重新实例化。保存的Bundle没有持久化在进程/内存/GC之外,所以如果你的活动进入后台,你不能真正依赖它。是的,也许你的活动的内存将在后台之旅中幸存下来并逃脱GC,但这是不可靠的(也不可预测)。

因此,如果你有一个场景,其中有有意义的“用户进度”或状态应该在应用程序的“启动”之间被持久化,那么指南是使用onP暂停和onResume。你必须自己选择并准备一个持久存储。

但是-有一个非常令人困惑的bug使所有这些复杂化。详细信息在这里:

基本上,如果你的应用程序使用Single任务标志启动,然后你从主屏幕或启动器菜单启动它,那么随后的调用将创建一个新任务……你实际上会有你的应用程序的两个不同实例居住在同一个堆栈中……这变得非常奇怪非常快。这似乎发生在你在开发期间启动应用程序时(即从EclipseIntelliJ),所以开发人员经常遇到这种情况。而且还通过一些应用商店更新机制(所以它也会影响你的用户)。

我与这些线程斗争了几个小时,才意识到我的主要问题是这个bug,而不是预期的框架行为。

Home按键行为

2013年6月更新:几个月后,我终于找到了“正确”的解决方案。你不需要自己管理任何有状态的startedApp标志。你可以从框架中检测到这一点并适当地保释。我在LauncherActivity.on开始时使用了这个创建:

if (!isTaskRoot()) {Intent intent = getIntent();String action = intent.getAction();if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {finish();return;}}

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

mySavedInstanceState=savedInstanceState;

并在需要时使用它来获取变量的内容,如下所示:

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

我按照上面的建议使用onSaveInstanceStateonRestoreInstanceState,但我想我也可以或替代地使用我的方法在变量更改时保存变量(例如使用putBoolean

要直接回答最初的问题。SavedInstanchouse为空,因为您的活动永远不会被重新创建。

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

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

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

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

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

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

在设置->开发人员选项下有一个名为“不要保留活动”的选项。启用后,Android将始终销毁活动并在它们返回时重新创建它们。这是在开发时保持启用状态的绝佳选择,因为它模拟了最坏的情况。(低内存设备一直在回收您的活动)。

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

为了帮助减少样板,我使用以下interfaceclass读取/写入Bundle以保存实例状态。


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

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 {
}

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

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();}}
}

示例用法:

public class MainActivity extends Activity {
@SaveInstanceprivate String foo;
@SaveInstanceprivate int bar;
@SaveInstanceprivate Intent baz;
@SaveInstanceprivate boolean qux;
@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Icicle.load(savedInstanceState, this);}
@Overridepublic void onSaveInstanceState(Bundle outState) {super.onSaveInstanceState(outState);Icicle.save(outState, this);}
}

备注:此代码改编自名为安卓自动上线的库项目,该项目根据mit许可证获得许可。

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

  1. 使用onSaveInstanceState()onRestoreInstanceState()
  2. 在清单android:configChanges="orientation|screenSize"

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

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

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

class MyModel extends Serializable{JSONObject obj;
setJsonObject(JsonObject obj){this.obj=obj;}
JSONObject getJsonObject()return this.obj;}}

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

@overrideonCreate(Bundle savedInstaceState){MyModel data= (MyModel)savedInstaceState.getSerializable("yourkey")JSONObject obj=data.getJsonObject();//Here you have retained JSONObject and can use.}

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

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

用冰锥做这样的事情:

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);}}

和这样做是一样的:

class MainActivity extends Activity {String username;String password;int age;
@Overridepublic 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 theBundle */}
@Overridepublic void onRestoreInstanceState(Bundle savedInstanceState) {super.onRestoreInstanceState(savedInstanceState);username = savedInstanceState.getString("MyString");password = savedInstanceState.getString("MyPassword");age = savedInstanceState.getInt("MyAge");}}

Icepick将适用于任何使用Bundle保存其状态的对象。

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

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

repositories {maven {url "https://clojars.org/repo/"}}dependencies {compile 'frankiesardo:icepick:3.2.0'provided 'frankiesardo:icepick-processor:3.2.0'}

现在,让我们检查下面的示例如何在活动中保存状态

public class ExampleActivity extends Activity {@State String username; // This will be automatically saved and restored
@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);}}

它适用于活动,片段或任何需要在Bundle上序列化其状态的对象(例如迫击炮的ViewPresenter)

Icepick还可以为自定义视图生成实例状态代码:

class CustomView extends View {@State int selectedPosition; // This will be automatically saved and restored
@Override public Parcelable onSaveInstanceState() {return Icepick.saveInstanceState(this, super.onSaveInstanceState());}
@Override public void onRestoreInstanceState(Parcelable state) {super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));}
// You can put the calls to Icepick into a BaseCustomView and inherit from it// All Views extending this CustomView automatically have state saved/restored}

不确定我的解决方案是否受到反对,但我使用绑定服务来持久化ViewModel状态。是将其存储在服务的内存中,还是持久化并从SQLite数据库中检索它取决于你的需求。这就是任何风格的服务所做的,它们提供服务,例如维护应用程序状态和抽象公共业务逻辑。

由于移动设备固有的内存和处理限制,我以类似于网页的方式对待Android视图。页面不维护状态,它纯粹是一个表示层组件,其唯一目的是呈现应用程序状态并接受用户输入。网络应用程序架构的最新趋势采用了古老的模型、视图、控制器(MVC)模式,其中页面是视图,域数据是模型,控制器位于Web服务后面。同样的模式可以在Android中使用,视图是……视图,模型是你的域数据,控制器被实现为Android绑定服务。每当您希望视图与控制器交互时,请在开始/恢复时绑定到它,并在停止/暂停时取消绑定。

这种方法为您提供了强制执行关注点分离设计原则的额外好处,因为您的所有应用程序业务逻辑都可以移动到您的服务中,从而减少了跨多个视图的重复逻辑,并允许视图强制执行另一个重要的设计原则,即单一职责。

要获取存储在onCreate()中的活动状态数据,首先您必须通过覆盖SaveInstanceState(Bundle savedInstanceState)方法将数据保存在SavedInstance State中。

当活动销毁SaveInstanceState(Bundle savedInstanceState)方法被调用并在那里保存您要保存的数据时。当活动重新启动时,您在onCreate()中得到相同的结果。(SavedInstance State不会为空,因为您在活动被销毁之前已经在其中保存了一些数据)

当一个活动被创建时,它的onCreate()方法被调用。

   @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);}

SavedInstance State是Bundle类的对象,第一次为null,但在重新创建时它包含值。要保存活动的状态,您必须覆盖onSaveInstance State()。

   @Overrideprotected void onSaveInstanceState(Bundle outState) {outState.putString("key","Welcome Back")super.onSaveInstanceState(outState);       //save state}

将您的值放在“outState”Bundle对象中,例如outState.putString(“key”,“欢迎回来”)并通过调用超级保存。当活动将被销毁时,它的状态将保存在Bundle对象中,并且可以在onCreate()或onReporeInstance State()中重新创建后恢复。在onCreate()和onReporeInstance State()中收到的Bundle是相同的。

   @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
//restore activity's stateif(savedInstanceState!=null){String reStoredString=savedInstanceState.getString("key");}}

  //restores activity's saved state@Overrideprotected void onRestoreInstanceState(Bundle savedInstanceState) {String restoredMessage=savedInstanceState.getString("key");}

这是来自Steve Moseley的答案(由工具制造商史蒂夫)的评论,它将事情置于透视图中(在整个onSaveInstance State与onPance中,东成本与西成本传奇)

@VVK-我部分不同意。退出应用程序的某些方法不会触发这限制了oSIS的有用性值得支持,对于最小的操作系统资源,但如果应用程序想要将用户返回到他们所处的状态,无论应用程序如何退出后,有必要改为使用持久存储方法。我使用onCreate来检查bundle,如果缺少,则检查持久存储。这集中了决策。我可以从崩溃中恢复,或返回按钮退出或自定义菜单项退出,或几天后回到屏幕用户。-工具制造商Steve Sep19分15秒10:38

静态编程语言代码:

保存:

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

然后在onCreate()onRestoreInstanceState()

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

如果您不想要选项,请添加默认值

现在Android为保存状态提供了运营模式,您应该尝试使用它而不是SaveInstance State。

什么该救,什么不该救?

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

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

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

默认情况下,系统将View对象保存在Bundle中。

  • 文本EditText
  • 滚动位置ListView,等等。

如果您需要另一个变量保存为实例状态的一部分,您应该使用推翻onSavedInstanceState(Bundle savedinstaneState)方法。

例如,int currentScore in a GameActivity

有关保存数据时onSavedInstaneState(Bundle SavedinstaneState)的更多详细信息

@Overridepublic void onSaveInstanceState(Bundle savedInstanceState) {// Save the user's current game statesavedInstanceState.putInt(STATE_SCORE, mCurrentScore);
// Always call the superclass so it can save the view hierarchy statesuper.onSaveInstanceState(savedInstanceState);}

如果你忘记打电话super.onSaveInstanceState(savedInstanceState);默认行为将无法工作,即EditText中的文本将无法保存。

恢复活动状态选择哪个?

 onCreate(Bundle savedInstanceState)

onRestoreInstanceState(Bundle savedInstanceState)

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

有关onRecovery oreInstance State(Bundle SavedinstaneState)的更多详细信息

@Overridepublic void onRestoreInstanceState(Bundle savedInstanceState) {// Always call the superclass so it can restore the view hierarchysuper.onRestoreInstanceState(savedInstanceState);
// Restore state members from the saved instancemCurrentScore = savedInstanceState.getInt(STATE_SCORE);}

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

奖金

系统仅在用户打算返回活动时调用onSaveInstanceState(Bundle savedInstanceState)。例如,你正在使用App X,突然接到一个呼叫。你移动到调用者应用程序并回到应用程序X。在这种情况下,onSaveInstanceState(Bundle savedInstanceState)方法将被调用。

但是如果用户按下后退按钮,请考虑这一点。假设用户不打算回到活动,因此在这种情况下,系统不会调用onSaveInstanceState(Bundle savedInstanceState)。问题是你应该在保存数据时考虑所有的情况。

相关链接:

默认行为演示
安卓官方文档.

静态编程语言

您必须重写onSaveInstanceStateonRestoreInstanceState才能存储和检索您想要持久化的变量

生命周期图

存储变量

public override fun onSaveInstanceState(savedInstanceState: Bundle) {super.onSaveInstanceState(savedInstanceState)
// prepare variables heresavedInstanceState.putInt("kInt", 10)savedInstanceState.putBoolean("kBool", true)savedInstanceState.putDouble("kDouble", 4.5)savedInstanceState.putString("kString", "Hello Kotlin")}

检索变量

public override fun onRestoreInstanceState(savedInstanceState: Bundle) {super.onRestoreInstanceState(savedInstanceState)
val myInt = savedInstanceState.getInt("kInt")val myBoolean = savedInstanceState.getBoolean("kBool")val myDouble = savedInstanceState.getDouble("kDouble")val myString = savedInstanceState.getString("kString")// use variables here}

您可以使用Live DataView Model For Lifecycle Handel fromJetPack.

https://developer.android.com/topic/libraries/architecture/livedata

相反,您应该使用ViewModel,它将保留数据直到活动生命周期。

有一种方法可以让Android在不实现任何方法的情况下保存状态。只需将这行添加到您的活动中的清单声明中:

android:configChanges="orientation|screenSize"

它应该看起来像这样:

<activityandroid:name=".activities.MyActivity"android:configChanges="orientation|screenSize"></activity>

这里您可以找到有关此属性的更多信息。

建议让Android为您处理此问题,而不是手动处理。

现在在视图模型中执行两种方法是有意义的。如果您想将第一个保存为已保存的实例:您可以像这样在视图模型中添加状态参数https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java

或者您可以将变量或对象保存在视图模型中,在这种情况下,视图模型将保持生命周期,直到活动被销毁。

public class HelloAndroidViewModel extends ViewModel {public Booelan firstInit = false;
public HelloAndroidViewModel() {firstInit = false;}...}
public class HelloAndroid extends Activity {
private TextView mTextView = null;HelloAndroidViewModel viewModel = ViewModelProviders.of(this).get(HelloAndroidViewModel.class);/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
mTextView = new TextView(this);
//Because even if the state is deleted, the data in the viewmodel will be kept because the activity does not destroyif(!viewModel.firstInit){viewModel.firstInit = truemTextView.setText("Welcome to HelloAndroid!");}else{mTextView.setText("Welcome back.");}
setContentView(mTextView);}}

静态编程语言解决方案:对于onSaveInstanceState中的自定义类保存,您可以将类转换为JSON字符串并使用Gson转换恢复它,对于单个String, Double, Int, Long值保存和恢复如下。以下示例是FragmentActivity

活动安排:

对于将数据放入saveInstanceState

override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)
//for custom class-----val gson = Gson()val json = gson.toJson(your_custom_class)outState.putString("CUSTOM_CLASS", json)
//for single value------outState.putString("MyString", stringValue)outState.putBoolean("MyBoolean", true)outState.putDouble("myDouble", doubleValue)outState.putInt("MyInt", intValue)}

恢复数据:

 override fun onRestoreInstanceState(savedInstanceState: Bundle) {super.onRestoreInstanceState(savedInstanceState)
//for custom class restoreval json = savedInstanceState?.getString("CUSTOM_CLASS")if (!json!!.isEmpty()) {val gson = Gson()testBundle = gson.fromJson(json, Session::class.java)}
//for single value restore
val myBoolean: Boolean = savedInstanceState?.getBoolean("MyBoolean")val myDouble: Double = savedInstanceState?.getDouble("myDouble")val myInt: Int = savedInstanceState?.getInt("MyInt")val myString: String = savedInstanceState?.getString("MyString")}

您也可以在活动onCreate上恢复它。

对于片段:

将class放在saveInstanceState中:

 override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)val gson = Gson()val json = gson.toJson(customClass)outState.putString("CUSTOM_CLASS", json)}

恢复数据:

 override fun onActivityCreated(savedInstanceState: Bundle?) {super.onActivityCreated(savedInstanceState)
//for custom class restoreif (savedInstanceState != null) {val json = savedInstanceState.getString("CUSTOM_CLASS")if (!json!!.isEmpty()) {val gson = Gson()val customClass: CustomClass = gson.fromJson(json, CustomClass::class.java)}}
// for single value restoreval myBoolean: Boolean = savedInstanceState.getBoolean("MyBoolean")val myDouble: Double = savedInstanceState.getDouble("myDouble")val myInt: Int = savedInstanceState.getInt("MyInt")val myString: String = savedInstanceState.getString("MyString")}

2020年,我们有一些变化:

如果你想让你的Activity在进程被杀死并重新启动后恢复它的状态,你可能想使用“保存状态”功能。以前,你需要覆盖Activity中的两个方法:onSaveInstanceStateonRestoreInstanceState。你也可以在onCreate方法中访问恢复的状态。类似地,在Fragment中,你有onSaveInstanceState方法可用(恢复的状态在onCreateonCreateViewonActivityCreated方法中可用)。

AndroidX SavedState 1.0.0开始,它是androidx活动androidx片段的依赖项,您可以访问SavedStateRegistry。您可以从活动/片段中获取SavedStateRegistry,然后注册您的SavedStateProvider

class MyActivity : AppCompatActivity() {
companion object {private const val MY_SAVED_STATE_KEY = "MY_SAVED_STATE_KEY "private const val SOME_VALUE_KEY = "SOME_VALUE_KEY "}    
private lateinit var someValue: Stringprivate val savedStateProvider = SavedStateRegistry.SavedStateProvider {Bundle().apply {putString(SOME_VALUE_KEY, someValue)}}  
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)savedStateRegistry.registerSavedStateProvider(MY_SAVED_STATE_KEY, savedStateProvider)someValue = savedStateRegistry.consumeRestoredStateForKey(MY_SAVED_STATE_KEY)?.getString(SOME_VALUE_KEY) ?: ""}  
}

如您所见,SavedStateRegistry强制您对数据使用密钥。这可以防止您的数据被附加到同一Activity/Fragment的另一个SavedStateProvider损坏。您还可以将SavedStateProvider提取到另一个类,通过使用您想要的任何抽象使其与您的数据一起使用,并以这种方式在您的应用程序中实现干净的保存状态行为。

使用Android ViewModel和SavedStateHandle持久化可序列化数据

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());binding.setViewModel(new ViewModelProvider(this).get(ViewModel.class));binding.setLifecycleOwner(this);setContentView(binding.getRoot());}
public static class ViewModel extends AndroidViewModel {
//This field SURVIVE the background process reclaim/killing & the configuration changepublic final SavedStateHandle savedStateHandle;
//This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration changepublic final MutableLiveData<String> inputText2 = new MutableLiveData<>();

public ViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {super(application);this.savedStateHandle = savedStateHandle;}}}

在布局文件

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variablename="viewModel"type="com.xxx.viewmodelsavedstatetest.MainActivity.ViewModel" /></data>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity">

<EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:autofillHints=""android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />
<SeekBarandroid:layout_width="match_parent"android:layout_height="wrap_content"android:max="100"android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />
<EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />
<SeekBarandroid:layout_width="match_parent"android:layout_height="wrap_content"android:max="100"android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />
<EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:hint="This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change"android:text='@={viewModel.inputText2}' />
</LinearLayout></layout>

测试:

1. start the test activity2. press home key to go home3. adb shell kill <the test activity process>4. open recent app list and restart the test activity

现在,您可以使用实时数据和生命周期感知组件

https://developer.android.com/topic/libraries/architecture/lifecycle