我一直在研究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. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
if (savedInstanceState == null) {
mTextView.setText("Welcome to HelloAndroid!");
} else {
mTextView.setText("Welcome back.");
}
setContentView(mTextView);
}
}
我认为这对于最简单的情况就足够了,但无论我如何离开应用程序,它总是会响应第一条消息。
我确信解决方案就像覆盖 onPause
或类似的东西一样简单,但我已经在文档中戳了 30 分钟左右,但没有发现任何明显的东西。
您需要覆盖 onSaveInstanceState(Bundle savedInstanceState)
并将要更改的应用程序状态值写入 Bundle
参数,如下所示:
@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");
}
或者来自一个片段。
@Override
public 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");
}
您通常会使用这种技术来存储应用程序的实例值(选择、未保存的文本等)。
savedInstanceState
仅用于保存与 Activity 的当前实例关联的状态,例如当前导航或选择信息,以便 Android 销毁并重新创建 Activity 时,它可以像以前一样恢复。请参阅 onCreate
和 onSaveInstanceState
的文档
对于更长寿的状态,请考虑使用 SQLite 数据库、文件或首选项。请参阅Saving Persistent State。
请注意,根据the documentation on Activity,将 onSaveInstanceState
和 onRestoreInstanceState
用于持久数据是不安全的时间>。
该文件指出(在“活动生命周期”部分):
请注意,将持久数据保存在 onPause() 而不是 onSaveInstanceState(Bundle) 中很重要,因为后者不是生命周期回调的一部分,因此不会在其文档中描述的所有情况下都被调用。
换句话说,将持久数据的保存/恢复代码放在 onPause()
和 onResume()
中!
如需进一步说明,请参阅 onSaveInstanceState()
文档:
这个方法在一个活动可能被杀死之前被调用,这样当它在未来某个时间回来时它可以恢复它的状态。例如,如果活动 B 在活动 A 之前启动,并且在某个时刻活动 A 被杀死以回收资源,活动 A 将有机会通过此方法保存其用户界面的当前状态,以便当用户返回时对于activity A,可以通过onCreate(Bundle) 或onRestoreInstanceState(Bundle) 恢复用户界面的状态。
我的同事写了一篇文章,解释了 Android 设备上的应用程序状态,包括对 Activity 生命周期和状态信息、如何存储状态信息以及保存到状态 Bundle
和 SharedPreferences
的说明。 Take a look at it here。
本文介绍了三种方法:
使用实例状态包存储应用程序生命周期(即临时)的局部变量/UI 控制数据
[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 控制数据
[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();
}
这是 Android 开发的经典“陷阱”。这里有两个问题:
有一个微妙的 Android 框架错误,它使开发过程中的应用程序堆栈管理变得非常复杂,至少在旧版本上是这样(不完全确定是否/何时/如何修复它)。我将在下面讨论这个错误。
管理此问题的“正常”或预期方式本身相当复杂,因为 onPause/onResume 和 onSaveInstanceState/onRestoreInstanceState 的双重性
浏览所有这些线程,我怀疑开发人员大部分时间都在同时讨论这两个不同的问题......因此所有关于“这对我不起作用”的混乱和报告。
首先,澄清“预期”行为:onSaveInstance 和 onRestoreInstance 是脆弱的,仅适用于瞬态。预期用途(据我所知)是在手机旋转(方向改变)时处理活动娱乐。换句话说,预期用途是当您的 Activity 在逻辑上仍处于“顶部”时,但仍必须由系统重新实例化。保存的 Bundle 不会保留在进程/内存/GC 之外,因此如果您的活动进入后台,您就不能真正依赖它。是的,也许你的 Activity 的内存会在它的后台之旅中幸存下来并逃脱 GC,但这并不可靠(也不是可预测的)。
因此,如果您的应用程序的“启动”之间存在有意义的“用户进度”或状态,则指导是使用 onPause 和 onResume。您必须自己选择并准备一个持久存储。
但是 - 有一个非常令人困惑的错误使这一切变得复杂。详情在这里:
从 Eclipse 启动应用程序时,Activity 堆栈在首次运行期间行为不正确 (#36907463)
市场/浏览器应用程序安装程序允许第二个实例关闭应用程序 (#36911210)
基本上,如果您的应用程序使用 SingleTask 标志启动,然后您从主屏幕或启动器菜单启动它,那么后续调用将创建一个新任务......您将有效地拥有应用程序的两个不同实例居住在同一个堆栈中......这很快就会变得非常奇怪。这似乎发生在您在开发期间(即从 Eclipse 或 IntelliJ)启动应用程序时,因此开发人员经常遇到这种情况。但也通过一些应用商店更新机制(因此它也会影响您的用户)。
在我意识到我的主要问题是这个错误,而不是预期的框架行为之前,我在这些线程中挣扎了几个小时。一个很棒的文章和解决方法(更新:见下文)似乎来自用户@kaciula 在这个答案中:
2013 年 6 月更新:几个月后,我终于找到了“正确”的解决方案。您不需要自己管理任何有状态的startedApp 标志。您可以从框架中检测到这一点并适当地保释。我在 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;
}
}
onSaveInstanceState
在系统需要内存并终止应用程序时调用。当用户刚刚关闭应用程序时不会调用它。所以我认为应用程序状态也应该保存在onPause
中。
它应该保存到一些持久性存储中,例如 Preferences
或 SQLite。
这两种方法都是有用且有效的,并且都最适合不同的场景:
用户终止应用程序并在以后重新打开它,但应用程序需要从最后一个会话重新加载数据——这需要一种持久存储方法,例如使用 SQLite。用户切换应用程序,然后返回到原来的位置,并希望从他们离开的地方继续 - 在 onSaveInstanceState() 和 onRestoreInstanceState() 中保存和恢复捆绑数据(例如应用程序状态数据)通常就足够了。
如果您以持久方式保存状态数据,则可以在 onResume()
或 onCreate()
中重新加载它(或者实际上在任何生命周期调用中)。这可能是也可能不是期望的行为。如果您将它存储在 InstanceState
中的捆绑包中,那么它是临时的,仅适用于存储数据以供在同一用户“会话”中使用(我松散地使用术语会话),但不适用于“会话”之间。
并不是说一种方法比另一种更好,就像所有事情一样,重要的是了解您需要什么行为并选择最合适的方法。
就我而言,保存状态充其量只是一个kludge。如果您需要保存持久数据,只需使用 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;
我想我找到了答案。让我用简单的话告诉我我做了什么:
假设我有两个活动,活动 1 和活动 2,我正在从活动 1 导航到活动 2(我在活动 2 中做了一些工作),然后通过单击活动 1 中的按钮再次返回活动 1。现在在这个阶段,我想回到活动 2,我想看到我的活动 2 与我上次离开活动 2 时的状态相同。
对于上述情况,我所做的是在清单中我进行了一些更改,如下所示:
<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的状态与我们之前离开的状态相同。
我相信这就是答案,这对我来说很好。如果我错了,请纠正我。
onSaveInstanceState()
用于临时数据(在 onCreate()
/onRestoreInstanceState()
中恢复),onPause()
用于持久数据(在 onResume()
中恢复)。来自Android技术资源:
如果 Activity 正在停止并且可能在它恢复之前被杀死,Android 会调用 onSaveInstanceState()!这意味着它应该存储在重新启动 Activity 时重新初始化到相同条件所需的任何状态。它是 onCreate() 方法的对应物,实际上传递给 onCreate() 的 savedInstanceState Bundle 与您在 onSaveInstanceState() 方法中构造为 outState 的 Bundle 相同。 onPause() 和 onResume() 也是互补的方法。 onPause() 总是在 Activity 结束时被调用,即使是我们发起的(例如使用 finish() 调用)。我们将使用它来将当前笔记保存回数据库。好的做法是释放任何可以在 onPause() 期间释放的资源,以便在处于被动状态时占用更少的资源。
为了帮助减少样板文件,我使用以下 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 下获得许可。
同时我一般不再使用
Bundle savedInstanceState & Co
对于大多数活动来说,生命周期过于复杂且没有必要。
谷歌自称,它甚至不可靠。
我的方法是立即在首选项中保存任何更改:
SharedPreferences p;
p.edit().put(..).commit()
在某些方面,SharedPreferences 的工作方式与 Bundle 类似。当然,首先必须从偏好中读取这些值。
在复杂数据的情况下,您可以使用 SQLite 而不是使用首选项。
应用此概念时,活动将继续使用上次保存的状态,无论它是初始打开并在其间重新启动,还是由于返回堆栈而重新打开。
直接回答原问题。 savedInstancestate 为空,因为您的 Activity 永远不会被重新创建。
只有在以下情况下,您的 Activity 才会使用状态包重新创建:
配置更改,例如更改方向或电话语言,可能需要创建新的活动实例。
操作系统销毁活动后,您从后台返回应用程序。
Android 将在内存压力下或在后台长时间处于后台后销毁后台活动。
在测试你的 hello world 示例时,有几种方法可以离开和返回 Activity。
当您按下后退按钮时,活动完成。重新启动应用程序是一个全新的实例。你根本没有从后台恢复。
当您按下主页按钮或使用任务切换器时,Activity 将进入后台。当导航回应用程序时,只有在必须销毁 Activity 时才会调用 onCreate。
在大多数情况下,如果您只是按下主页然后再次启动应用程序,则不需要重新创建活动。它已经存在于内存中,因此不会调用 onCreate()。
设置 -> 开发人员选项下有一个名为“不保留活动”的选项。启用后,Android 将始终销毁活动并在后台运行时重新创建它们。这是在开发时保持启用状态的绝佳选择,因为它模拟了最坏的情况。 (低内存设备一直在回收您的活动)。
其他答案很有价值,因为它们教您存储状态的正确方法,但我觉得他们并没有真正回答为什么您的代码没有按您预期的方式工作。
onSaveInstanceState(bundle)
和 onRestoreInstanceState(bundle)
方法仅在旋转屏幕(方向更改)时对数据持久性很有用。
它们甚至在应用程序之间切换时也不好用(因为调用了 onSaveInstanceState()
方法,但 onCreate(bundle)
和onRestoreInstanceState(bundle)
不会再次调用。
要获得更多持久性,请使用共享首选项。read this article
onCreate
和 onRestoreInstanceState
没有被调用,因为当您切换应用程序时 Activity
根本没有被破坏,因此无需恢复任何内容。 Android 调用 onSaveInstanceState
以防 Activity 稍后被销毁(旋转屏幕时会发生 100% 的情况,因为整个设备配置已更改,必须从头开始重新创建 Activity)。
我的问题是我只需要在应用程序生命周期内保持持久性(即一次执行,包括在同一个应用程序中启动其他子活动和旋转设备等)。我尝试了上述答案的各种组合,但在所有情况下都没有得到我想要的。最后,对我有用的是在 onCreate 期间获得对 savedInstanceState 的引用:
mySavedInstanceState=savedInstanceState;
并在我需要时使用它来获取我的变量的内容,如下所示:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
我按照上面的建议使用 onSaveInstanceState
和 onRestoreInstanceState
,但我想我也可以或替代地使用我的方法在变量更改时保存变量(例如使用 putBoolean
)
尽管接受的答案是正确的,但使用名为 Icepick 的库可以更快、更轻松地在 Android 上保存 Activity 状态。 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
保存其状态的对象。
当一个活动被创建时,它的 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");
}
基本上有两种方法可以实现此更改。
使用 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);
}
这是史蒂夫·莫斯利 (Steve Moseley) 的回答(由 ToolmakerSteve 撰写)的评论,该评论将事情放在了正确的位置(在整个 onSaveInstanceState 与 onPause,东部成本与西部成本传奇中)
@VVK - 我部分不同意。退出应用程序的某些方式不会触发 onSaveInstanceState (oSIS)。这限制了 oSIS 的实用性。对于最少的操作系统资源,它值得支持,但是如果一个应用程序想要将用户返回到他们所处的状态,那么无论应用程序是如何退出的,都需要使用持久存储方法来代替。我使用 onCreate 检查捆绑包,如果丢失,则检查持久存储。这集中了决策制定。我可以从崩溃中恢复,或者返回按钮退出或自定义菜单项退出,或者在很多天后回到用户所在的屏幕。 – ToolmakerSteve 2015 年 9 月 19 日在 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 int
val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable
如果您不想拥有 Optionals,请添加默认值
要获取存储在 onCreate()
中的活动状态数据,首先您必须通过覆盖 SaveInstanceState(Bundle savedInstanceState)
方法将数据保存在 savedInstanceState 中。
当调用 Activity destroy SaveInstanceState(Bundle savedInstanceState)
方法并在那里保存要保存的数据时。当活动重新启动时,您会在 onCreate()
中得到相同的结果。(savedInstanceState 不会为空,因为您在活动被破坏之前已经在其中保存了一些数据)
科特林
您必须覆盖 onSaveInstanceState
和 onRestoreInstanceState
来存储和检索您想要持久化的变量
生命周期图
https://i.stack.imgur.com/Ts2F1.png
存储变量
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
}
不确定我的解决方案是否不受欢迎,但我使用绑定服务来保持 ViewModel 状态。是否将其存储在服务的内存中或持久化并从 SQLite 数据库中检索它取决于您的要求。这就是任何风格的服务所做的,它们提供诸如维护应用程序状态和抽象通用业务逻辑之类的服务。
由于移动设备固有的内存和处理限制,我以与网页类似的方式处理 Android 视图。页面不维护状态,它纯粹是一个表示层组件,其唯一目的是呈现应用程序状态并接受用户输入。 Web 应用程序架构的最新趋势采用了古老的模型、视图、控制器 (MVC) 模式,其中页面是视图,域数据是模型,控制器位于 Web 服务后面。相同的模式可以在 Android 中使用,View 是,嗯...... View,模型是您的域数据,而 Controller 是作为 Android 绑定服务实现的。每当您希望视图与控制器交互时,请在启动/恢复时绑定到它,并在停止/暂停时取消绑定。
这种方法为您提供了执行关注点分离设计原则的额外好处,因为您所有的应用程序业务逻辑都可以移动到您的服务中,这减少了跨多个视图的重复逻辑并允许视图执行另一个重要的设计原则,即单一职责。
使用 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 还可以为自定义 Views 生成实例状态码:
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
}
现在 Android 提供了 ViewModels 来保存状态,您应该尝试使用它而不是 saveInstanceState。
有一种方法可以让 Android 在不实现任何方法的情况下保存状态。只需将此行添加到您的 Manifest in Activity 声明中:
android:configChanges="orientation|screenSize"
它应该如下所示:
<activity
android:name=".activities.MyActivity"
android:configChanges="orientation|screenSize">
</activity>
Here 您可以找到有关此属性的更多信息。
建议让 Android 为您处理,而不是手动处理。
什么该保存,什么不该保存?
有没有想过为什么在改变方向时会自动保存 EditText
中的文本?好吧,这个答案是给你的。
当 Activity 的一个实例被销毁并且系统重新创建一个新实例时(例如,配置更改)。它尝试使用一组保存的旧活动状态(实例状态)数据重新创建它。
实例状态是存储在 Bundle
对象中的 键值 对的集合。
例如,默认情况下系统将视图对象保存在 Bundle 中。
EditText 中的文本
在 ListView 等中滚动位置。
如果您需要将另一个变量保存为实例状态的一部分,您应该使用 OVERRIDE onSavedInstanceState(Bundle savedinstaneState)
方法。
例如,GameActivity 中的 int currentScore
保存数据时有关 onSavedInstanceState(Bundle savedinstaneState) 的更多详细信息
@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 中的文本将不会保存。
选择哪个来恢复 Activity 状态?
onCreate(Bundle savedInstanceState)
或者
onRestoreInstanceState(Bundle savedInstanceState)
两种方法都获得相同的 Bundle 对象,因此在哪里编写恢复逻辑并不重要。唯一的区别是,在 onCreate(Bundle savedInstanceState)
方法中,您必须进行空检查,而在后一种情况下不需要。其他答案已经有代码片段。你可以参考他们。
有关 onRestoreInstanceState(Bundle savedinstaneState) 的更多详细信息
@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);以便系统默认恢复视图层次结构
奖金
仅当用户打算返回 Activity 时,系统才会调用 onSaveInstanceState(Bundle savedInstanceState)
。例如,您正在使用 App X,突然接到一个电话。您移动到调用者应用程序并返回到应用程序 X。在这种情况下,将调用 onSaveInstanceState(Bundle savedInstanceState)
方法。
但是如果用户按下后退按钮,请考虑这一点。假定用户不打算返回 Activity,因此在这种情况下 onSaveInstanceState(Bundle savedInstanceState)
不会被系统调用。要点是您在保存数据时应该考虑所有情况。
相关链接:
Demo on default behavior
Android Official Documentation。
取而代之的是,您应该使用 ViewModel,它将保留数据直到活动生命周期。
现在在视图模型中做 2 种方式是有意义的。如果您想将第一个保存为已保存的实例:您可以像这样在视图模型中添加状态参数 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);
}
}
不定期副业成功案例分享
onSaveInstanceState
几乎无用,除了屏幕方向更改的情况。在几乎所有其他情况下,您永远不能依赖它,并且需要手动将您的 UI 状态保存在其他地方。或者通过覆盖 BACK 按钮行为来防止您的应用程序被杀死。我不明白他们为什么一开始就这样实现它。完全不直观。除了这种非常特殊的方法,你不能让系统给你的那个 Bundle 来保存东西。View
。来自onSaveInstanceState
文档:“默认实现通过在具有 ID 的层次结构中的每个视图上调用onSaveInstanceState()
并保存当前聚焦视图的 ID,为您处理大部分 UI 实例状态(所有这些都由onRestoreInstanceState(Bundle)
的默认实现恢复)"