我一直在研究Android SDK平台,有点不清楚如何保存应用程序的状态。因此,考虑到“Hello,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. */
@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分钟左右而没有找到任何明显的东西。
答案 0 :(得分:2420)
您需要覆盖onSaveInstanceState(Bundle savedInstanceState)
并将要更改的应用程序状态值写入Bundle
参数,如下所示:
@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()
,然后您可以在其中提取值这样:
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
double myDouble = savedInstanceState.getDouble("myDouble");
int myInt = savedInstanceState.getInt("MyInt");
String myString = savedInstanceState.getString("MyString");
}
您通常会使用此技术来存储应用程序的实例值(选择,未保存的文本等)。
答案 1 :(得分:406)
savedInstanceState
仅用于保存与活动的当前实例关联的状态,例如当前导航或选择信息,因此,如果Android销毁并重新创建活动,则可以像以前一样返回。请参阅onCreate
和onSaveInstanceState
对于更长寿的状态,请考虑使用SQLite数据库,文件或首选项。请参阅Saving Persistent State。
答案 2 :(得分:392)
请注意, NOT 使用onSaveInstanceState
和onRestoreInstanceState
用于持久数据是安全的有关http://developer.android.com/reference/android/app/Activity.html中的活动状态的文档。
该文件陈述(在“活动生命周期”部分):
请注意保存很重要 相反,
onPause()
中的持久数据onSaveInstanceState(Bundle)
的 因为后者不属于 生命周期回调,所以不会 在所描述的每种情况下都会被呼 在其文档中。
换句话说,将保存/恢复代码放在onPause()
和onResume()
中的持久数据中!
编辑:有关进一步说明,请参阅onSaveInstanceState()
文档:
在激活某个活动之前调用此方法,以便在此时执行 在未来的某个时候,它可以恢复它的状态。对于 例如,如果活动B在活动A前面,在某些活动中发布 点活动A被杀死以回收资源,活动A将具有 有机会通过它保存其用户界面的当前状态 方法使用户返回活动A时的状态 用户界面可以通过
onCreate(Bundle)
或onRestoreInstanceState(Bundle)
。
答案 3 :(得分:187)
我的同事写了一篇文章解释Android设备上的应用状态,包括对活动生命周期和状态信息的解释,如何存储状态信息,以及保存到状态Bundle
和SharedPreferences
以及take a look at here
本文介绍了三种方法:
[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);
}
[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();
}
[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();
}
答案 4 :(得分:137)
这是Android开发的经典“问题”。这里有两个问题:
浏览所有这些主题,我怀疑开发人员大多数时间同时讨论这两个不同的问题......因此所有混淆和报告“这对我不起作用”。
首先,澄清'预期'行为:onSaveInstance和onRestoreInstance是脆弱的,仅适用于瞬态。预期用途(afaict)用于在手机旋转(方向改变)时处理活动娱乐。换句话说,预期的用法是当您的Activity仍然在逻辑上“在顶部”时,但仍然必须由系统重新实例化。保存的Bundle不会在进程/ memory / gc之外保留,因此如果您的活动进入后台,则无法真正依赖此。是的,也许你的Activity的记忆将在它的背景之旅中存活并逃脱GC,但这不可靠(也不可预测)。
因此,如果您的应用程序“启动”之间存在有意义的“用户进度”或状态,则指导是使用onPause和onResume。您必须自己选择并准备一个持久性商店。
但是 - 有一个非常令人困惑的错误使所有这一切变得复杂。详情如下:
http://code.google.com/p/android/issues/detail?id=2373
http://code.google.com/p/android/issues/detail?id=5277
基本上,如果您的应用程序是使用SingleTask标志启动的,然后再从主屏幕或启动器菜单启动它,那么后续调用将创建一个新任务...您将有效地拥有两个不同的实例您的应用程序驻留在相同的堆栈中......这非常非常快。当您在开发期间(即从Eclipse或Intellij)启动应用程序时,似乎会发生这种情况,因此开发人员会遇到这种情况。但也通过一些应用程序商店更新机制(因此它也会影响您的用户)。
在我意识到我的主要问题是这个错误,而不是预期的框架行为之前,我在这些线程中奋战了几个小时。一个很好的写法和解决方法(更新:见下文)似乎来自用户@kaciula在这个答案中:
2013年6月更新:几个月后,我终于找到了“正确”的解决方案。您不需要自己管理任何有状态的startupApp标志,您可以从框架中检测到这一点并适当地保释。我在LauncherActivity.onCreate的开头附近使用它:
if (!isTaskRoot()) {
Intent intent = getIntent();
String action = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
finish();
return;
}
}
答案 5 :(得分:77)
onSaveInstanceState
。用户刚关闭应用程序时不会调用它。所以我认为应用程序状态也应该保存在onPause
中。它应该保存到某些持久存储中,如Preferences
或Sqlite
答案 6 :(得分:66)
这两种方法都是有用且有效的,两者最适合不同的场景:
onSaveInstanceState()
中保存和恢复捆绑数据(例如应用程序状态数据),onRestoreInstanceState()
通常足够的。如果以持久方式保存状态数据,则可以在onResume()
或onCreate()
(或实际在任何生命周期调用中)重新加载。这可能是也可能不是期望的行为。如果将它存储在InstanceState
的捆绑包中,那么它是瞬态的,仅适用于存储用于同一用户“会话”的数据(我使用术语会话松散),但不适用于“会话”之间。
并不是说一种方法比另一种方法更好,就像所有方法一样,了解您需要的行为并选择最合适的方法非常重要。
答案 7 :(得分:60)
就我而言,拯救国家充其量只是一个障碍。如果需要保存持久数据,只需使用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 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) {
}
}
}
之后的简单电话
dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;
答案 8 :(得分:55)
我想我找到了答案。让我用简单的话说出我的所作所为:
假设我有两个活动,activity1和activity2,我从activity1导航到activity2(我已在activity2中完成了一些工作),再次通过单击activity1中的按钮返回活动1。现在在这个阶段我想回到activity2,我想在上次离开activity2时看到我的activity2处于相同的状态。
对于上面的场景,我所做的是在清单中我做了一些改变:
<activity android:name=".activity2"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance">
</activity>
在按钮点击事件的activity1中我做了这样的事情:
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);
在按钮点击事件的activity2中,我这样做了:
Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);
现在将会发生的事情是,我们在activity2中所做的任何更改都不会丢失,我们可以查看与之前离开时相同状态的activity2。
我相信这是答案,这对我来说很好。如果我错了,请纠正我。
答案 9 :(得分:40)
onSaveInstanceState()
表示暂时数据(在onCreate()
/ onRestoreInstanceState()
中恢复),onPause()
表示持久数据(在onResume()
中恢复)。
来自Android技术资源:
如果Activity被停止并且可能在恢复之前被杀死,则Android会调用onSaveInstanceState()!这意味着它应该存储在重新启动Activity时重新初始化为相同条件所需的任何状态。它与onCreate()方法相对应,实际上传入onCreate()的savedInstanceState Bundle与你在onSaveInstanceState()方法中构造为outState的Bundle相同。
onPause()和 onResume()也是免费方法。当Activity结束时总是调用onPause(),即使我们发起了这个(例如使用finish()调用)。我们将使用它将当前注释保存回数据库。好的做法是释放在onPause()期间可以释放的任何资源,以便在处于被动状态时占用更少的资源。
答案 10 :(得分:37)
当活动转到后台时,真正的onSaveInstance
状态callen
来自文档的引用: “在将活动置于这样的背景状态”
之前调用方法onSaveInstanceState(Bundle)
答案 11 :(得分:32)
为了帮助减少样板,我使用以下interface
和class
来读取/写入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 {
@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的库项目,该项目已在MIT license下获得许可。
答案 12 :(得分:31)
与此同时,我一般不再使用
Bundle savedInstanceState & Co
大多数活动的生命周期过于复杂且没有必要。
谷歌表示,它甚至不可靠。
我的方法是立即在首选项中保存所有更改:
SharedPreferences p;
p.edit().put(..).commit()
在某些方面,SharedPreferences的工作方式与Bundles类似。 当然,首先必须从偏好中读取这些值。
对于复杂数据,您可以使用SQLite而不是使用首选项。
应用此概念时,活动将继续使用上次保存的状态,无论是初始打开还是重新启动,或者由于后端堆栈而重新打开。
答案 13 :(得分:30)
直接回答原始问题。 savedInstancestate为null,因为永远不会重新创建Activity。
您的活动只会在以下情况下使用州捆绑包重新创建:
Android会在内存压力下或长时间处于后台后销毁后台活动。
在测试你的hello world示例时,有几种方法可以离开并返回Activity。
在大多数情况下,如果您只是按下主页然后再次启动应用程序,则无需重新创建活动。它已经存在于内存中,因此不会调用onCreate()。
“设置”下的选项 - &gt;开发者选项称为“不要保持活动”。当它启用时,Android将始终销毁活动并在它们背景时重新创建它们。这是在开发时保持启用的一个很好的选项,因为它模拟了最坏的情况。 (低内存设备一直在回收您的活动。)
其他答案很有价值,因为他们教会您存储状态的正确方法,但我觉得他们没有真正解答为什么您的代码没有按预期方式工作。
答案 14 :(得分:26)
onSaveInstanceState(bundle)
和onRestoreInstanceState(bundle)
方法仅在旋转屏幕(方向更改)时对数据持久性很有用。
在应用程序之间切换时它们甚至都不好(因为onSaveInstanceState()
方法被调用,但onCreate(bundle)
和onRestoreInstanceState(bundle)
不再被调用。
对于更多持久性,请使用共享首选项read this article
答案 15 :(得分:18)
我的问题是我只在应用程序生命周期内需要持久性(即单个执行包括在同一个应用程序中启动其他子活动并旋转设备等)。我尝试了上述答案的各种组合,但在所有情况下都没有得到我想要的东西。最后,对我有用的是在onCreate:
期间获取对savedInstanceState的引用mySavedInstanceState=savedInstanceState;
并在我需要时使用它来获取变量的内容,如下所示:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
我使用上面建议的onSaveInstanceState
和onRestoreInstanceState
,但我想我也可以或者使用我的方法在变量发生变化时保存变量(例如使用putBoolean
)
答案 16 :(得分:17)
虽然接受的答案是正确的,但是使用名为Icepick的库在Android上保存活动状态的方法更快更容易。 Icepick是一个注释处理器,负责处理为您保存和恢复状态时使用的所有样板代码。
用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;
@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
保存其状态的对象。
答案 17 :(得分:14)
创建活动时,会调用onCreate()方法。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
savedInstanceState是Bundle类的一个对象,它第一次为null,但在重新创建时包含值。要保存Activity的状态,您必须覆盖onSaveInstanceState()。
@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()或onRestoreInstanceState()中重新创建后恢复。在onCreate()和onRestoreInstanceState()中收到的Bundle是相同的。
@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");
}
}
或
//restores activity's saved state
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
String restoredMessage=savedInstanceState.getString("key");
}
答案 18 :(得分:13)
实现此更改基本上有两种方法。
onSaveInstanceState()
和onRestoreInstanceState()
。android:configChanges="orientation|screenSize"
。我真的不建议使用第二种方法。因为在我的一次经验中,它导致设备屏幕的一半在从纵向旋转到横向时变黑,反之亦然。
使用上面提到的第一种方法,我们可以在方向更改或任何配置更改发生时保留数据。 我知道一种方法,你可以在savedInstance状态对象中存储任何类型的数据。
示例:如果要保留Json对象,请考虑一个案例。 使用getter和setter创建一个模型类。
class MyModel extends Serializable{
JSONObject obj;
setJsonObject(JsonObject obj)
{
this.obj=obj;
}
JSONObject getJsonObject()
return this.obj;
}
}
现在,在onCreate和onSaveInstanceState方法的活动中,执行以下操作。它看起来像这样:
@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);
}
答案 19 :(得分:9)
以下是来自史蒂夫·莫斯利的答案(由 ToolmakerSteve )发表的评论,这些评论将事情置于透视中(在整个onSaveInstanceState vs onPause,east cost vs west cost saga )
@VVK - 我部分不同意。某些退出应用程序的方法不会触发 onSaveInstanceState(oSIS)。这限制了oSIS的有用性。它的 值得支持,最小的操作系统资源,但如果一个应用程序想要 无论应用程序如何,都将用户返回到他们所处的状态 退出时,有必要使用持久存储方法。 我使用onCreate检查包,如果缺少,请检查 持久存储。这使决策集中化。我可以 从崩溃或后退按钮退出或自定义菜单项退出恢复,或 多天后回到屏幕用户。 - ToolmakerSteve Sep 19 '15 at 10:38
答案 20 :(得分:8)
Kotlin代码:
保存:
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 int
val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable
如果您不想拥有Optionals
,请添加默认值答案 21 :(得分:6)
要获取存储在onCreate()
中的活动状态数据,首先必须通过覆盖SaveInstanceState(Bundle savedInstanceState)
方法将数据保存在savedInstanceState中。
当调用活动销毁SaveInstanceState(Bundle savedInstanceState)
方法时,您将保存要保存的数据。当活动重新启动时,你在onCreate()
中变得相同。(savedInstanceState不会为null,因为你在活动被销毁之前已经保存了一些数据)
答案 22 :(得分:5)
使用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'
}
现在,让我们查看下面的示例如何在Activity
中保存状态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上序列化其状态的对象(例如迫击炮的ViewPresenters)
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
}
答案 23 :(得分:5)
不确定我的解决方案是否不赞成,但我使用绑定服务来保持ViewModel状态。是将它存储在服务的内存中还是持久存储并从SQLite数据库中检索它取决于您的要求。这就是任何风格的服务,它们提供诸如维护应用程序状态和抽象通用业务逻辑之类的服务。
由于移动设备固有的内存和处理限制,我以类似于网页的方式处理Android视图。页面不维护状态,它纯粹是一个表示层组件,其唯一目的是呈现应用程序状态并接受用户输入。 Web应用程序体系结构的最新趋势采用了古老的模型,视图,控制器(MVC)模式,其中页面是视图,域数据是模型,控制器位于Web服务后面。在Android中可以使用相同的模式,View是...,View,模型是您的域数据,Controller是作为Android绑定服务实现的。每当您希望视图与控制器交互时,在开始/恢复时绑定它并在停止/暂停时取消绑定。
这种方法为您提供了强制执行Separation of Concern设计原则的额外好处,因为您可以将所有应用程序业务逻辑移动到您的服务中,从而减少多个视图中的重复逻辑,并允许视图执行另一个重要的设计原则,单一责任。
答案 24 :(得分:2)
现在,Android提供了ViewModels用于保存状态,您应该尝试使用它而不是saveInstanceState。
答案 25 :(得分:2)
有一种方法可以使Android在不执行任何方法的情况下保存状态。只需将此行添加到您在“活动”声明中的清单中即可:
android:configChanges="orientation|screenSize"
它应该像这样:
<activity
android:name=".activities.MyActivity"
android:configChanges="orientation|screenSize">
</activity>
Here,您可以找到有关此属性的更多信息。
与手动处理相比,建议让Android为您处理。
答案 26 :(得分:1)
使用 Android ViewModel 和 SavedStateHandle 来持久化可序列化数据
public class MainActivity extends AppCompatActivity {
@Override
protected 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 change
public final SavedStateHandle savedStateHandle;
//This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change
public 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>
<variable
name="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">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints=""
android:hint="This field SURVIVE the background process reclaim/killing & the configuration change"
android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="This field SURVIVE the background process reclaim/killing & the configuration change"
android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />
<EditText
android: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 activity
2. press home key to go home
3. adb shell kill <the test activity process>
4. open recent app list and restart the test activity
答案 27 :(得分:0)
人们曾经想过,EditText
中的文本为什么会在方向改变时自动保存?好吧,这个答案是给你的。
当“活动”实例被销毁并且系统重新创建新实例时(例如,配置更改)。它尝试使用一组旧的活动状态(实例状态)保存的数据来重新创建它。
实例状态是存储在Bundle
对象中的键值对的集合。
例如,默认情况下,系统将View对象保存在Bundle中。
EditText
中的文字ListView
中的位置,等等。如果需要将另一个变量保存为实例状态的一部分,则应覆盖 onSavedInstanceState(Bundle savedinstaneState)
方法。
例如,int currentScore
在GameActivity中
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
因此,如果您忘了打电话,那是错误的
super.onSaveInstanceState(savedInstanceState);
的默认行为 将不起作用,即EditText中的文本将无法保存。
onCreate(Bundle savedInstanceState)
OR
onRestoreInstanceState(Bundle savedInstanceState)
两个方法都获得相同的Bundle对象,因此在哪里编写恢复逻辑并不重要。唯一的区别是在onCreate(Bundle savedInstanceState)
方法中,您将必须进行空检查,而在后一种情况下则不需要。其他答案已经编写了代码片段。您可以参考他们。
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from the saved instance
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}
始终调用
super.onRestoreInstanceState(savedInstanceState);
,以便系统默认还原视图层次结构
onSaveInstanceState(Bundle savedInstanceState)
仅在用户打算返回到活动时才由系统调用。例如,您使用App X,突然接到电话。您移至呼叫者应用,然后回到应用X。在这种情况下,将调用onSaveInstanceState(Bundle savedInstanceState)
方法。
但是,如果用户按下“后退”按钮,请考虑一下。假定用户不打算返回活动,因此在这种情况下,系统不会调用onSaveInstanceState(Bundle savedInstanceState)
。
重要的是您在保存数据时应考虑所有方案。
答案 28 :(得分:0)
您必须重写onSaveInstanceState
和onRestoreInstanceState
才能存储和检索要持久化的变量
public override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
// prepare variables here
savedInstanceState.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
}
答案 29 :(得分:0)
您可以对Live Data
中的L View Model
使用ifecycle Handel
和JetPack
。看到这个参考:
https://developer.android.com/topic/libraries/architecture/livedata
答案 30 :(得分:0)
相反,您应该使用ViewModel,它将保留数据直到活动生命周期。
答案 31 :(得分:0)
现在在视图模型中执行两种方法是有意义的。 如果要将第一个另存为已保存的实例: 您可以像这样在视图模型中添加状态参数 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. */
@Override
public 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 destroy
if(!viewModel.firstInit){
viewModel.firstInit = true
mTextView.setText("Welcome to HelloAndroid!");
}else{
mTextView.setText("Welcome back.");
}
setContentView(mTextView);
}
}
答案 32 :(得分:0)
您需要覆盖onSaveInstanceState(Bundle savedInstanceState)并将要更改的应用程序状态值写入Bundle参数
答案 33 :(得分:0)
科特琳解决方案:
对于保存在onSaveInstanceState
中的自定义类,您可以将您的类转换为JSON
字符串,并通过Gson
转换对其进行还原,并且对于单个String, Double, Int, Long
值,可以按以下方式保存和恢复。以下示例适用于Fragment
和Activity
:
活动:
对于放入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 restore
val 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
上将其还原。
对于片段:
对于saveInstanceState
中的put类:
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 restore
if (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 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")
}
答案 34 :(得分:0)
在2020年,我们会有一些变化:
如果您希望Activity
在进程被终止并重新启动后恢复其状态,则可能要使用“保存状态”功能。以前,您需要在Activity
中覆盖两个方法:onSaveInstanceState
和onRestoreInstanceState
。您还可以通过onCreate
方法访问恢复的状态。同样,在Fragment
中,您可以使用onSaveInstanceState
方法(并且恢复状态在onCreate
,onCreateView
和onActivityCreated
方法中可用)。>
从AndroidX SavedState 1.0.0和AndroidX Activity的依赖项AndroidX Fragment开始,您可以访问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: String
private 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
强制您将密钥用于数据。这样可以防止您的数据被附加到同一SavedStateProvider
的另一个Activity/Fragment
破坏。此外,您还可以将SavedStateProvider
提取到另一个类,以使用任何抽象方法使其与您的数据一起使用想要并以这种方式在您的应用程序中实现干净的保存状态行为。
答案 35 :(得分:-2)
我有一个更好的主意。这样最好保存您的数据,而无需再次调用onCreate。您可以在方向发生变化时将其禁用。
在您的清单中
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
编辑: 当Android由于内存不足而终止进程时,此解决方案将无法正常工作。如果您需要确保保留数据,则在这种情况下需要使用SavedInstanceState,否则我建议使用这种方式。酷且易于使用!