Как сохранить состояние активности Android с помощью сохранения состояния экземпляра?
Я работал над платформой 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 минут или около того и не нашел ничего очевидного.
39 ответов
Вы должны переопределить 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.
}
Пакет, по сути, является способом хранения карты 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");
}
Обычно вы используете эту технику для хранения значений экземпляра для вашего приложения (выборки, несохраненный текст и т. Д.).
savedInstanceState
предназначен только для сохранения состояния, связанного с текущим экземпляром Activity, например, текущей навигацией или информацией о выборе, так что, если Android уничтожает и воссоздает Activity, он может вернуться, как это было раньше. Смотрите документацию для onCreate
а также onSaveInstanceState
Для более долгоживущего состояния рассмотрите возможность использования базы данных SQLite, файла или настроек. См. Сохранение постоянного состояния.
Обратите внимание, что это не безопасно использовать 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)
,
Мой коллега написал статью, объясняющую состояние приложения на устройствах Android, включая объяснения жизненного цикла активности и информации о состоянии, как хранить информацию о состоянии и сохранять в состояние Bundle
а также SharedPreferences
и посмотрите на здесь.
В статье рассматриваются три подхода:
Храните локальные переменные / данные управления пользовательским интерфейсом для времени жизни приложения (т.е. временно), используя пакет состояния экземпляра
[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();
}
Это классический "гоча" разработки для Android. Здесь есть две проблемы:
- Существует небольшая ошибка в Android Framework, которая значительно усложняет управление стеком приложений во время разработки, по крайней мере, в старых версиях (не совсем уверенно, если / когда / как это было исправлено). Я буду обсуждать эту ошибку ниже.
- "Нормальный" или предполагаемый способ решения этой проблемы сам по себе довольно сложен из-за двойственности onPause / onResume и onSaveInstanceState / onRestoreInstanceState
Просматривая все эти темы, я подозреваю, что большую часть времени разработчики говорят об этих двух разных проблемах одновременно... отсюда и вся путаница и сообщения о том, что "это не работает для меня".
Во-первых, чтобы прояснить "предполагаемое" поведение: onSaveInstance и onRestoreInstance хрупки и только для переходного состояния. Предполагаемое использование (afaict) состоит в том, чтобы обрабатывать Активный отдых, когда телефон поворачивается (изменение ориентации). Другими словами, предполагаемое использование - это когда ваша активность по-прежнему логически "на вершине", но все равно должна быть переоценена системой. Сохраненный Bundle не сохраняется вне процесса / memory / gc, поэтому вы не можете действительно полагаться на это, если ваша деятельность переходит в фоновый режим. Да, возможно, память вашей Деятельности переживет свое путешествие на задний план и избежит 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: Несколько месяцев спустя я наконец нашел "правильное" решение. Вам не нужно управлять какими-либо флагами StarApp с сохранением состояния самостоятельно, вы можете обнаружить это из фреймворка и соответственно внести залог. Я использую это в начале моего 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
тогда он временный и подходит только для хранения данных для использования в одной и той же пользовательской "сессии" (я использую термин "сессия"), но не между "сессиями".
Дело не в том, что один подход лучше другого, как и все, просто важно понять, какое поведение вам требуется, и выбрать наиболее подходящий подход.
Насколько я могу судить, сохранение состояния - в лучшем случае клудж. Если вам нужно сохранить постоянные данные, просто используйте базу данных 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;
Я думаю, что нашел ответ. Позвольте мне рассказать, что я сделал, простыми словами:
Предположим, у меня есть два действия, действие 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);
И в активности 2 на событие нажатия кнопки я сделал так:
Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);
Теперь произойдет то, что любые изменения, которые мы внесли в действие 2, не будут потеряны, и мы сможем просмотреть действие 2 в том же состоянии, в котором мы вышли ранее.
Я считаю, что это ответ, и это прекрасно работает для меня. Поправь меня, если я ошибаюсь.
onSaveInstanceState()
для переходных данных (восстанавливается в onCreate()
/onRestoreInstanceState()
), onPause()
для постоянных данных (восстанавливается в onResume()
). Из технических ресурсов Android:
onSaveInstanceState () вызывается Android, если действие останавливается и может быть убито до его возобновления! Это означает, что в нем должно храниться любое состояние, необходимое для повторной инициализации до того же состояния при перезапуске действия. Это аналог метода onCreate (), и фактически Bundle сохраненного экземпляра, переданного в onCreate (), является тем же пакетом, который вы создаете как outState в методе onSaveInstanceState ().
onPause () и onResume () также являются дополнительными методами. onPause () всегда вызывается, когда заканчивается действие, даже если мы это спровоцировали (например, с помощью метода finish ()). Мы будем использовать это, чтобы сохранить текущую заметку обратно в базу данных. Хорошей практикой является освобождение любых ресурсов, которые также могут быть освобождены во время onPause (), чтобы занимать меньше ресурсов в пассивном состоянии.
В самом деле onSaveInstance
состояние Каллен, когда активность переходит в фоновый режим
Цитата из документов:
"метод onSaveInstanceState(Bundle)
вызывается перед переводом активности в такое фоновое состояние
Между тем я вообще больше не пользуюсь
Bundle savedInstanceState & Co
жизненный цикл для большинства видов деятельности слишком сложен и не нужен. И Google заявляет о себе, это даже не надежно.
Мой способ сохранить любые изменения сразу в настройках
SharedPreferences p;
p.edit().put(..).commit()
в некотором смысле SharedPreferences работают аналогично Bundles. И естественно и сначала такие значения должны быть красными от предпочтений.
В случае сложных данных вы можете использовать Sqlite вместо предпочтений.
При применении этой концепции действие просто продолжает использовать последнее сохраненное состояние, независимо от того, было ли это начальным открытием с промежуточными перезагрузками или повторным открытием из-за заднего стека.
Чтобы помочь уменьшить шаблон, я использую следующее 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.
Воссоздание деятельности
Есть несколько сценариев, в которых ваша активность разрушается из-за нормального поведения приложения, например, когда пользователь нажимает кнопку "Назад" или ваша активность сигнализирует о собственном уничтожении, вызывая finish()
, Система также может уничтожить вашу активность, если она в данный момент остановлена и не использовалась в течение длительного времени, или для выполнения основной задачи требуется больше ресурсов, поэтому система должна завершить фоновые процессы для восстановления памяти.
Когда ваш activity
уничтожается, потому что пользователь нажимает кнопку Назад или activity
заканчивает себя, концепция системы о том, что Activity
экземпляр исчез навсегда, потому что поведение указывает на то, что активность больше не нужна. Однако, если система уничтожает действие из-за системных ограничений (а не из-за нормального поведения приложения), тогда, хотя фактический экземпляр Activity пропал, система запоминает, что он существовал таким образом, что, если пользователь возвращается к нему, система создает новый экземпляр действия с использованием набора сохраненных данных, который описывает состояние действия, когда оно было destroyed
, Сохраненные данные, которые система использует для восстановления предыдущего состояния, называются "состоянием экземпляра" и представляют собой набор пар ключ-значение, хранящихся в объекте Bundle.
Чтобы сохранить дополнительные данные о состоянии активности, необходимо переопределить метод обратного вызова onSaveInstanceState(). Система вызывает этот метод, когда пользователь покидает вашу деятельность, и передает ему объект Bundle, который будет сохранен в случае неожиданного уничтожения вашей активности. Если система должна воссоздать экземпляр действия позже, она передает один и тот же объект Bundle обоим onRestoreInstanceState()
а также onCreate()
методы.
Когда система начинает останавливать вашу деятельность, она вызывает onSaveInstanceState()
(1) чтобы вы могли указать дополнительные данные о состоянии, которые вы хотите сохранить на случай, если экземпляр Activity будет воссоздан. Если действие уничтожено и тот же экземпляр должен быть воссоздан, система передает данные состояния, определенные в (1), обоим onCreate()
метод (2) и onRestoreInstanceState()
метод (3).
Сохранить свой Activity
государственный
Когда ваша деятельность начинает останавливаться, системные вызовы onSaveInstanceState()
Таким образом, ваша деятельность может сохранять информацию о состоянии с помощью набора пар ключ-значение. Реализация по умолчанию этого метода сохраняет информацию о состоянии иерархии представления действия, такую как текст в EditText
виджет или положение прокрутки ListView
,
Чтобы сохранить дополнительную информацию о состоянии для вашей деятельности, вы должны реализовать onSaveInstanceState()
и добавить пары ключ-значение в объект Bundle. Например:
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);
}
Внимание: всегда вызывайте реализацию суперкласса onSaveInstanceState()
поэтому реализация по умолчанию может сохранить состояние иерархии представления.
Восстановите свой Activity
государственный
Когда ваша деятельность воссоздается после того, как она была ранее уничтожена, вы можете восстановить сохраненное состояние из Пакета, в котором система передает вашу активность. Оба onCreate()
а также onRestoreInstanceState()
методы обратного вызова получают то же самое Bundle
который содержит информацию о состоянии экземпляра.
Поскольку onCreate()
Метод вызывается, создает ли система новый экземпляр вашей деятельности или воссоздает предыдущий, прежде чем пытаться прочитать его, вы должны проверить, является ли состояние Bundle нулевым. Если оно равно null, то система создает новый экземпляр действия вместо восстановления предыдущего, который был уничтожен.
Например, вот как вы можете восстановить некоторые данные о состоянии в 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
}
}
Вместо восстановления состояния во время onCreate()
Вы можете выбрать для реализации onRestoreInstanceState()
, который система вызывает после onStart()
метод. Системные вызовы onRestoreInstanceState()
только если существует сохраненное состояние для восстановления, поэтому вам не нужно проверять, является ли Bundle нулевым:
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);
}
Чтобы ответить на оригинальный вопрос напрямую. Параметр saveInstancestate имеет значение null, поскольку ваша активность никогда не создается заново.
Ваша активность будет воссоздана только с помощью пакета состояний, когда:
- Изменения конфигурации, такие как изменение ориентации или языка телефона, что может потребовать создания нового экземпляра действия.
- Вы возвращаетесь в приложение из фона после того, как ОС уничтожила активность.
Android будет уничтожать фоновые действия, когда они находятся под давлением памяти или после того, как они находились в фоновом режиме в течение длительного периода времени.
Когда вы тестируете пример "Привет, мир", есть несколько способов уйти и вернуться в действие.
- Когда вы нажимаете кнопку "Назад", действие заканчивается. Повторный запуск приложения - это совершенно новый экземпляр. Вы вообще не выходите из фона.
- Когда вы нажимаете кнопку "Домой" или используете переключатель задач, действие переходит в фоновый режим. При переходе обратно к приложению onCreate будет вызываться только в том случае, если действие пришлось уничтожить.
В большинстве случаев, если вы просто нажимаете кнопку "Домой", а затем снова запускаете приложение, действие не нужно создавать заново. Он уже существует в памяти, поэтому onCreate() не будет вызываться.
В разделе "Настройки" -> "Параметры разработчика" есть опция "Не сохранять действия". Когда он включен, Android всегда уничтожает действия и воссоздает их, когда они находятся на заднем плане. Это отличный вариант, чтобы оставить включенным при разработке, потому что он имитирует худший сценарий. (Устройство с низким объемом памяти постоянно перерабатывает ваши действия).
Другие ответы ценны тем, что они учат вас правильным способам сохранения состояния, но я не чувствую, что они действительно ответили ПОЧЕМУ ваш код не работал так, как вы ожидали.
onSaveInstanceState(bundle)
а также onRestoreInstanceState(bundle)
методы полезны для сохранения данных только при вращении экрана (изменение ориентации).
Они даже не хороши при переключении между приложениями (так как onSaveInstanceState()
метод называется, но onCreate(bundle)
а также onRestoreInstanceState(bundle)
больше не вызывается
Для большей настойчивости используйте общие настройки. прочитайте эту статью
Моя проблема заключалась в том, что мне требовалось постоянство только в течение жизненного цикла приложения (т. Е. Единственное выполнение, включая запуск других вложенных действий в том же приложении, вращение устройства и т. Д.). Я пробовал различные комбинации вышеуказанных ответов, но не получал то, что хотел во всех ситуациях. В конце концов, мне удалось получить ссылку на saveInstanceState во время onCreate:
mySavedInstanceState=savedInstanceState;
и использовать это для получения содержимого моей переменной, когда мне это нужно, в соответствии с:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
я использую onSaveInstanceState
а также onRestoreInstanceState
как предложено выше, но я думаю, что я мог бы также или альтернативно использовать мой метод, чтобы сохранить переменную, когда она изменяется (например, используя putBoolean
)
Хотя принятый ответ правильный, существует более быстрый и простой способ сохранить состояние "Активность" на Android с помощью библиотеки Icepick. 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);
}
saveInstanceState - это объект класса Bundle, который является нулевым в первый раз, но он содержит значения при его воссоздании. Чтобы сохранить состояние действия, вы должны переопределить onSaveInstanceState ().
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("key","Welcome Back")
super.onSaveInstanceState(outState); //save state
}
поместите ваши значения в объект Bundle "outState", например, outState.putString("key","Welcome Back") и сохраните, вызвав super. Когда активность будет уничтожена, ее состояние будет сохранено в объекте Bundle и может быть восстановлено после воссоздания в onCreate() или onRestoreInstanceState(). Пакет, полученный в onCreate() и onRestoreInstanceState (), одинаков.
@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"
,
Я действительно не рекомендую использовать второй метод. Так как в одном из моих опытов это приводило к тому, что половина экрана устройства становилась черной при повороте из портретного положения в ландшафтное и наоборот.
Используя первый метод, упомянутый выше, мы можем сохранить данные при изменении ориентации или при любом изменении конфигурации. Я знаю способ, которым вы можете хранить любой тип данных внутри объекта состояния saveInstance.
Пример: рассмотрим случай, если вы хотите сохранить объект Json. создать класс модели с геттерами и сеттерами.
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);
}
Код Котлина:
спасти:
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
Добавьте значения по умолчанию, если вы не хотите иметь дополнительные
Вот комментарий от ответа Стива Мозли(от ToolmakerSteve), который ставит вещи в перспективе (в целом onSaveInstanceState против onPause, восточная стоимость против западной стоимости саги)
@VVK - Я частично не согласен. Некоторые способы выхода из приложения не вызывают onSaveInstanceState (oSIS). Это ограничивает полезность oSIS. Его стоит поддерживать для минимальных ресурсов ОС, но если приложение хочет вернуть пользователя в состояние, в котором он находился, независимо от того, как было завершено приложение, вместо этого необходимо использовать подход постоянного хранения. Я использую onCreate, чтобы проверить пакет, а если он отсутствует, то проверить постоянное хранилище. Это централизует принятие решений. Я могу восстановиться после сбоя, либо кнопки "Назад", либо выхода из пользовательского пункта меню "Выход", либо вернуться к экрану пользователя было много дней спустя. - ToolmakerSteve 19 '15 в 10:38
Чтобы получить данные о состоянии активности, хранящиеся в onCreate()
сначала вы должны сохранить данные в saveInstanceState путем переопределения SaveInstanceState(Bundle savedInstanceState)
метод.
Когда деятельность уничтожить SaveInstanceState(Bundle savedInstanceState)
метод вызывается и вы сохраняете данные, которые хотите сохранить. И вы получаете то же самое в onCreate()
когда активность перезапускается.(saveInstanceState не будет нулевым, так как вы сохранили в нем некоторые данные до того, как активность будет уничтожена)
Котлин
Вы должны переопределить 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
}
Не уверен, что мое решение не одобрено, но я использую связанный сервис для сохранения состояния ViewModel. Храните ли вы его в памяти в службе или сохраняете и извлекаете из базы данных SqlLite, зависит от ваших требований. Это то, что делают сервисы любого типа, они предоставляют такие сервисы, как поддержание состояния приложения и абстрактную общую бизнес-логику.
Из-за ограничений памяти и обработки, присущих мобильным устройствам, я отношусь к представлениям Android аналогично веб-странице. Страница не поддерживает состояние, это просто компонент уровня представления, единственной целью которого является представление состояния приложения и принятие пользовательского ввода. Последние тенденции в архитектуре веб-приложений используют использование устаревшего шаблона Модель, представление, контроллер (MVC), где страница представляет собой представление, данные домена являются моделью, а контроллер располагается за веб-службой. Тот же самый шаблон может использоваться в Android с View, являющимся хорошо... View, модель - данные вашего домена, и Контроллер реализован как связанная служба Android. Всякий раз, когда вы хотите, чтобы представление взаимодействовало с контроллером, выполните привязку к нему при запуске / возобновлении и отключите при остановке / паузе.
Этот подход дает вам дополнительный бонус за соблюдение принципа разделения интересов, заключающегося в том, что бизнес-логика всех ваших приложений может быть перенесена в ваш сервис, что сокращает дублирование логики между несколькими представлениями и позволяет представлению применять другой важный принцип проектирования, Single Responsibility.
Просто быстро решить эту проблему с помощью 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);
}
}
Это работает для Деятельностей, Фрагментов или любого объекта, который должен сериализовать свое состояние в Пакете (например, 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
}
Теперь Android предоставляет ViewModels для сохранения состояния, вы должны попытаться использовать его вместо saveInstanceState.
использование 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
Есть способ заставить Android сохранять состояния без реализации какого-либо метода. Просто добавьте эту строку в свое объявление Manifest in Activity:
android:configChanges="orientation|screenSize"
Должно получиться так:
<activity
android:name=".activities.MyActivity"
android:configChanges="orientation|screenSize">
</activity>
Здесь вы можете найти больше информации об этой собственности.
Рекомендуется позволить Android обрабатывать это за вас, а не вручную.
Kotlin Solution: для сохранения пользовательского класса в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")
}
Вы можете восстановить его в Activity onCreate
также.
Для фрагмента:
Для того, чтобы поставить класс в saveInstanceState
:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val gson = Gson()
val json = gson.toJson(customClass)
outState.putString("CUSTOM_CLASS", json)
}
Восстановить данные:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//for custom class 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")
}