Android 如何使用保存实例状态保存活动状态?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/151777/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to save an activity state using save instance state?
提问by Bernard
I've been working on the Android SDK platform, and it is a little unclear how to save an application's state. So given this minor re-tooling of the 'Hello, Android' example:
我一直在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);
}
}
I thought it would be enough for the simplest case, but it always responds with the first message, no matter how I navigate away from the app.
我认为这对于最简单的情况就足够了,但无论我如何离开应用程序,它总是以第一条消息作为响应。
I'm sure the solution is as simple as overriding onPause
or something like that, but I've been poking away in the documentation for 30 minutes or so and haven't found anything obvious.
我确信解决方案就像覆盖onPause
或类似的东西一样简单,但我已经在文档中摸索了 30 分钟左右,但没有发现任何明显的东西。
采纳答案by Reto Meier
You need to override onSaveInstanceState(Bundle savedInstanceState)
and write the application state values you want to change to the Bundle
parameter like this:
您需要覆盖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.
}
The Bundle is essentially a way of storing a NVP ("Name-Value Pair") map, and it will get passed in to onCreate()
and also onRestoreInstanceState()
where you would then extract the values from activity like this:
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");
}
Or from a fragment.
或者从一个片段。
@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");
}
You would usually use this technique to store instance values for your application (selections, unsaved text, etc.).
您通常会使用此技术来存储应用程序的实例值(选择、未保存的文本等)。
回答by Dave L.
The savedInstanceState
is only for saving state associated with a current instance of an Activity, for example current navigation or selection info, so that if Android destroys and recreates an Activity, it can come back as it was before. See the documentation for onCreate
and onSaveInstanceState
该savedInstanceState
只保存与活动的当前实例相关联的状态,例如当前导航或选择信息,因此,如果Android的破坏和重新创建一个活动,它可以回来,因为它以前。请参阅文档onCreate
和onSaveInstanceState
For more long lived state, consider using a SQLite database, a file, or preferences. See Saving Persistent State.
对于更长久的状态,请考虑使用 SQLite 数据库、文件或首选项。请参阅保存持久状态。
回答by Steve Moseley
Note that it is NOTsafe to use onSaveInstanceState
and onRestoreInstanceState
for persistent data, according to the documentation on Activity states in http://developer.android.com/reference/android/app/Activity.html.
请注意,这是不使用安全onSaveInstanceState
和onRestoreInstanceState
持久性数据,根据在活动状态的文档http://developer.android.com/reference/android/app/Activity.html。
The document states (in the 'Activity Lifecycle' section):
该文件指出(在“活动生命周期”部分):
Note that it is important to save persistent data in
onPause()
instead ofonSaveInstanceState(Bundle)
because the later is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.
请注意,重要的是将持久数据保存在
onPause()
而不是onSaveInstanceState(Bundle)
因为后者不是生命周期回调的一部分,因此不会在其文档中描述的每种情况下都被调用。
In other words, put your save/restore code for persistent data in onPause()
and onResume()
!
换句话说,将持久数据的保存/恢复代码放入onPause()
和onResume()
!
EDIT: For further clarification, here's the onSaveInstanceState()
documentation:
编辑:为了进一步说明,这里是onSaveInstanceState()
文档:
This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, activity A will have a chance to save the current state of its user interface via this method so that when the user returns to activity A, the state of the user interface can be restored via
onCreate(Bundle)
oronRestoreInstanceState(Bundle)
.
这个方法在一个活动可能被杀死之前被调用,这样当它在未来某个时间回来时它可以恢复它的状态。例如,如果活动 B 在活动 A 之前启动,并且在某个时刻活动 A 被杀死以回收资源,则活动 A 将有机会通过此方法保存其用户界面的当前状态,以便当用户返回时对于活动 A,可以通过
onCreate(Bundle)
或 恢复用户界面的状态onRestoreInstanceState(Bundle)
。
回答by Martin Belcher - AtWrk
My colleague wrote an article explaining application state on Android devices including explanations on activity lifecycle and state information, how to store state information, and saving to state Bundle
and SharedPreferences
and take a look at here.
我的同事写了一篇文章,解释了 Android 设备上的应用程序状态,包括对活动生命周期和状态信息的解释、如何存储状态信息以及保存到状态Bundle
,SharedPreferences
并在此处查看。
The article covers three approaches:
文章介绍了三种方法:
Store local variable/UI control data for application lifetime (i.e. temporarily) using an instance state bundle
使用实例状态包为应用程序生命周期(即临时)存储局部变量/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);
}
Store local variable/UI control data between application instances (i.e. permanently) using shared preferences
使用共享首选项在应用程序实例之间(即永久地)存储局部变量/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();
}
Keeping object instances alive in memory between activities within application lifetime using a retained non-configuration instance
使用保留的非配置实例在应用程序生命周期内的活动之间在内存中保持对象实例处于活动状态
[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();
}
回答by Mike Repass
This is a classic 'gotcha' of Android development. There are two issues here:
这是 Android 开发的一个经典“陷阱”。这里有两个问题:
- There is a subtle Android Framework bug which greatly complicates application stack management during development, at least on legacy versions (not entirely sure if/when/how it was fixed). I'll discuss this bug below.
- The 'normal' or intended way to manage this issue is, itself, rather complicated with the duality of onPause/onResume and onSaveInstanceState/onRestoreInstanceState
- 有一个微妙的 Android 框架错误,它使开发过程中的应用程序堆栈管理变得非常复杂,至少在旧版本上(不完全确定是否/何时/如何修复)。我将在下面讨论这个错误。
- 由于 onPause/onResume 和 onSaveInstanceState/onRestoreInstanceState 的二元性,管理此问题的“正常”或预期方法本身相当复杂
Browsing across all these threads, I suspect that much of the time developers are talking about these two different issues simultaneously ... hence all the confusion and reports of "this doesn't work for me".
浏览所有这些线程,我怀疑大部分时间开发人员都在同时讨论这两个不同的问题……因此所有关于“这对我不起作用”的混乱和报告。
First, to clarify the 'intended' behavior: onSaveInstance and onRestoreInstance are fragile and only for transient state. The intended usage (afaict) is to handle Activity recreation when the phone is rotated (orientation change). In other words, the intended usage is when your Activity is still logically 'on top', but still must be reinstantiated by the system. The saved Bundle is not persisted outside of the process/memory/gc, so you cannot really rely on this if your activity goes to the background. Yes, perhaps your Activity's memory will survive its trip to the background and escape GC, but this is not reliable (nor is it predictable).
首先,澄清“预期”行为:onSaveInstance 和 onRestoreInstance 是脆弱的,仅适用于瞬态。预期用途(afaict)是在手机旋转(方向改变)时处理 Activity 娱乐。换句话说,预期用途是当您的 Activity 在逻辑上仍处于“顶部”状态,但仍必须由系统重新实例化时。保存的 Bundle 不会在进程/内存/gc 之外持久化,因此如果您的活动进入后台,您就不能真正依赖它。是的,也许您的 Activity 的内存会在它的后台之旅中幸存下来并逃脱 GC,但这并不可靠(也不是可预测的)。
So if you have a scenario where there is meaningful 'user progress' or state that should be persisted between 'launches' of your application, the guidance is to use onPause and onResume. You must choose and prepare a persistent store yourself.
因此,如果您有一个场景,其中在应用程序的“启动”之间应该保留有意义的“用户进度”或状态,则指导是使用 onPause 和 onResume。您必须自己选择和准备一个持久性存储。
BUT - there is a very confusing bug which complicates all of this. Details are here:
但是 - 有一个非常令人困惑的错误使所有这一切都变得复杂。详细信息在这里:
http://code.google.com/p/android/issues/detail?id=2373
http://code.google.com/p/android/issues/detail?id=2373
http://code.google.com/p/android/issues/detail?id=5277
http://code.google.com/p/android/issues/detail?id=5277
Basically, if your application is launched with the SingleTask flag, and then later on you launch it from the home screen or launcher menu, then that subsequent invocation will create a NEW task ... you'll effectively have two different instances of your app inhabiting the same stack ... which gets very strange very fast. This seems to happen when you launch your app during development (i.e. from Eclipse or Intellij), so developers run into this a lot. But also through some of the app store update mechanisms (so it impacts your users as well).
基本上,如果您的应用程序是使用 SingleTask 标志启动的,然后稍后您从主屏幕或启动器菜单启动它,那么后续调用将创建一个新任务……您将有效地拥有应用程序的两个不同实例居住在同一个堆栈中......这很快就变得非常奇怪。当您在开发期间(即从 Eclipse 或 Intellij)启动应用程序时,这似乎会发生,因此开发人员经常遇到这种情况。但也通过一些应用商店更新机制(因此它也会影响您的用户)。
I battled through these threads for hours before I realized that my main issue was this bug, not the intended framework behavior. A great writeup and workaround(UPDATE: see below) seems to be from user @kaciula in this answer:
在我意识到我的主要问题是这个错误,而不是预期的框架行为之前,我在这些线程中挣扎了几个小时。一篇很棒的文章和解决方法(更新:见下文)在这个答案中似乎来自用户@kaciula:
UPDATE June 2013: Months later, I have finally found the 'correct' solution. You don't need to manage any stateful startedApp flags yourself, you can detect this from the framework and bail appropriately. I use this near the beginning of my LauncherActivity.onCreate:
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;
}
}
回答by Fedor
onSaveInstanceState
is called when the system needs memory and kills an application. It is not called when the user just closes the application. So I think application state should also be saved in onPause
It should be saved to some persistent storage like Preferences
or Sqlite
onSaveInstanceState
当系统需要内存并杀死应用程序时调用。当用户关闭应用程序时不会调用它。因此,我认为应用程序的状态也应该保存在onPause
它应该被保存到像一些永久存储Preferences
或Sqlite
回答by David
Both methods are useful and valid and both are best suited for different scenarios:
这两种方法都是有用且有效的,并且都最适合不同的场景:
- The user terminates the application and re-opens it at a later date, but the application needs to reload data from the last session – this requires a persistent storage approach such as using SQLite.
- The user switches application and then comes back to the original and wants to pick up where they left off - save and restore bundle data (such as application state data) in
onSaveInstanceState()
andonRestoreInstanceState()
is usually adequate.
- 用户终止应用程序并在以后重新打开它,但应用程序需要从上次会话重新加载数据——这需要使用持久存储方法,例如使用 SQLite。
- 用户切换应用程序,然后又回到原来的,并希望拿起他们离开的地方-保存和恢复的包数据(如应用程序状态数据)
onSaveInstanceState()
和onRestoreInstanceState()
通常是足够的。
If you save the state data in a persistent manner, it can be reloaded in an onResume()
or onCreate()
(or actually on any lifecycle call). This may or may not be desired behaviour. If you store it in a bundle in an InstanceState
, then it is transient and is only suitable for storing data for use in the same user ‘session' (I use the term session loosely) but not between ‘sessions'.
如果您以持久方式保存状态数据,则可以在onResume()
或onCreate()
(或实际上在任何生命周期调用中)重新加载它。这可能是也可能不是期望的行为。如果您将它存储在一个包中InstanceState
,则它是暂时的,并且仅适用于存储在同一用户“会话”中使用的数据(我松散地使用术语会话),但不适用于“会话”之间。
It is not that one approach is better than the other, like everything, it is just important to understand what behaviour you require and to select the most appropriate approach.
并不是说一种方法比另一种更好,就像所有事情一样,重要的是了解您需要什么行为并选择最合适的方法。
回答by Mike A.
Saving state is a kludge at best as far as I'm concerned. If you need to save persistent data, just use an SQLitedatabase. Android makes it SOOOeasy.
就我而言,保存状态充其量只是一团糟。如果您需要保存持久数据,只需使用SQLite数据库。Android可以SOOO容易。
Something like this:
像这样的东西:
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) {
}
}
}
A simple call after that
之后的一个简单调用
dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;
回答by roy mathew
I think I found the answer. Let me tell what I have done in simple words:
我想我找到了答案。让我用简单的话来说一下我所做的:
Suppose I have two activities, activity1 and activity2 and I am navigating from activity1 to activity2 (I have done some works in activity2) and again back to activity 1 by clicking on a button in activity1. Now at this stage I wanted to go back to activity2 and I want to see my activity2 in the same condition when I last left activity2.
假设我有两个活动,活动 1 和活动 2,我从活动 1 导航到活动 2(我在活动 2 中做了一些工作),然后通过单击活动 1 中的按钮再次返回活动 1。现在在这个阶段,我想回到活动 2 并且我想在我上次离开活动 2 时看到我的活动 2 处于相同的状态。
For the above scenario what I have done is that in the manifest I made some changes like this:
对于上述情况,我所做的是在清单中我做了一些这样的改变:
<activity android:name=".activity2"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance">
</activity>
And in the activity1 on the button click event I have done like this:
在按钮单击事件的 activity1 中,我是这样做的:
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);
And in activity2 on button click event I have done like this:
在按钮单击事件的activity2中,我是这样做的:
Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);
Now what will happen is that whatever the changes we have made in the activity2 will not be lost, and we can view activity2 in the same state as we left previously.
现在会发生的是,无论我们在活动 2 中所做的任何更改都不会丢失,并且我们可以以与之前离开的状态相同的状态查看活动 2。
I believe this is the answer and this works fine for me. Correct me if I am wrong.
我相信这就是答案,这对我来说很好用。如果我错了,请纠正我。
回答by Ixx
onSaveInstanceState()
for transient data (restored in onCreate()
/onRestoreInstanceState()
), onPause()
for persistent data (restored in onResume()
).
From Android technical resources:
onSaveInstanceState()
对于瞬态数据(在onCreate()
/ 中恢复onRestoreInstanceState()
),onPause()
对于持久数据(在 中恢复onResume()
)。来自 Android 技术资源:
onSaveInstanceState()is called by Android if the Activity is being stopped and may be killed before it is resumed! This means it should store any state necessary to re-initialize to the same condition when the Activity is restarted. It is the counterpart to the onCreate() method, and in fact the savedInstanceState Bundle passed in to onCreate() is the same Bundle that you construct as outState in the onSaveInstanceState() method.
onPause()and onResume()are also complimentary methods. onPause() is always called when the Activity ends, even if we instigated that (with a finish() call for example). We will use this to save the current note back to the database. Good practice is to release any resources that can be released during an onPause() as well, to take up less resources when in the passive state.
如果 Activity 正在停止并且可能在恢复之前被杀死,则 Android 会调用onSaveInstanceState()!这意味着它应该存储在重新启动 Activity 时重新初始化为相同条件所需的任何状态。它是 onCreate() 方法的对应物,实际上传递给 onCreate() 的 savedInstanceState Bundle 与您在 onSaveInstanceState() 方法中构造为 outState 的 Bundle 相同。
onPause()和onResume()也是免费的方法。onPause() 总是在 Activity 结束时被调用,即使是我们发起的(例如使用 finish() 调用)。我们将使用它来将当前笔记保存回数据库。好的做法是释放任何可以在 onPause() 期间释放的资源,以便在处于被动状态时占用更少的资源。