使用保存实例状态保存Android活动状态
我一直在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); } }
我认为最简单的情况就足够了,但无论我如何离开应用程序,它总是以第一条消息作为响应。
我相信这个解决scheme就像覆盖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"); }
您通常会使用这种技术为应用程序存储实例值(select,未保存的文本等)。
savedInstanceState
仅用于保存与Activity的当前实例相关联的状态,例如当前的导航或select信息,以便如果Android销毁并重新创build一个Activity,它可以像以前一样返回。 请参阅onCreate
和onSaveInstanceState
的文档
对于更长时间的状态,考虑使用SQLite数据库,文件或首选项。 请参见保存持久状态 。
请注意,根据http://developer.android.com/reference/android/app/Activity.html中有关Activity状态的文档,; 对持久性数据使用onSaveInstanceState
和onRestoreInstanceState
是不安全的。
文档陈述(在“活动生命周期”部分):
请注意,将持久数据保存在
onPause()
而不是onSaveInstanceState(Bundle)
是很重要的,因为后者不是生命周期callback的一部分,所以不会在其文档中描述的任何情况下调用。
换句话说,将持久数据的保存/恢复代码放在onPause()
和onResume()
!
编辑 :为了进一步澄清,这里是onSaveInstanceState()
文档:
这个方法在一个活动可能被杀死之前被调用,所以当它回来的时候它可以恢复它的状态。 例如,如果活动B在活动A之前启动,并且某个活动A被杀死以回收资源,则活动A将有机会通过此方法保存其用户界面的当前状态,以便当用户返回到活动A,用户界面的状态可以通过
onCreate(Bundle)
或onRestoreInstanceState(Bundle)
来恢复。
我的同事写了一篇文章,解释Android设备上的应用状态,包括活动生命周期和状态信息的解释,如何存储状态信息,以及保存到状态Bundle
和SharedPreferences
并在这里看看 。
文章涵盖三种方法:
使用Instance State Bundle存储应用程序生存期(即临时)的本地variables/ 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); }
使用共享首选项在应用程序实例(即永久)之间存储本地variables/ 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(); }
使用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(); }
这是Android开发的一个经典“难题”。 这里有两个问题:
- 有一个微妙的Android框架的错误,使开发过程中的应用程序堆栈pipe理,至less在旧版本(不完全确定是否/何时/如何修复)大大复杂。 我将在下面讨论这个错误。
- pipe理这个问题的“正常”或预期的方式本身就相当复杂,onPause / onResume和onSaveInstanceState / onRestoreInstanceState
浏览所有这些主题,我怀疑很多时候开发人员正在同时讨论这两个不同的问题……因此,所有关于“这不适用于我”的混淆和报告。
首先,澄清“意图”的行为:onSaveInstance和onRestoreInstance是脆弱的,只为瞬态。 预期用法(afaict)是在手机旋转(方向改变)时处理活动娱乐。 换句话说,预期的用法是当你的Activity仍然是逻辑上的“顶部”,但仍然必须由系统重新实例化。 保存的Bundle不会在process / memory / gc之外持久化,所以如果你的活动进入后台,你不能真正依靠这个。 是的,也许你的活动的记忆将在其背景和逃避GC的过程中幸存下来,但这是不可靠的(也是不可预测的)。
因此,如果您的应用程序在“应用程序启动”之间存在有意义的“用户进度”或状态应该保持的情况,则指导是使用onPause和onResume。 您必须自己select并准备一个持久性商店。
但是 – 有一个非常混乱的错误,这使所有这一切变得复杂。 细节在这里:
http://code.google.com/p/android/issues/detail?id=2373
http://code.google.com/p/android/issues/detail?id=5277
基本上,如果您的应用程序是使用SingleTask标志启动的,然后从主屏幕或启动器菜单启动它,那么随后的调用将创build一个新的任务…您将有效地拥有两个不同的应用程序实例居住在同一个堆栈…非常快速地变得非常奇怪。 这似乎是在开发过程中(例如从Eclipse或Intellij)启动应用程序时发生的,所以开发人员会遇到这个问题。 而且还通过一些app store的更新机制(所以它也会影响你的用户)。
在我意识到我的主要问题是这个错误,而不是预期的框架行为之前,我通过这些线程进行了几个小时的斗争。 一个伟大的写作和 解决方法 (更新:见下文)似乎是从用户@ kaciula在这个答案:
家庭按键行为
更新2013年6月 :几个月后,我终于find了“正确的”解决scheme。 您不需要自己pipe理任何有状态的启动应用程序标志,您可以从框架中检测到这一点,并适当地保释。 我在我的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
在系统需要内存并杀死应用程序时被调用。 当用户closures应用程序时不会调用它。 所以我认为应用程序的状态也应该保存在onPause
它应该被保存到喜好或Sqlite
一些持久性存储
这两种方法都是有用和有效的,并且最适合于不同的情况:
- 用户终止应用程序,并在以后重新打开它,但应用程序需要重新加载上一次会话的数据 – 这需要一个持久的存储方法,如使用SQLite。
- 用户切换应用程序,然后回到原来的位置,并希望select停止的位置 – 在
onSaveInstanceState()
和onRestoreInstanceState()
保存和恢复包数据(如应用程序状态数据)通常是足够的。
如果以持久方式保存状态数据,则可以通过onResume()
或onCreate()
(或实际上在任何生命周期调用中)重新加载状态数据。 这可能是也可能不是理想的行为。 如果你将它存储在一个InstanceState
的包中,那么它是暂时的,并且只适用于存储在同一个用户会话中使用的数据(我使用术语会话松散),而不是在“会话”之间。
并不是说一种方法比其他方法好,就像所有的方法一样,理解你需要什么样的行为并select最合适的方法是很重要的。
就我而言,保存状态至多是一个混乱。 如果您需要保存持久性数据,只需使用SQLite数据库即可。 Android使它变得简单。
像这样的东西:
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;
我想我find了答案。 让我用简单的话说出我所做的事:
假设我有两个活动,活动1和活动2,我从活动1导航到活动2(我在活动2中做了一些工作),然后再次通过点击活动1中的button回到活动1。 现在在这个阶段,我想回到活动2,我想看到我的活动2在相同的情况下,当我最后离开activity2。
对于上述情况我所做的是在清单中我做了一些这样的改变:
<activity android:name=".activity2" android:alwaysRetainTaskState="true" android:launchMode="singleInstance"> </activity>
在button点击事件的activity1中,我做了这样的事情:
Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); intent.setClassName(this,"com.mainscreen.activity2"); startActivity(intent);
而在activity2上button点击事件我做了这样的:
Intent intent=new Intent(); intent.setClassName(this,"com.mainscreen.activity1"); startActivity(intent);
现在将会发生的是,无论我们在活动中所做的改变2是否不会丢失,并且我们可以以与之前离开相同的状态查看活动2。
我相信这是答案,这对我来说很好。 纠正我,如果我错了。
onSaveInstanceState()
用于onRestoreInstanceState()
数据(在onCreate()
/ onRestoreInstanceState()
)中恢复, onPause()
用于持久数据(在onResume()
恢复)。 来自Android的技术资源:
onSaveInstanceState()由Android调用,如果活动正在停止,并可能在它被恢复之前被杀死! 这意味着当活动重新启动时,它应该存储任何需要重新初始化到相同条件的状态。 它是onCreate()方法的对象,事实上,传入onCreate()的savedInstanceState Bundle与在onSaveInstanceState()方法中构造为outState的Bundle是相同的。
onPause()和onResume()也是免费的方法。 当Activity结束时,onPause()总是被调用,即使我们煽动(例如finish()调用)。 我们将使用它来将当前的笔记保存回数据库。 好的做法是释放onPause()期间可释放的任何资源,以便在被动状态下占用更less的资源。
当Activity进入后台时,真正的onSaveInstance
状态为callen
从文档引用:“方法onSaveInstanceState(Bundle)
被称为放置在这样的背景状态的活动”
为了帮助减less样板,我使用下面的interface
和class
来读/写一个Bundle
以保存实例状态。
首先,创build一个将用于注释实例variables的接口:
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 { }
然后,创build一个类,其中将使用reflection将值保存到包中:
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); } }
注意:此代码是根据MIT许可证授权的名为AndroidAutowire的库项目改编的。
同时我一般不再使用
Bundle savedInstanceState & Co
现场循环对于大多数活动来说太复杂并且不必要。 谷歌自己说,它甚至不可靠。
我的方法是立即保存偏好中的任何更改
SharedPreferences p; p.edit().put(..).commit()
在某种程度上,SharedPreferences的工作方式类似于Bundles。 自然而然,起初这样的价值观必须是红色的。
在复杂数据的情况下,您可以使用Sqlite而不是使用首选项。
当应用这个概念时,活动只是继续使用上次保存的状态,不pipe它是初始打开还是重新启动,还是由于反向堆栈而重新打开。
onSaveInstanceState(bundle)
和onRestoreInstanceState(bundle)
方法仅在旋转屏幕(方向更改)时对数据持久性很有用。
在应用程序之间切换时,它们甚至不是很好(因为onSaveInstanceState()
方法被调用,但是onCreate(bundle)
和onRestoreInstanceState(bundle)
不会再被调用。
为了更多的持久性使用共享的首选 阅读这篇文章
重新创build一个活动
There are a few scenarios in which your activity is destroyed due to normal app behavior, such as when the user presses the Back button or your activity signals its own destruction by calling finish()
. The system may also destroy your activity if it's currently stopped and hasn't been used in a long time or the foreground activity requires more resources so the system must shut down background processes to recover memory.
When your activity
is destroyed because the user presses Back or the activity
finishes itself, the system's concept of that Activity
instance is gone forever because the behavior indicates the activity is no longer needed. However, if the system destroys the activity due to system constraints (rather than normal app behavior), then although the actual Activity instance is gone, the system remembers that it existed such that if the user navigates back to it, the system creates a new instance of the activity using a set of saved data that describes the state of the activity when it was destroyed
. The saved data that the system uses to restore the previous state is called the "instance state" and is a collection of key-value pairs stored in a Bundle object.
To save additional data about the activity state, you must override the onSaveInstanceState() callback method. The system calls this method when the user is leaving your activity and passes it the Bundle object that will be saved in the event that your activity is destroyed unexpectedly. If the system must recreate the activity instance later, it passes the same Bundle object to both the onRestoreInstanceState()
and onCreate()
methods.
As the system begins to stop your activity, it calls onSaveInstanceState()
(1) so you can specify additional state data you'd like to save in case the Activity instance must be recreated. If the activity is destroyed and the same instance must be recreated, the system passes the state data defined at (1) to both the onCreate()
method (2) and the onRestoreInstanceState()
method (3).
Save Your Activity
State
As your activity begins to stop, the system calls onSaveInstanceState()
so your activity can save state information with a collection of key-value pairs. The default implementation of this method saves information about the state of the activity's view hierarchy, such as the text in an EditText
widget or the scroll position of a ListView
.
To save additional state information for your activity, you must implement onSaveInstanceState()
and add key-value pairs to the Bundle object. 例如:
static final String STATE_SCORE = "playerScore"; static final String STATE_LEVEL = "playerLevel"; @Override public void onSaveInstanceState(Bundle savedInstanceState) { // Save the user's current game state savedInstanceState.putInt(STATE_SCORE, mCurrentScore); savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel); // Always call the superclass so it can save the view hierarchy state super.onSaveInstanceState(savedInstanceState); }
Caution: Always call the superclass implementation of onSaveInstanceState()
so the default implementation can save the state of the view hierarchy.
Restore Your Activity
State
When your activity is recreated after it was previously destroyed, you can recover your saved state from the Bundle that the system passes your activity. Both the onCreate()
and onRestoreInstanceState()
callback methods receive the same Bundle
that contains the instance state information.
Because the onCreate()
method is called whether the system is creating a new instance of your activity or recreating a previous one, you must check whether the state Bundle is null before you attempt to read it. If it is null, then the system is creating a new instance of the activity, instead of restoring a previous one that was destroyed.
For example, here's how you can restore some state data in onCreate()
:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Always call the superclass first // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { // Restore value of members from saved state mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); } else { // Probably initialize members with default values for a new instance } }
Instead of restoring the state during onCreate()
you may choose to implement onRestoreInstanceState()
, which the system calls after the onStart()
method. The system calls onRestoreInstanceState()
only if there is a saved state to restore, so you do not need to check whether the Bundle is null:
public void onRestoreInstanceState(Bundle savedInstanceState) { // Always call the superclass so it can restore the view hierarchy super.onRestoreInstanceState(savedInstanceState); // Restore state members from saved instance mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); }
To answer the original question directly. savedInstancestate is null because your Activity is never being re-created.
Your Activity will only be re-created with a state bundle when:
- Configuration changes such as changing the orientation or phone language which may requires a new activity instance to be created.
- You return to the app from the background after the OS has destroyed the activity.
Android will destroy background activities when under memory pressure or after they've been in the background for an extended period of time.
When testing your hello world example there are a few ways to leave and return to the Activity.
- When you press the back button the Activity is finished. Re-launching the app is a brand new instance. You aren't resuming from the background at all.
- When you press the home button or use the task switcher the Activity will go into the background. When navigating back to the application onCreate will only be called if the Activity had to be destroyed.
In most cases if you're just pressing home and then launching the app again the activity won't need to be re-created. It already exists in memory so onCreate() won't be called.
There is an option under Settings -> Developer Options called "Don't keep activities". When it's enabled Android will always destroy activities and recreate them when they're backgrounded. This is a great option to leave enabled when developing because it simulates the worst case scenario. ( A low memory device recycling your activities all the time ).
The other answers are valuable in that they teach you the correct ways to store state but I didn't feel they really answered WHY your code wasn't working in the way you expected.
My problem was that I needed persistence only during the application lifetime (ie a single execution including starting other sub-activities within the same app and rotating the device etc). I tried various combinations of the above answers but did not get what I wanted in all situations. In the end what worked for me was to obtain a reference to the savedInstanceState during onCreate:
mySavedInstanceState=savedInstanceState;
and use that to obtain the contents of my variable when I needed it, along the lines of:
if (mySavedInstanceState !=null) { boolean myVariable = mySavedInstanceState.getBoolean("MyVariable"); }
I use onSaveInstanceState
and onRestoreInstanceState
as suggested above but I guess i could also or alternatively use my method to save the variable when it changes (eg using putBoolean
)
Although the accepted answer is correct, there is a faster and easier method to save the Activity state on Android using a library called Icepick . Icepick is an annotation processor that takes care of all the boilerplate code used in saving and restoring state for you.
Doing something like this with 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); } }
Is the same as doing this:
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 will work with any object that saves its state with a Bundle
.
There are basically two ways to implement this change.
- using
onSaveInstanceState()
andonRestoreInstanceState()
. - In manifest
android:configChanges="orientation|screenSize"
.
I really do not recommend to use second method. Since in one of my experience it was causing half of the device screen black while rotating from portrait to landscape and vice versa.
Using first method mentioned above , we can persist data when orientation is changed or any config change happens. I know a way in which you can store any type of data inside savedInstance state object.
Example: Consider a case if you want to persist Json object. create a model class with getters and setters .
class MyModel extends Serializable{ JSONObject obj; setJsonObject(JsonObject obj) { this.obj=obj; } JSONObject getJsonObject() return this.obj; } }
Now in your activity in onCreate and onSaveInstanceState method do the following. It will look something like this:
@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); }
When an activity is created it's onCreate() method is called.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }
savedInstanceState is an object of Bundle class which is null for the first time, but it contains values when it is recreated. To save Activity's state you have to override onSaveInstanceState().
@Override protected void onSaveInstanceState(Bundle outState) { outState.putString("key","Welcome Back") super.onSaveInstanceState(outState); //save state }
put your values in "outState" Bundle object like outState.putString("key","Welcome Back") and save by calling super. When activity will be destroyed it's state get saved in Bundle object and can be restored after recreation in onCreate() or onRestoreInstanceState(). Bundle received in onCreate() and onRestoreInstanceState() are same.
@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"); }
Not sure if my solution is frowned upon or not but I use a bound service to persist ViewModel state. Whether you store it in memory in the service or persist and retrieve from a SqlLite database depends on your requirements. This is what services of any flavor do, they provide services such as maintaining application state and abstract common business logic.
Because of memory and processing constraints inherent on mobile devices, I treat Android views in a similar way to a web page. The page does not maintain state, it is purely a presentation layer component whose only purpose is to present application state and accept user input. Recent trends in web app architecture employ the use of the age old Model, View, Controller (MVC) pattern, where the page is the View, Domain data is the model and the controller sits behind a web service. The same pattern can be employed in android with the View being well … the View, the model is your domain data and the Controller is implemented as an Android bound service. Whenever you want a view to interact with the controller, bind to it on start/resume and unbind on stop/pause.
This approach gives you the added bonus of enforcing the Separation of Concern design principle in that all of you application business logic can be moved into your service which reduces duplicated logic across multiple views and allows the view to enforce another important design principle, Single Responsibility.
Simple quick to solve this problem is using IcePick
First, setup the library in app/build.gradle
repositories { maven {url "https://clojars.org/repo/"} } dependencies { compile 'frankiesardo:icepick:3.2.0' provided 'frankiesardo:icepick-processor:3.2.0' }
Now, let's check this example below how to save state in 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); } }
It works for Activities, Fragments or any object that needs to serialize its state on a Bundle (eg mortar's ViewPresenters)
Icepick can also generate the instance state code for custom 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 }
To get activity state data stored in onCreate(), first you have to save data in savedInstanceState by overrding SaveInstanceState(Bundle savedInstanceState) method.
When activity destroy SaveInstanceState(Bundle savedInstanceState) method gets called and there you save data you want to save. And you get same in onCreate() when activity restart.(savedInstanceState wont be null since you have saved some data in it before activity get destroyed)
Here is a comment from Steve Moseley 's answer (by ToolmakerSteve ) that puts things into perspective (in the whole onSaveInstanceState vs onPause, east cost vs west cost saga)
@VVK – I partially disagree. Some ways of exiting an app don't trigger onSaveInstanceState (oSIS). This limits the usefulness of oSIS. Its worth supporting, for minimal OS resources, but if an app wants to return the user to the state they were in, no matter how the app was exited, it is necessary to use a persistent storage approach instead. I use onCreate to check for bundle, and if it is missing, then check persistent storage. This centralizes the decision making. I can recover from a crash, or back button exit or custom menu item Exit, or get back to screen user was on many days later. – ToolmakerSteve Sep 19 '15 at 10:38