Как сохранить состояние активности Android с помощью save instance state?
-
02-07-2019 - |
Вопрос
Я работаю на платформе Android SDK, и немного неясно, как сохранить состояние приложения.Итак, учитывая эту незначительную доработку примера "Привет, Android":
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class HelloAndroid extends Activity {
private TextView mTextView = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
if (savedInstanceState == null) {
mTextView.setText("Welcome to HelloAndroid!");
} else {
mTextView.setText("Welcome back.");
}
setContentView(mTextView);
}
}
Я думал, этого будет достаточно для простейшего случая, но он всегда отвечает первым сообщением, независимо от того, как я удаляюсь от приложения.
Я уверен, что решение так же просто, как переопределение onPause
или что-то в этом роде, но я копался в документации минут 30 или около того и не нашел ничего очевидного.
Решение
Вам нужно переопределить onSaveInstanceState(Bundle savedInstanceState)
и запишите значения состояния приложения, которые вы хотите изменить, в Bundle
параметр, подобный этому:
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate if the process is
// killed and restarted.
savedInstanceState.putBoolean("MyBoolean", true);
savedInstanceState.putDouble("myDouble", 1.9);
savedInstanceState.putInt("MyInt", 1);
savedInstanceState.putString("MyString", "Welcome back to Android");
// etc.
}
Пакет, по сути, представляет собой способ хранения карты 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
предназначен только для сохранения состояния, связанного с текущим экземпляром действия, например текущей информации о навигации или выборе, чтобы, если Android уничтожит и воссоздаст действие заново, оно могло вернуться таким, каким было раньше.Смотрите документацию для onCreate
и onSaveInstanceState
Для получения более длительного состояния рассмотрите возможность использования базы данных SQLite, файла или настроек.Видишь Сохранение постоянного состояния.
Обратите внимание, что это НЕТ безопасен в использовании onSaveInstanceState
и onRestoreInstanceState
для постоянных данных, согласно документации о Деятельности , изложенной в http://developer.android.com/reference/android/app/Activity.html.
В документе указано (в разделе "Жизненный цикл деятельности")::
Обратите внимание, что важно сохранять постоянные данные в
onPause()
вместо вместоonSaveInstanceState(Bundle)
поскольку более поздний не является частью обратных вызовов жизненного цикла, so не будет вызываться в каждой ситуации, как описано в его документации.
Другими словами, поместите свой код сохранения / восстановления постоянных данных в 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) - управление активностью при повороте телефона (изменении ориентации).Другими словами, предполагаемое использование - это когда ваша Активность по-прежнему логически "на высоте", но все равно должна быть восстановлена системой.Сохраненный пакет не сохраняется за пределами процесса / памяти / 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), поэтому разработчики часто сталкиваются с этим.Но также и через некоторые механизмы обновления App Store (так что это влияет и на ваших пользователей).
Я часами разбирался в этих потоках, прежде чем понял, что моей главной проблемой была эта ошибка, а не предполагаемое поведение фреймворка.Отличная статья и обходной путь (ОБНОВЛЕНИЕ:смотрите ниже), кажется, от пользователя @kaciula в этом ответе:
Поведение при нажатии клавиши Home
ОБНОВЛЕНИЕ За июнь 2013 года:Месяцы спустя я, наконец, нашел "правильное" решение.Вам не нужно самостоятельно управлять какими-либо флагами startedApp с отслеживанием состояния, вы можете определить это из фреймворка и соответствующим образом выполнить залог.Я использую это в начале моего LauncherActivity.onCreate:
if (!isTaskRoot()) {
Intent intent = getIntent();
String action = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
finish();
return;
}
}
onSaveInstanceState
вызывается, когда системе требуется память и приложение завершает работу.Он не вызывается, когда пользователь просто закрывает приложение.Поэтому я думаю, что состояние приложения также должно быть сохранено в onPause
Он должен быть сохранен в какое-нибудь постоянное хранилище, например Preferences
или Sqlite
Оба метода полезны и валидны, и оба лучше всего подходят для различных сценариев:
- Пользователь завершает работу приложения и повторно открывает его позже, но приложению необходимо перезагрузить данные из последнего сеанса – для этого требуется подход к постоянному хранению, такой как использование SQLite.
- Пользователь переключает приложение, а затем возвращается к исходному и хочет продолжить с того места, на котором они остановились - сохранить и восстановить данные пакета (например, данные о состоянии приложения) в
onSaveInstanceState()
иonRestoreInstanceState()
обычно адекватен.
Если вы сохраняете данные о состоянии постоянным образом, их можно повторно загрузить в onResume()
или onCreate()
(или фактически при любом вызове жизненного цикла).Это может быть, а может и не быть желательным поведением.Если вы храните его в пачке в InstanceState
, тогда он является временным и подходит только для хранения данных для использования в одном и том же пользовательском "сеансе" (я использую термин "сеанс" свободно), но не между "сеансами’.
Дело не в том, что один подход лучше другого, как и все остальное, просто важно понять, какое поведение вам требуется, и выбрать наиболее подходящий подход.
Насколько я понимаю, сохранение состояния - это в лучшем случае клудж.Если вам нужно сохранить постоянные данные, просто используйте 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;
Я думаю, что нашел ответ.Позвольте мне простыми словами рассказать о том, что я сделал:
Предположим, у меня есть два действия, activity1 и activity2, и я перехожу от activity1 к activity2 (я выполнил некоторые работы в activity2) и снова возвращаюсь к activity 1, нажав на кнопку в activity1.Теперь, на этом этапе, я хотел вернуться к activity2, и я хочу видеть свою activity2 в том же состоянии, в котором я в последний раз покидал activity2.
Для приведенного выше сценария то, что я сделал, это то, что в манифесте я внес некоторые изменения, подобные этому:
<activity android:name=".activity2"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance">
</activity>
И в activity1 на событии нажатия кнопки я сделал вот так:
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);
И в activity2 при событии нажатия кнопки я сделал вот так:
Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);
Теперь произойдет то, что любые изменения, которые мы внесли в activity2, не будут потеряны, и мы сможем просматривать activity2 в том же состоянии, в котором мы оставили ранее.
Я верю, что это ответ, и у меня это прекрасно работает.Поправьте меня, если я ошибаюсь.
onSaveInstanceState()
для временных данных (восстановленных в onCreate()
/onRestoreInstanceState()
), onPause()
для постоянных данных (восстановленных в onResume()
).Из технических ресурсов Android:
onSaveInstanceState() вызывается Android, если Действие прекращается, и может быть прервано до его возобновления!Это означает, что он должен сохранять любое состояние, необходимое для повторной инициализации до того же состояния при перезапуске Действия.Это аналог метода onCreate(), и фактически пакет savedInstanceState, переданный в onCreate(), является тем же пакетом, который вы создаете как outState в методе onSaveInstanceState().
onPause() Включение паузы () и onResume() есть также дополнительные методы.onPause() всегда вызывается, когда действие заканчивается, даже если мы сами спровоцировали это (например, с помощью вызова finish()).Мы будем использовать это, чтобы сохранить текущую заметку обратно в базу данных.Хорошей практикой является освобождение любых ресурсов, которые также могут быть освобождены во время onPause(), чтобы занимать меньше ресурсов в пассивном состоянии.
В самом деле onSaveInstance
укажите callen, когда Действие переходит в фоновый режим
Цитата из документов:"метод onSaveInstanceState(Bundle)
вызывается перед переводом действия в такое фоновое состояние"
Чтобы уменьшить шаблонность, я использую следующее interface
и class
для чтения / записи в Bundle
для сохранения состояния экземпляра.
Сначала создайте интерфейс, который будет использоваться для аннотирования переменных вашего экземпляра:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.FIELD
})
public @interface SaveInstance {
}
Затем создайте класс, в котором отражение будет использоваться для сохранения значений в пакете:
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import java.io.Serializable;
import java.lang.reflect.Field;
/**
* Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
* SaveInstance}.</p>
*/
public class Icicle {
private static final String TAG = "Icicle";
/**
* Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
*
* @param outState
* The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
* Fragment#onSaveInstanceState(Bundle)}
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @see #load(Bundle, Object)
*/
public static void save(Bundle outState, Object classInstance) {
save(outState, classInstance, classInstance.getClass());
}
/**
* Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
*
* @param outState
* The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
* Fragment#onSaveInstanceState(Bundle)}
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @param baseClass
* Base class, used to get all superclasses of the instance.
* @see #load(Bundle, Object, Class)
*/
public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
if (outState == null) {
return;
}
Class<?> clazz = classInstance.getClass();
while (baseClass.isAssignableFrom(clazz)) {
String className = clazz.getName();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(SaveInstance.class)) {
field.setAccessible(true);
String key = className + "#" + field.getName();
try {
Object value = field.get(classInstance);
if (value instanceof Parcelable) {
outState.putParcelable(key, (Parcelable) value);
} else if (value instanceof Serializable) {
outState.putSerializable(key, (Serializable) value);
}
} catch (Throwable t) {
Log.d(TAG, "The field '" + key + "' was not added to the bundle");
}
}
}
clazz = clazz.getSuperclass();
}
}
/**
* Load all saved fields that have the {@link SaveInstance} annotation.
*
* @param savedInstanceState
* The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @see #save(Bundle, Object)
*/
public static void load(Bundle savedInstanceState, Object classInstance) {
load(savedInstanceState, classInstance, classInstance.getClass());
}
/**
* Load all saved fields that have the {@link SaveInstance} annotation.
*
* @param savedInstanceState
* The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @param baseClass
* Base class, used to get all superclasses of the instance.
* @see #save(Bundle, Object, Class)
*/
public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
if (savedInstanceState == null) {
return;
}
Class<?> clazz = classInstance.getClass();
while (baseClass.isAssignableFrom(clazz)) {
String className = clazz.getName();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(SaveInstance.class)) {
String key = className + "#" + field.getName();
field.setAccessible(true);
try {
Object fieldVal = savedInstanceState.get(key);
if (fieldVal != null) {
field.set(classInstance, fieldVal);
}
} catch (Throwable t) {
Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle");
}
}
}
clazz = clazz.getSuperclass();
}
}
}
Пример использования:
public class MainActivity extends Activity {
@SaveInstance
private String foo;
@SaveInstance
private int bar;
@SaveInstance
private Intent baz;
@SaveInstance
private boolean qux;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icicle.load(savedInstanceState, this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icicle.save(outState, this);
}
}
Примечание: Этот код был адаптирован из библиотечного проекта под названием AndroidAutowire - автоматический провод который лицензирован в соответствии с MIT license.
Между тем от меня, в общем-то, больше нет никакой пользы
Bundle savedInstanceState & Co
Жизненный цикл для большинства видов деятельности слишком сложен и не является необходимым.
И Google заявляет сам, что это даже ненадежно.
Мой способ - немедленно сохранять любые изменения в настройках:
SharedPreferences p;
p.edit().put(..).commit()
В некотором роде SharedPreferences работают аналогично пакетам.И естественно, что сначала такие значения должны быть считаны из настроек.
В случае сложных данных вы можете использовать SQLite вместо использования настроек.
При применении этой концепции действие просто продолжает использовать последнее сохраненное состояние, независимо от того, было ли это начальное открытие с промежуточными перезагрузками или повторное открытие из-за резервного стека.
Чтобы прямо ответить на первоначальный вопрос.savedInstanceState равно нулю, потому что ваше действие никогда не создается повторно.
Ваша активность будет воссоздана с помощью пакета состояний только тогда, когда:
- Изменения конфигурации, такие как изменение ориентации или языка телефона, что может потребовать создания нового экземпляра activity.
- Вы возвращаетесь в приложение из фонового режима после того, как операционная система уничтожила действие.
Android уничтожит фоновые действия при нехватке памяти или после того, как они находились в фоновом режиме в течение длительного периода времени.
При тестировании вашего примера hello world есть несколько способов выйти из этого упражнения и вернуться к нему.
- Когда вы нажимаете кнопку "Назад", действие завершается.Повторный запуск приложения - это совершенно новый экземпляр.Вы вообще не возвращаетесь к фоновой работе.
- Когда вы нажимаете кнопку "Домой" или используете переключатель задач, Действие переходит в фоновый режим.При обратном переходе к приложению onCreate будет вызываться только в том случае, если Действие должно было быть уничтожено.
В большинстве случаев, если вы просто нажимаете кнопку "Домой", а затем снова запускаете приложение, действие не нужно будет создавать заново.Он уже существует в памяти, поэтому onCreate() вызываться не будет.
В разделе Настройки -> Параметры разработчика есть опция под названием "Не сохранять действия".Когда он включен, Android всегда будет уничтожать действия и воссоздавать их заново, когда они будут созданы на заднем плане.Это отличный вариант, который можно оставить включенным при разработке, поскольку он имитирует наихудший сценарий.(Устройство с низким объемом памяти постоянно перерабатывает ваши действия).
Другие ответы ценны тем, что они учат вас правильным способам сохранения состояния, но я не почувствовал, что они действительно отвечают, ПОЧЕМУ ваш код работает не так, как вы ожидали.
Тот Самый onSaveInstanceState(bundle)
и onRestoreInstanceState(bundle)
методы полезны для сохранения данных просто при вращении экрана (изменении ориентации).
Они не работают даже при переключении между приложениями (поскольку onSaveInstanceState()
метод вызывается, но onCreate(bundle)
и onRestoreInstanceState(bundle)
больше не вызывается.
Для большей настойчивости используйте общие настройки. прочтите эту статью
Моя проблема заключалась в том, что мне нужна была настойчивость только в течение срока службы приложения (т. е.однократное выполнение, включая запуск других подзадач в том же приложении, поворот устройства и т.д.).Я пробовал различные комбинации приведенных выше ответов, но не во всех ситуациях получал то, что хотел.В конце концов, у меня получилось получить ссылку на savedInstanceState во время onCreate:
mySavedInstanceState=savedInstanceState;
и используйте это, чтобы получить содержимое моей переменной, когда мне это нужно, следующим образом:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
Я использую onSaveInstanceState
и onRestoreInstanceState
как было предложено выше, но я думаю, я мог бы также или альтернативно использовать свой метод для сохранения переменной при ее изменении (напримериспользуя putBoolean
)
Хотя принятый ответ правильный, существует более быстрый и простой способ сохранения состояния активности на Android с использованием библиотеки под названием Ледоруб.Icepick - это обработчик аннотаций, который обрабатывает весь шаблонный код, используемый для сохранения и восстановления состояния для вас.
Делаем что-то подобное с помощью Icepick:
class MainActivity extends Activity {
@State String username; // These will be automatically saved and restored
@State String password;
@State int age;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
@Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
}
Это то же самое, что делать это:
class MainActivity extends Activity {
String username;
String password;
int age;
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString("MyString", username);
savedInstanceState.putString("MyPassword", password);
savedInstanceState.putInt("MyAge", age);
/* remember you would need to actually initialize these variables before putting it in the
Bundle */
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
username = savedInstanceState.getString("MyString");
password = savedInstanceState.getString("MyPassword");
age = savedInstanceState.getInt("MyAge");
}
}
Icepick будет работать с любым объектом, который сохраняет свое состояние с помощью Bundle
.
Когда действие создается, вызывается его метод onCreate().
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
savedInstanceState - это объект класса Bundle, который в первый раз имеет значение null, но при повторном создании содержит значения.Чтобы сохранить состояние Activity, вы должны переопределить onSaveInstanceState().
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("key","Welcome Back")
super.onSaveInstanceState(outState); //save state
}
поместите ваши значения в объект пакета "outState", например outState.putString("ключ","Добро пожаловать обратно"), и сохраните, вызвав super .Когда activity будет уничтожена, ее состояние сохраняется в объекте 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"
.
Я действительно не рекомендую использовать второй метод.Поскольку в одном из моих опытов половина экрана устройства была черной при повороте из книжной ориентации в альбомную и наоборот.
Используя первый метод, упомянутый выше, мы можем сохранять данные при изменении ориентации или любых изменениях конфигурации.Я знаю способ, с помощью которого вы можете хранить данные любого типа внутри объекта состояния savedInstance.
Пример:Рассмотрим случай, если вы хотите сохранить объект 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);
}
Вот комментарий от Стив Мозлиответ автора (by Инструментальщики) это ставит вещи в перспективу (во всей саге о onSaveInstanceState vs onPause, east cost vs west cost)
@VVK - Я частично не согласен.Некоторые способы выхода из приложения не запускают onSaveInstanceState (oSIS).Это ограничивает полезность oSIS.Это стоит поддерживать при минимальных ресурсах ОС, но если приложение хочет вернуть пользователя в состояние, в котором он находился, независимо от того, как приложение было завершено, вместо этого необходимо использовать подход с постоянным хранилищем. Я использую onCreate для проверки наличия пакета, и если он отсутствует, то проверьте постоянное хранилище. Это централизует процесс принятия решений.Я могу восстановиться после сбоя, или выйти из кнопки "Назад", или выйти из пункта пользовательского меню, или вернуться к экрану, на котором пользователь был включен много дней спустя.– ToolmakerSteve сентябрь 19 '15 в 10:38
Код Kotlin:
Сохранить:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState.apply {
putInt("intKey", 1)
putString("stringKey", "String Value")
putParcelable("parcelableKey", parcelableObject)
})
}
а потом в onCreate()
или onRestoreInstanceState()
val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable
Добавьте значения по умолчанию, если вы не хотите использовать Дополнительные параметры
Чтобы получить данные о состоянии активности, хранящиеся в onCreate()
, сначала вы должны сохранить данные в savedInstanceState , переопределив SaveInstanceState(Bundle savedInstanceState)
способ.
Когда активность уничтожает SaveInstanceState(Bundle savedInstanceState)
вызывается метод, и там вы сохраняете данные, которые хотите сохранить.И вы получаете то же самое в onCreate()
при перезапуске activity. (savedInstanceState не будет иметь значения null, поскольку вы сохранили в нем некоторые данные до того, как activity будет уничтожена)
Простым и быстрым способом решения этой проблемы является использование Ледоруб
Во-первых, настройте библиотеку в 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);
}
}
Это работает для действий, фрагментов или любого объекта, которому необходимо сериализовать свое состояние в пакете (напримерпросмотрщики миномета)
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
}
Не уверен, одобряют ли мое решение или нет, но я использую связанный сервис для сохранения состояния ViewModel.Сохраняете ли вы его в памяти службы или сохраняете и извлекаете из базы данных SQLite, зависит от ваших требований.Это то, что делают сервисы любого типа, они предоставляют такие услуги, как поддержание состояния приложения и абстрактная общая бизнес-логика.
Из-за ограничений памяти и обработки, присущих мобильным устройствам, я обрабатываю представления Android аналогично веб-странице.Страница не поддерживает состояние, это чисто компонент уровня представления, единственной целью которого является представление состояния приложения и прием пользовательского ввода.Последние тенденции в архитектуре веб-приложений используют устаревший шаблон Model, View, Controller (MVC), где страница является представлением, данные домена - моделью, а контроллер находится за веб-службой.Тот же шаблон может быть использован и в Android с таким видом, ну ...представление, модель - это данные вашего домена, а контроллер реализован как служба, привязанная к Android.Всякий раз, когда вы хотите, чтобы представление взаимодействовало с контроллером, привяжитесь к нему при запуске / возобновлении и отмените привязку при остановке / паузе.
Такой подход дает вам дополнительный бонус в виде применения принципа разделения задач, поскольку вся бизнес-логика вашего приложения может быть перенесена в ваш сервис, что уменьшает дублирование логики в нескольких представлениях и позволяет представлению применять другой важный принцип проектирования - единую ответственность.
Теперь Android предоставляет Модели просмотра для сохранения состояния вы должны попытаться использовать это вместо saveInstanceState .
На чем экономить, а на чем нет?
Вы когда-нибудь задумывались, почему текст в EditText
автоматически сохраняется при смене ориентации?Что ж, этот ответ для вас.
Когда экземпляр Действия уничтожается и Система воссоздает новый экземпляр (например, изменение конфигурации).Он пытается воссоздать его, используя набор сохраненных данных о старом состоянии активности (состояние экземпляра).
Состояние экземпляра - это совокупность ключ-значение пары, хранящиеся в Bundle
объект.
По умолчанию система сохраняет объекты просмотра, например, в Пакете.
- Текст в
EditText
- Положение прокрутки в
ListView
, и т.д.
Если вам нужна другая переменная для сохранения как части состояния экземпляра, вам следует ПЕРЕОПРЕДЕЛЕНИЕ onSavedInstanceState(Bundle savedinstaneState)
способ.
Например, int currentScore
в игровой активности
Более подробная информация о onSavedInstanceState(пакет savedinstaneState) при сохранении данных
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
Так что по ошибке, если вы забудете позвонить
super.onSaveInstanceState(savedInstanceState);
поведение по умолчанию не будет работать, т.е. текст в EditText сохраняться не будет.
Что выбрать для восстановления состояния Активности?
onCreate(Bundle savedInstanceState)
или
onRestoreInstanceState(Bundle savedInstanceState)
Оба метода получают один и тот же объект Bundle, поэтому на самом деле не имеет значения, где вы пишете свою логику восстановления.Единственная разница заключается в том, что в onCreate(Bundle savedInstanceState)
метод, в котором вам нужно будет указать нулевую проверку, хотя в последнем случае это не требуется.В других ответах уже есть фрагменты кода.Вы можете обратиться к ним.
Более подробная информация об onRestoreInstanceState(пакет savedinstaneState)
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from the saved instance
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}
Всегда звони
super.onRestoreInstanceState(savedInstanceState);
таким образом, Система восстановит иерархию представлений по умолчанию
Бонус
Тот Самый onSaveInstanceState(Bundle savedInstanceState)
вызывается системой только тогда, когда пользователь намеревается вернуться к выполнению Действия.Например, вы используете приложение X, и вдруг вам звонят.Вы переходите в приложение вызывающего абонента и возвращаетесь в приложение X.В этом случае onSaveInstanceState(Bundle savedInstanceState)
будет вызван метод.
Но учтите это, если пользователь нажмет кнопку "Назад".Предполагается, что пользователь не намерен возвращаться к своему Действию, следовательно, в данном случае onSaveInstanceState(Bundle savedInstanceState)
не будет вызван системой.Дело в том, что вы должны учитывать все сценарии при сохранении данных.
Релевантные ссылки:
Демонстрация поведения по умолчанию
Официальная документация Android.
Котлин
Вы должны переопределить 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
}
У меня есть идея получше.Было бы лучше сохранить ваши данные, не вызывая onCreate повторно.Вы можете отключить его от активности при смене ориентации.
В вашем манифесте:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
Редактировать: это решение не будет работать, когда Android завершит процесс из-за нехватки памяти.если вам нужно сохранить данные наверняка, в этом случае вам нужно использовать savedInstanceState , в противном случае я предлагаю использовать этот способ.классный и простой в использовании!