Я работаю над платформой 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);
}
}
Я думал, что этого будет достаточно для простейшего случая, но он всегда отвечает первым сообщением, независимо от того, как я ухожу из приложения.
Я уверен, что решение такое же простое, как переопределение onPause или что-то в этом роде, но я копался в документации около 30 минут и не нашел ничего очевидного.
Вы явно разрушаете свою активность, - как вы сказали, уходя от нее, например, нажимая назад. Фактически, сценарий, в котором используется это «savedInstanceState», - это когда Android уничтожает вашу активность для отдыха. Для примера: если вы измените язык своего телефона во время выполнения операции (и поэтому необходимо загрузить различные ресурсы из вашего проекта). Другой очень распространенный сценарий - когда вы поворачиваете телефон в сторону, чтобы действие воссоздалось и отображалось в альбомной ориентации.
Чтобы получить второе сообщение, включите «Не сохранять действия» в настройках разработчика. Нажмите кнопку домой и вернитесь из последних.
это очень полезно developer.android.com/training/basics/activity-lifecycle/…
вы можете сделать это с помощью: onSaveInstanceState (Bundle savedInstanceState)
вам следует изучить другие варианты постоянного хранения данных в памяти. Например, почему бы не объявить переменную статической или даже статической отдельно?
savedInstanceState предназначен только для сохранения состояния, связанного с текущим экземпляром Activity, например, текущей навигации или информации о выборе, так что, если Android уничтожает и воссоздает Activity, оно может вернуться, как было раньше. См. Документацию для onCreate и onSaveInstanceState
Для более долгоживущего состояния рассмотрите возможность использования базы данных SQLite, файла или настроек. См. Сохранение постоянного состояния.
Когда saveInstanceState == null, а когда не null?
saveInstanceState имеет значение null, когда система создает новый экземпляр вашей Activity, и не null при его восстановлении.
... что поднимает вопрос когда, нужно ли системе создавать новый экземпляр Activity. Некоторые способы выхода из приложения не создают пакет, поэтому необходимо создать новый экземпляр. Это основная проблема; это означает, что нельзя полагаться о существовании пакета, и необходимо использовать некоторые альтернативные средства постоянного хранения. Преимущество onSave / onRestoreInstanceState заключается в том, что это механизм, который система может выполнять резко, не потребляя много системных ресурсов. Так что хорошо поддерживать это, а также иметь постоянное хранилище для более плавного выхода из приложения.
Вам нужно переопределить 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");
}
Или из фрагмента.
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
double myDouble = savedInstanceState.getDouble("myDouble");
int myInt = savedInstanceState.getInt("MyInt");
String myString = savedInstanceState.getString("MyString");
}
Обычно вы используете этот метод для хранения значений экземпляра для вашего приложения (выборки, несохраненный текст и т. д.).
Есть ли шанс, что это работает на телефоне, но не в эмуляторе? Кажется, я не могу получить значение saveInstanceState, отличное от NULL.
У меня есть ArrayList точек, как сохранить все точки в этом списке массивов, а затем восстановить их?
ВНИМАНИЕ: вам нужно вызвать super.onSaveInstanceState (savedInstanceState) перед добавлением ваших значений в Bundle, иначе они будут уничтожены при этом вызове (Droid X Android 2.2).
@Shaun, похоже, вы узнали после публикации этого комментария, что что-то эквивалентное onRestoreInstanceState также доступно для фрагментов.
У меня проблемы с этим на стоковом X8. OnSaveInstanceState НИКОГДА не вызывается. Ни при возврате на главный экран, ни при нажатии кнопки возврата для перехода к предыдущему действию, ни в случае выхода из приложения. Вызывается состояние "При уничтожении", но не состояние onSaveInstance.
Осторожно: в официальной документации указано, что вы должны сохранять важную информацию в методе onPause, поскольку метод onsaveinstance не является частью жизненного цикла Android. developer.android.com/reference/android/app/Activity.html
Очень информативный ответ. У меня есть один вопрос. Если мы сможем воссоздать то, что в последний раз было в onCreate, когда мы должны использовать onRestoreInstanceState?
@Andy, это когда пользователь находится в середине некоторой активности (например, пишет в форме), и действие уничтожается (например, из-за изменения ориентации устройства). Этот метод позволяет восстановить состояние формы, чтобы пользователь не начал с нуля.
Этот факт фактически делает onSaveInstanceState практически бесполезным, за исключением случая изменения ориентации экрана. Практически во всех других случаях вы никогда не сможете на него положиться, и вам нужно будет вручную сохранить состояние пользовательского интерфейса в другом месте. Или предотвратить уничтожение вашего приложения, переопределив поведение кнопки BACK. Я вообще не понимаю, зачем они вообще это реализовали именно так. Совершенно не интуитивно понятно. И у вас не может быть этого Bundle, который система дает вам для сохранения, кроме как в этом конкретном методе.
@schlingel +1 за упоминание о том, что onSaveInstanceState () не является частью жизненного цикла. Думаю, при нажатии кнопки "Назад" не вызывается. Вызывается только при нажатии кнопки «Домой».
@ Trojan.ZBOT, когда есть сохраненное состояние экземпляра, оно не равно нулю, а когда нет, оно равно нулю ;-)
@schlingel - да. но когда мы получим сохраненное состояние экземпляра? Всегда ? На ротации телефона? При нажатии кнопки домой? Так далее ? это мой вопрос. Я новичок, поэтому не знаю этих вещей.
leason1 в классе android - реализовать все «похожие» методы жизненного цикла и использовать отладчик или просто System.out, чтобы проверить, когда именно вызывается этот метод. Это важно, если вы хотите создавать приложения для Android, иначе ваши приложения будут давать сбой без очевидной причины, а только время от времени, поэтому вы легко опубликуете его, даже не заметив этого.
Обратите внимание, что сохранение / восстановление состояния UI в / из Bundle - это автоматически, заботящийся о для View, которым были присвоены идентификаторы. Из документации onSaveInstanceState: «Реализация по умолчанию заботится о большей части состояния пользовательского интерфейса для каждого экземпляра за вас, вызывая onSaveInstanceState() для каждого представления в иерархии, имеющего идентификатор, и сохраняя идентификатор текущего сфокусированного представления (все из которых восстанавливается стандартной реализацией onRestoreInstanceState(Bundle)) "
@ataulm, почему onCreate не подходит для этого случая? onCreate также используется для изменения ориентации.
@jkschneider В разработчик Android super.onSaveInstanceState (savedInstanceState) вызывается после добавления значений в Bundle. Что правильно?
Я согласен с @Shomu вот ссылка на документацию developer.android.com/topic/libraries/architecture
onSaveInstanceState вызывается, когда системе требуется память и завершается работа приложения. Он не вызывается, когда пользователь просто закрывает приложение. Поэтому я думаю, что состояние приложения также должно быть сохранено в onPause. Его следует сохранить в каком-то постоянном хранилище, например Preferences или Sqlite.
Извините, это не совсем так. onSaveInstanceState вызывается перед повторным выполнением действия. т.е. каждый раз, когда пользователь поворачивает устройство. Он предназначен для хранения переходных состояний просмотра. Когда Android принудительно закрывает приложение, onSaveInstanceState фактически НЕ вызывается (вот почему это небезопасно для хранения важных данных приложения). onPause, однако, гарантированно будет вызван до того, как действие будет уничтожено, поэтому его следует использовать для хранения постоянной информации в настройках или Squlite. Правильный ответ, неправильные причины.
На самом деле onSaveInstanceState() вызывается, когда Activity переходит в фоновый режим.
Цитата из документов: «Этот метод вызывается перед тем, как действие может быть прекращено, чтобы, когда оно вернется в будущем, оно могло восстановить свое состояние». Источник
Обратите внимание, что НЕТ безопасно использовать onSaveInstanceState и onRestoreInstanceStateдля постоянных данных, согласно документации по состояниям активности в http://developer.android.com/reference/android/app/Activity.html.
В документе говорится (в разделе «Жизненный цикл деятельности»):
Note that it is important to save persistent data in
onPause()instead ofonSaveInstanceState(Bundle)because the later is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.
Другими словами, поместите свой код сохранения / восстановления для постоянных данных в onPause() и onResume()!
РЕДАКТИРОВАТЬ: Для дальнейшего пояснения, вот документация onSaveInstanceState():
This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, activity A will have a chance to save the current state of its user interface via this method so that when the user returns to activity A, the state of the user interface can be restored via
onCreate(Bundle)oronRestoreInstanceState(Bundle).
Просто для придирки: это тоже небезопасно. Это просто зависит от того, что вы хотите сохранить и как долго, что @Bernard не совсем понятно в его исходном вопросе. InstanceState идеально подходит для сохранения текущего состояния пользовательского интерфейса (данные, введенные в элементы управления, текущие позиции в списках и т. д.), Тогда как пауза / возобновление - единственная возможность для длительного постоянного хранения.
Это должно быть отклонено. Небезопасно использовать в (Save | Restore) InstanceState методы жизненного цикла (т.е. делать в них что-нибудь еще, кроме сохранения / восстановления состояния). Они отлично подходят для сохранения / восстановления состояния. Кроме того, как вы хотите сохранить / восстановить состояние в onPause и onResume? Вы не получаете Bundles в тех методах, которые можете использовать, поэтому вам придется использовать какое-то другое сохранение состояния в базах данных, файлах и т. д., Что глупо.
Мы не должны голосовать против этого человека, по крайней мере, он приложил усилия, чтобы просмотреть документацию, и я думаю, что мы, люди, здесь для того, чтобы на самом деле создать осведомленное сообщество и помогать друг другу, а не ПРОТИВ ГОЛОСА. Итак, я голосую за усилия, и я прошу вас, люди, не голосовать против, а голосовать за или не голосовать ... этот человек устраняет путаницу, которую хотелось бы иметь при просмотре документации. 1 голос за :)
Я не думаю, что этот ответ заслуживает отрицательной оценки. По крайней мере, он попытался ответить и процитировал отрывок из doco.
Этот ответ абсолютно правильный и заслуживает голоса "ЗА", а не "против"! Разрешите пояснить разницу между состояниями для тех парней, которые этого не видят. Состояние графического интерфейса пользователя, такое как выбранные переключатели и некоторый текст в поле ввода, гораздо менее важно, чем состояние данных, например записи, добавленные в список, отображаемый в ListView. Последний должен быть сохранен в базе данных в onPause, потому что это единственный гарантированный вызов. Если вы вместо этого поместите его в onSaveInstanceState, вы рискуете потерять данные, если они не будут вызваны. Но если выбор радиокнопки не сохраняется по той же причине - это не беда.
@stevemoseley: ты ошибаешься. Потому что onSaveInstanceState вызывается для получения состояния каждого экземпляра из действия перед его уничтожением. Если действие не прекращается (оно помещается в фоновый режим), то нет необходимости хранить ЗНАЧЕНИЯ ЭКЗАМЕНА в onPause (), поскольку эти значения не удаляются из памяти. Когда приложение выводится на передний план, значения доступны для использования. НО в случае, если система закроет приложение для освобождения памяти, значения экземпляра будут уничтожены, и onPause не сможет быть использован для их получения. Таким образом, имеет смысл использовать onSaveInstanceState для хранения значений экземпляра, а не onPause.
@Steve Moseley Нет. onSaveInstanceState () используется для значений преходящий - когда вам не нужно сохранять значения настойчивый. Это зависит от вашей ситуации. В любом случае это плохой ответ. -1 - Пожалуйста, не голосуйте только потому, что он процитировал часть из документации. Если вы потратите время на что-то бесполезное, я дам вам -1.
вы можете отредактировать этот пост с примером, как сохранить в onPause ()?
@laplasz - для onPause() вам нужно будет сохранить в SharedPreferences, в базе данных или в облаке. См. Примеры: http://developer.android.com/guide/topics/data/data-storage .html # pref. Они используют onStop(), но код тот же.
Я отредактировал этот ответ, чтобы уточнить, что onSaveInstanceState() и его друзья небезопасны, только если вы попытаетесь использовать их для сохранения постоянных данных. Надеюсь, это сделает ответ достаточно точным и информативным.
@VVK - частично не согласен. Некоторые способы выхода из приложения не запускают onSaveInstanceState (oSIS). Это ограничивает полезность oSIS. Его стоит поддерживать для минимальных ресурсов ОС, но если приложение хочет вернуть пользователя в состояние, в котором он находился, независимо от того, как приложение было закрыто, вместо этого необходимо использовать подход постоянного хранения. Я использую onCreate для проверки пакета, и если он отсутствует, проверяю постоянное хранилище. Это централизует принятие решений. Я могу восстановиться после сбоя, или выхода с помощью кнопки «Назад», или пользовательского пункта меню «Выход», или вернуться к экрану, на котором пользователь был включен много дней спустя.
ОБНОВЛЕНИЕ: с новой версией android nougat 24+, когда пакет saveInstance пересекает предел IPC в 1 МБ, будет выброшено исключение RuntimeException, которое потенциально может привести к сбою приложения (в отличие от журнала предупреждений в предыдущих выпусках). Если вы сохраняете большие данные в сохраненном состоянии, подумайте об альтернативах, таких как sqlite / sharedpreferences. Обходной путь для предотвращения сбоя - использование targetdkversion 23 и ниже.
Мой коллега написал статью, в которой объясняется состояние приложения на устройствах 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();
}
В статье @ MartinBelcher-Eigo говорится о данных в SharedPreferences: «Эти данные записываются в базу данных на устройстве ..» Я считаю, что данные хранятся в файле в каталоге приложения файловой системы.
Данные @Tom SharefPrefs записываются в файл xml. XML - это своего рода база данных? Я бы сказал, что это так;)
Насколько я понимаю, сохранение состояния - это в лучшем случае путаница. Если вам нужно сохранить постоянные данные, просто используйте базу данных 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;
Потому что загрузка базы данных SQLite занимает слишком много времени, учитывая, что это критический путь к отображению пользовательского интерфейса приложения для пользователя. Я на самом деле не рассчитал время, поэтому я рад, что меня поправили, но, конечно, загрузка и открытие файла базы данных не будут быстрыми?
Большое спасибо за решение, которое новичок может вырезать, вставить в свое приложение и сразу же использовать! @Tom Что касается скорости, то для хранения 1000 пар требуется около семи секунд, но вы можете сделать это в AsyncTask. Однако вам нужно добавить finally {cursor.close ()}, иначе при этом произойдет сбой из-за утечки памяти.
Я наткнулся на это, и, хотя это кажется аккуратным, я не решаюсь попробовать использовать это в Google Glass, это устройство, над которым я работаю в последнее время.
Оба метода полезны и действительны, и оба лучше всего подходят для разных сценариев:
onSaveInstanceState() и onRestoreInstanceState().Если вы сохраняете данные состояния на постоянной основе, их можно повторно загрузить в onResume() или onCreate() (или фактически при любом вызове жизненного цикла). Это может быть, а может и не быть желаемым поведением. Если вы храните его в комплекте в InstanceState, то он временный и подходит только для хранения данных для использования в одном и том же пользовательском «сеансе» (я использую термин «сеанс» вольно), но не между «сеансами».
Дело не в том, что один подход лучше другого, как и все остальное, просто важно понять, какое поведение вам требуется, и выбрать наиболее подходящий подход.
onSaveInstanceState() для временных данных (восстановлен в onCreate() / onRestoreInstanceState()), onPause() для постоянных данных (восстановлен в onResume()).
Из технических ресурсов Android:
onSaveInstanceState() is called by Android if the Activity is being stopped and may be killed before it is resumed! This means it should store any state necessary to re-initialize to the same condition when the Activity is restarted. It is the counterpart to the onCreate() method, and in fact the savedInstanceState Bundle passed in to onCreate() is the same Bundle that you construct as outState in the onSaveInstanceState() method.
onPause() and onResume() are also complimentary methods. onPause() is always called when the Activity ends, even if we instigated that (with a finish() call for example). We will use this to save the current note back to the database. Good practice is to release any resources that can be released during an onPause() as well, to take up less resources when in the passive state.
Думаю, я нашел ответ. Позвольте мне простыми словами рассказать о том, что я сделал:
Предположим, у меня есть два действия, 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 в том же состоянии, в котором мы вышли ранее.
Я считаю, что это ответ, и мне это подходит. Поправьте меня, если я ошибаюсь.
@bagusflyer, если быть более конкретным ??? Ваш комментарий бесполезен, и никто не может вам помочь, основываясь на этом.
Это ответ на другую ситуацию: два действия в одном приложении. OP - это уход приложение (например, кнопка домой или другие средства для переключения на другое приложение).
Это именно тот ответ, который я искал!
Между тем я вообще больше не использую
Bundle savedInstanceState & Co
Жизненный цикл для большинства видов деятельности слишком сложен и не является необходимым.
И Google заявляет, что это даже НЕ надежно.
Мой способ - немедленно сохранять любые изменения в настройках:
SharedPreferences p;
p.edit().put(..).commit()
В некотором смысле SharedPreferences работают так же, как Bundles. И естественно, сначала такие значения нужно читать из предпочтений.
В случае сложных данных вы можете использовать SQLite вместо предпочтений.
При применении этой концепции действие просто продолжает использовать последнее сохраненное состояние, независимо от того, было ли это первоначальное открытие с промежуточными перезагрузками или повторное открытие из-за заднего стека.
Методы onSaveInstanceState(bundle) и onRestoreInstanceState(bundle) полезны для сохранения данных просто при повороте экрана (изменение ориентации).
Они не годятся даже при переключении между приложениями (поскольку метод onSaveInstanceState() вызывается, но onCreate(bundle) и onRestoreInstanceState(bundle) больше не вызываются.
Для большей настойчивости используйте общие настройки. прочтите эту статью
В вашем случае onCreate и onRestoreInstanceState не вызываются, потому что Activity вообще не уничтожается при переключении приложений, поэтому нет необходимости что-либо восстанавливать. Android вызывает onSaveInstanceState на случай, если действие будет уничтожено позже (что происходит со 100% уверенностью при повороте экрана, потому что вся конфигурация устройства изменилась, и действие необходимо воссоздать с нуля).
Это классический «подводный камень» разработки под Android. Здесь есть две проблемы:
Просматривая все эти темы, я подозреваю, что большую часть времени разработчики говорят об этих двух разных проблемах одновременно ... отсюда вся путаница и сообщения о том, что «это не работает для меня».
Во-первых, чтобы прояснить «предполагаемое» поведение: onSaveInstance и onRestoreInstance хрупкие и только для переходного состояния. Предполагаемое использование (afaict) состоит в том, чтобы обрабатывать воссоздание активности при повороте телефона (изменение ориентации). Другими словами, предполагаемое использование - это когда ваша Activity все еще логически «наверху», но все еще должна быть восстановлена системой. Сохраненный пакет не сохраняется вне процесса / памяти / gc, поэтому вы не можете полагаться на него, если ваша деятельность переходит в фоновый режим. Да, возможно, память вашего Activity переживет свое путешествие на задний план и выйдет из 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 в этом ответе:
Поведение при нажатии клавиши Home
ОБНОВЛЕНИЕ июнь 2013 г.: Несколько месяцев спустя я наконец нашел «правильное» решение. Вам не нужно самостоятельно управлять какими-либо флагами startApp с отслеживанием состояния, вы можете обнаружить это из фреймворка и соответствующим образом выполнить залог. Я использую это в начале своего 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;
}
}
Моя проблема заключалась в том, что мне требовалась настойчивость только в течение всего жизненного цикла приложения (т.е. однократное выполнение, включая запуск других вспомогательных действий в одном приложении, вращение устройства и т. Я пробовал различные комбинации приведенных выше ответов, но не во всех ситуациях получал то, что хотел. В конце концов, у меня сработало получение ссылки на savedInstanceState во время onCreate:
mySavedInstanceState=savedInstanceState;
и использовать это, чтобы получить содержимое моей переменной, когда оно мне нужно, в следующих строках:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
Я использую onSaveInstanceState и onRestoreInstanceState, как было предложено выше, но думаю, я мог бы также или в качестве альтернативы использовать свой метод для сохранения переменной при ее изменении (например, с помощью putBoolean)
Чтобы напрямую ответить на исходный вопрос. saveInstancestate имеет значение null, потому что ваше действие никогда не создается повторно.
Ваше действие будет воссоздано с пакетом состояний только в следующих случаях:
Android уничтожает фоновые действия при нехватке памяти или после того, как они находились в фоновом режиме в течение длительного периода времени.
При тестировании вашего примера hello world есть несколько способов выйти и вернуться к Activity.
В большинстве случаев, если вы просто нажимаете кнопку «Домой», а затем снова запускаете приложение, повторно создавать действие не требуется. Он уже существует в памяти, поэтому onCreate () не будет вызываться.
В разделе «Настройки» -> «Параметры разработчика» есть опция «Не сохранять действия». Когда он включен, Android всегда уничтожает действия и воссоздает их, когда они находятся в фоновом режиме. Это отличный вариант, чтобы оставить его включенным при разработке, потому что он имитирует наихудший сценарий. (Устройство с низким объемом памяти, постоянно повторяющее ваши действия).
Другие ответы ценны тем, что они учат вас правильным способам хранения состояния, но я не чувствовал, что они действительно ответили, ПОЧЕМУ ваш код работает не так, как вы ожидали.
Чтобы уменьшить количество шаблонов, я использую следующие 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.
Есть два основных способа реализовать это изменение.
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);
}
Хотя принятый ответ правильный, существует более быстрый и простой способ сохранить состояние Activity на 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.
Простое решение этой проблемы - использование Ледоруб
Сначала установите библиотеку в 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);
}
}
Он работает для Activity, Fragments или любого объекта, которому необходимо сериализовать свое состояние в Bundle (например, 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
}
@ralphspoon да, он работает для фрагментов и пользовательского представления. Пожалуйста, проверьте пример кода. Я отредактировал свой ответ. Я предлагаю вам найти официальную документацию здесь github.com/frankiesardo/icepick, чтобы найти больше примеров кода.
@ChetanMehra, вы имеете в виду настраиваемый класс просмотра, верно? Если это настраиваемый вид, мы можем переопределить onSaveInstanceState и onRestoreInstanceState, как в приведенном выше примере CustomView.
Я имею в виду объект класса внутри класса представления, например: класс CustomView расширяет View {@State ClassA a;} или класс CustomView расширяет View {@ State Внутренний класс {}}
@THANNPhearum Можно задать еще один вопрос?
Понятно. Если это так, ваш ClassA должен быть Parcelable. Как уже упоминалось, он работает для действий, фрагментов или любого объекта, которому необходимо сериализовать свое состояние в Bundle.
Не уверен, что мое решение осуждается или нет, но я использую связанную службу для сохранения состояния ViewModel. Храните ли вы его в памяти в службе или сохраняете и извлекаете из базы данных SQLite, зависит от ваших требований. Это то, что делают сервисы любого типа, они предоставляют такие сервисы, как поддержание состояния приложения и абстрактную общую бизнес-логику.
Из-за ограничений памяти и обработки, присущих мобильным устройствам, я отношусь к представлениям Android так же, как к веб-странице. Страница не поддерживает состояние, это просто компонент уровня представления, единственная цель которого - представить состояние приложения и принять ввод пользователя. Последние тенденции в архитектуре веб-приложений основаны на использовании устаревшего шаблона Модель, Представление, Контроллер (MVC), где страница - это представление, данные домена - это модель, а контроллер находится за веб-службой. Тот же самый шаблон может быть использован в Android с представлением, ну ... представлением, модель - это данные вашего домена, а контроллер реализован как привязанная к Android служба. Всякий раз, когда вы хотите, чтобы представление взаимодействовало с контроллером, привязывайтесь к нему при запуске / возобновлении и отменяйте привязку при остановке / паузе.
Этот подход дает вам дополнительный бонус в виде обеспечения соблюдения принципа проектирования разделения проблем в том, что все бизнес-логику вашего приложения можно перенести в вашу службу, что сокращает дублирование логики в нескольких представлениях и позволяет представлению обеспечивать соблюдение другого важного принципа проектирования - единой ответственности.
Чтобы получить данные о состоянии активности, хранящиеся в onCreate(), сначала необходимо сохранить данные в savedInstanceState, переопределив метод SaveInstanceState(Bundle savedInstanceState).
Когда вызывается метод Activity destroy SaveInstanceState(Bundle savedInstanceState), вы сохраняете данные, которые хотите сохранить. И вы получите то же самое в onCreate() при перезапуске активности. (SavedInstanceState не будет нулевым, поскольку вы сохранили в нем некоторые данные до того, как действие будет уничтожено)
Когда действие создается, вызывается метод onCreate ().
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
saveInstanceState - это объект класса Bundle, который впервые имеет значение NULL, но при его воссоздании содержит значения. Чтобы сохранить состояние Activity, вы должны переопределить 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 против onPause, восточная стоимость против саги западной стоимости)
@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 checkpersistent 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
Код Котлина:
спасти:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState.apply {
putInt("intKey", 1)
putString("stringKey", "String Value")
putParcelable("parcelableKey", parcelableObject)
})
}
а затем в onCreate() или onRestoreInstanceState()
val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable
Добавьте значения по умолчанию, если вы не хотите использовать Optionals
Теперь Android предоставляет ViewModels для сохранения состояния, вы должны попробовать использовать это вместо saveInstanceState.
Это неправда. Из документации: «В отличие от сохраненного состояния экземпляра, модели ViewModel уничтожаются во время смерти процесса, инициированного системой. Вот почему вы должны использовать объекты ViewModel в сочетании с onSaveInstanceState () (или какой-либо другой персистентностью диска), сохраняя идентификаторы в saveInstanceState для облегчения просмотра модели перезагружают данные после смерти системы ".
Просто столкнулся с этим с изменением разрешений в фоновом режиме.
Я согласен, из документа «если вам нужно обработать смерть процесса, инициированного системой, вы можете использовать onSaveInstanceState () в качестве резервной копии».
Вы когда-нибудь задумывались, почему текст в EditText автоматически сохраняется при изменении ориентации? Что ж, этот ответ для вас.
Когда экземпляр Activity уничтожается, а Система воссоздает новый экземпляр (например, изменение конфигурации). Он пытается воссоздать его, используя набор сохраненных данных старого состояния активности (состояние экземпляра).
Состояние экземпляра - это набор пар ключ-значение, хранящихся в объекте Bundle.
By default System saves the View objects in the Bundle for example.
EditTextListView и т. д.Если вам нужно сохранить другую переменную как часть состояния экземпляра, вам следует использовать метод ПЕРЕОПРЕДЕЛЕНИЕonSavedInstanceState(Bundle savedinstaneState).
Например, int currentScore в GameActivity
@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);
}
So by mistake if you forget to call
super.onSaveInstanceState(savedInstanceState);the default behavior will not work ie Text in EditText will not save.
onCreate(Bundle savedInstanceState)
ИЛИ ЖЕ
onRestoreInstanceState(Bundle savedInstanceState)
Оба метода получают один и тот же объект Bundle, поэтому на самом деле не имеет значения, где вы пишете логику восстановления. Единственное отличие состоит в том, что в методе onCreate(Bundle savedInstanceState) вам нужно будет выполнить нулевую проверку, хотя в последнем случае она не нужна. В других ответах уже есть фрагменты кода. Вы можете направить их.
@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);
}
Always call
super.onRestoreInstanceState(savedInstanceState);so that System restore the View hierarchy by default
onSaveInstanceState(Bundle savedInstanceState) вызывается системой только тогда, когда пользователь намеревается вернуться к Activity. Например, вы используете приложение X, и внезапно вам звонят. Вы переходите к приложению вызывающего абонента и возвращаетесь к приложению X. В этом случае будет вызван метод onSaveInstanceState(Bundle savedInstanceState).
Но учтите это, если пользователь нажимает кнопку «Назад». Предполагается, что пользователь не намерен возвращаться к Activity, поэтому в этом случае 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
}
вы можете использовать Live Data и View Model для Lifecycle Handel от JetPack. см. эту ссылку:
https://developer.android.com/topic/libraries/architecture/livedata
Вместо этого вы должны использовать ViewModel, который сохранит данные до жизненного цикла активности.
Есть способ заставить Android сохранять состояния без реализации какого-либо метода. Просто добавьте эту строку в свое объявление Manifest in Activity:
android:configChanges = "orientation|screenSize"
Должно получиться так:
<activity
android:name = ".activities.MyActivity"
android:configChanges = "orientation|screenSize">
</activity>
Здесь вы можете найти больше информации об этой собственности.
Рекомендуется позволить Android справиться с этим за вас, чем ручная обработка.
Это не имеет ничего общего с сохранением состояния, вы просто отказываетесь от изменения ориентации, имейте в виду, что приложение может быть перезапущено, приостановлено и возобновлено в любое время для разных событий.
Этот ответ для тех, кто хочет сохранить состояние при изменении ориентации и хочет избежать понимания и реализации сложного способа.
честно говоря, я понимаю вашу точку зрения, я думаю, что большинство людей, которые борются за сохранение состояния, используют фрагменты, потому что действия фактически сохраняют статистику компонентов пользовательского интерфейса, пока у них есть идентификатор, но фрагменты более особенные, я использовал фрагменты однажды, но я никогда не буду использовать их снова статистика инстанса сохранения была болью, чтобы иметь дело с
Теперь имеет смысл проделать 2 способа в модели представления. если вы хотите сохранить первый как сохраненный экземпляр: Вы можете добавить параметр состояния в модель представления следующим образом https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java
или вы можете сохранить переменные или объект в модели представления, в этом случае модель представления будет удерживать жизненный цикл до тех пор, пока действие не будет уничтожено.
public class HelloAndroidViewModel extends ViewModel {
public Booelan firstInit = false;
public HelloAndroidViewModel() {
firstInit = false;
}
...
}
public class HelloAndroid extends Activity {
private TextView mTextView = null;
HelloAndroidViewModel viewModel = ViewModelProviders.of(this).get(HelloAndroidViewModel.class);
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
//Because even if the state is deleted, the data in the viewmodel will be kept because the activity does not destroy
if (!viewModel.firstInit){
viewModel.firstInit = true
mTextView.setText("Welcome to HelloAndroid!");
}else{
mTextView.setText("Welcome back.");
}
setContentView(mTextView);
}
}
вы правы, но эта библиотека все еще находится в стадии выпуска, поэтому я думаю, нам следует подождать ...
Вам нужно переопределить onSaveInstanceState (Bundle savedInstanceState) и записать значения состояния приложения, которые вы хотите изменить, в параметр Bundle
Котлин Решение:
Для сохранения пользовательского класса в 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")
}
В 2020 году у нас есть некоторые изменения:
Если вы хотите, чтобы Activity восстановил свое состояние после завершения процесса и его повторного запуска, вы можете использовать функцию «сохраненное состояние». Раньше вам нужно было переопределить два метода в Activity: onSaveInstanceState и onRestoreInstanceState. Вы также можете получить доступ к восстановленному состоянию в методе onCreate. Точно так же в Fragment у вас есть доступный метод onSaveInstanceState (а восстановленное состояние доступно в методах onCreate, onCreateView и onActivityCreated).
Начиная с AndroidX SavedState 1.0.0, который является зависимостью AndroidX Activity и Фрагмент AndroidX, вы получаете доступ к SavedStateRegistry. Вы можете получить SavedStateRegistry из Activity / Fragment, а затем зарегистрировать свой SavedStateProvider:
class MyActivity : AppCompatActivity() {
companion object {
private const val MY_SAVED_STATE_KEY = "MY_SAVED_STATE_KEY "
private const val SOME_VALUE_KEY = "SOME_VALUE_KEY "
}
private lateinit var someValue: String
private val savedStateProvider = SavedStateRegistry.SavedStateProvider {
Bundle().apply {
putString(SOME_VALUE_KEY, someValue)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedStateRegistry.registerSavedStateProvider(MY_SAVED_STATE_KEY, savedStateProvider)
someValue = savedStateRegistry.consumeRestoredStateForKey(MY_SAVED_STATE_KEY)?.getString(SOME_VALUE_KEY) ?: ""
}
}
Как видите, SavedStateRegistry заставляет вас использовать ключ для ваших данных. Это может предотвратить повреждение ваших данных другим SavedStateProvider, подключенным к тому же Activity/Fragment.Также вы можете извлечь свой SavedStateProvider в другой класс, чтобы он работал с вашими данными, используя любую абстракцию, которую вы хотите, и таким образом добиться чистого сохраненного поведения состояния. в вашем приложении.
использование 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
Когда saveInstanceState == null, а когда не null?