Как сохранить состояние активности с помощью сохранения состояния экземпляра?

Я работаю над платформой 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 минут и не нашел ничего очевидного.

Когда saveInstanceState == null, а когда не null?

Trojan.ZBOT 29.12.2013 04:50

Вы явно разрушаете свою активность, - как вы сказали, уходя от нее, например, нажимая назад. Фактически, сценарий, в котором используется это «savedInstanceState», - это когда Android уничтожает вашу активность для отдыха. Для примера: если вы измените язык своего телефона во время выполнения операции (и поэтому необходимо загрузить различные ресурсы из вашего проекта). Другой очень распространенный сценарий - когда вы поворачиваете телефон в сторону, чтобы действие воссоздалось и отображалось в альбомной ориентации.

villoren 03.01.2014 23:51

Чтобы получить второе сообщение, включите «Не сохранять действия» в настройках разработчика. Нажмите кнопку домой и вернитесь из последних.

Yaroslav Mytkalyk 07.02.2014 22:34

это очень полезно developer.android.com/training/basics/activity-lifecycle/…

Syed Raza Mehdi 14.07.2015 10:00

вы можете сделать это с помощью: onSaveInstanceState (Bundle savedInstanceState)

VahidHoseini 04.09.2016 07:35

вам следует изучить другие варианты постоянного хранения данных в памяти. Например, почему бы не объявить переменную статической или даже статической отдельно?

Miguel Silva 26.02.2021 11:54
2 704
6
817 330
35
Перейти к ответу Данный вопрос помечен как решенный

Ответы 35

savedInstanceState предназначен только для сохранения состояния, связанного с текущим экземпляром Activity, например, текущей навигации или информации о выборе, так что, если Android уничтожает и воссоздает Activity, оно может вернуться, как было раньше. См. Документацию для onCreate и onSaveInstanceState

Для более долгоживущего состояния рассмотрите возможность использования базы данных SQLite, файла или настроек. См. Сохранение постоянного состояния.

Когда saveInstanceState == null, а когда не null?

Trojan.ZBOT 30.12.2013 02:20

saveInstanceState имеет значение null, когда система создает новый экземпляр вашей Activity, и не null при его восстановлении.

Gabriel Câmara 15.01.2014 17:21

... что поднимает вопрос когда, нужно ли системе создавать новый экземпляр Activity. Некоторые способы выхода из приложения не создают пакет, поэтому необходимо создать новый экземпляр. Это основная проблема; это означает, что нельзя полагаться о существовании пакета, и необходимо использовать некоторые альтернативные средства постоянного хранения. Преимущество onSave / onRestoreInstanceState заключается в том, что это механизм, который система может выполнять резко, не потребляя много системных ресурсов. Так что хорошо поддерживать это, а также иметь постоянное хранилище для более плавного выхода из приложения.

ToolmakerSteve 19.09.2015 13:24
Ответ принят как подходящий

Вам нужно переопределить 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.

Adam Jack 19.11.2009 01:39

У меня есть ArrayList точек, как сохранить все точки в этом списке массивов, а затем восстановить их?

AZ_ 29.11.2010 10:09

ВНИМАНИЕ: вам нужно вызвать super.onSaveInstanceState (savedInstanceState) перед добавлением ваших значений в Bundle, иначе они будут уничтожены при этом вызове (Droid X Android 2.2).

jkschneider 13.04.2011 22:59

@Shaun, похоже, вы узнали после публикации этого комментария, что что-то эквивалентное onRestoreInstanceState также доступно для фрагментов.

hotshot309 17.01.2012 06:07

У меня проблемы с этим на стоковом X8. OnSaveInstanceState НИКОГДА не вызывается. Ни при возврате на главный экран, ни при нажатии кнопки возврата для перехода к предыдущему действию, ни в случае выхода из приложения. Вызывается состояние "При уничтожении", но не состояние onSaveInstance.

Andras Balázs Lajtha 27.02.2012 14:38

Осторожно: в официальной документации указано, что вы должны сохранять важную информацию в методе onPause, поскольку метод onsaveinstance не является частью жизненного цикла Android. developer.android.com/reference/android/app/Activity.html

schlingel 19.06.2012 11:40

Очень информативный ответ. У меня есть один вопрос. Если мы сможем воссоздать то, что в последний раз было в onCreate, когда мы должны использовать onRestoreInstanceState?

Andy 19.07.2012 11:55

@Andy, это когда пользователь находится в середине некоторой активности (например, пишет в форме), и действие уничтожается (например, из-за изменения ориентации устройства). Этот метод позволяет восстановить состояние формы, чтобы пользователь не начал с нуля.

ataulm 02.04.2013 10:35

Этот факт фактически делает onSaveInstanceState практически бесполезным, за исключением случая изменения ориентации экрана. Практически во всех других случаях вы никогда не сможете на него положиться, и вам нужно будет вручную сохранить состояние пользовательского интерфейса в другом месте. Или предотвратить уничтожение вашего приложения, переопределив поведение кнопки BACK. Я вообще не понимаю, зачем они вообще это реализовали именно так. Совершенно не интуитивно понятно. И у вас не может быть этого Bundle, который система дает вам для сохранения, кроме как в этом конкретном методе.

chakrit 05.07.2013 15:25

@schlingel +1 за упоминание о том, что onSaveInstanceState () не является частью жизненного цикла. Думаю, при нажатии кнопки "Назад" не вызывается. Вызывается только при нажатии кнопки «Домой».

Crocode 22.12.2013 04:21

@ Trojan.ZBOT, когда есть сохраненное состояние экземпляра, оно не равно нулю, а когда нет, оно равно нулю ;-)

schlingel 29.12.2013 12:25

@schlingel - да. но когда мы получим сохраненное состояние экземпляра? Всегда ? На ротации телефона? При нажатии кнопки домой? Так далее ? это мой вопрос. Я новичок, поэтому не знаю этих вещей.

Trojan.ZBOT 30.12.2013 02:19

leason1 в классе android - реализовать все «похожие» методы жизненного цикла и использовать отладчик или просто System.out, чтобы проверить, когда именно вызывается этот метод. Это важно, если вы хотите создавать приложения для Android, иначе ваши приложения будут давать сбой без очевидной причины, а только время от времени, поэтому вы легко опубликуете его, даже не заметив этого.

Srneczek 08.04.2015 15:22

Обратите внимание, что сохранение / восстановление состояния UI в / из Bundle - это автоматически, заботящийся о для View, которым были присвоены идентификаторы. Из документации onSaveInstanceState: «Реализация по умолчанию заботится о большей части состояния пользовательского интерфейса для каждого экземпляра за вас, вызывая onSaveInstanceState() для каждого представления в иерархии, имеющего идентификатор, и сохраняя идентификатор текущего сфокусированного представления (все из которых восстанавливается стандартной реализацией onRestoreInstanceState(Bundle)) "

Vicky Chijwani 25.06.2015 02:15

@ataulm, почему onCreate не подходит для этого случая? onCreate также используется для изменения ориентации.

Adam Johns 31.07.2015 19:23

@jkschneider В разработчик Android super.onSaveInstanceState (savedInstanceState) вызывается после добавления значений в Bundle. Что правильно?

Fivos 16.11.2015 16:57

Я согласен с @Shomu вот ссылка на документацию developer.android.com/topic/libraries/architecture

Shailesh 11.12.2018 03:51

onSaveInstanceState вызывается, когда системе требуется память и завершается работа приложения. Он не вызывается, когда пользователь просто закрывает приложение. Поэтому я думаю, что состояние приложения также должно быть сохранено в onPause. Его следует сохранить в каком-то постоянном хранилище, например Preferences или Sqlite.

Извините, это не совсем так. onSaveInstanceState вызывается перед повторным выполнением действия. т.е. каждый раз, когда пользователь поворачивает устройство. Он предназначен для хранения переходных состояний просмотра. Когда Android принудительно закрывает приложение, onSaveInstanceState фактически НЕ вызывается (вот почему это небезопасно для хранения важных данных приложения). onPause, однако, гарантированно будет вызван до того, как действие будет уничтожено, поэтому его следует использовать для хранения постоянной информации в настройках или Squlite. Правильный ответ, неправильные причины.

moveaway00 28.03.2012 08:06

На самом деле 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 of onSaveInstanceState(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) or onRestoreInstanceState(Bundle).

Просто для придирки: это тоже небезопасно. Это просто зависит от того, что вы хотите сохранить и как долго, что @Bernard не совсем понятно в его исходном вопросе. InstanceState идеально подходит для сохранения текущего состояния пользовательского интерфейса (данные, введенные в элементы управления, текущие позиции в списках и т. д.), Тогда как пауза / возобновление - единственная возможность для длительного постоянного хранения.

Pontus Gagge 24.06.2010 18:01

Это должно быть отклонено. Небезопасно использовать в (Save | Restore) InstanceState методы жизненного цикла (т.е. делать в них что-нибудь еще, кроме сохранения / восстановления состояния). Они отлично подходят для сохранения / восстановления состояния. Кроме того, как вы хотите сохранить / восстановить состояние в onPause и onResume? Вы не получаете Bundles в тех методах, которые можете использовать, поэтому вам придется использовать какое-то другое сохранение состояния в базах данных, файлах и т. д., Что глупо.

Felix 11.07.2010 14:10

Мы не должны голосовать против этого человека, по крайней мере, он приложил усилия, чтобы просмотреть документацию, и я думаю, что мы, люди, здесь для того, чтобы на самом деле создать осведомленное сообщество и помогать друг другу, а не ПРОТИВ ГОЛОСА. Итак, я голосую за усилия, и я прошу вас, люди, не голосовать против, а голосовать за или не голосовать ... этот человек устраняет путаницу, которую хотелось бы иметь при просмотре документации. 1 голос за :)

AZ_ 26.11.2010 08:13

Я не думаю, что этот ответ заслуживает отрицательной оценки. По крайней мере, он попытался ответить и процитировал отрывок из doco.

GSree 05.01.2011 07:54

Этот ответ абсолютно правильный и заслуживает голоса "ЗА", а не "против"! Разрешите пояснить разницу между состояниями для тех парней, которые этого не видят. Состояние графического интерфейса пользователя, такое как выбранные переключатели и некоторый текст в поле ввода, гораздо менее важно, чем состояние данных, например записи, добавленные в список, отображаемый в ListView. Последний должен быть сохранен в базе данных в onPause, потому что это единственный гарантированный вызов. Если вы вместо этого поместите его в onSaveInstanceState, вы рискуете потерять данные, если они не будут вызваны. Но если выбор радиокнопки не сохраняется по той же причине - это не беда.

JBM 16.06.2011 19:15

@stevemoseley: ты ошибаешься. Потому что onSaveInstanceState вызывается для получения состояния каждого экземпляра из действия перед его уничтожением. Если действие не прекращается (оно помещается в фоновый режим), то нет необходимости хранить ЗНАЧЕНИЯ ЭКЗАМЕНА в onPause (), поскольку эти значения не удаляются из памяти. Когда приложение выводится на передний план, значения доступны для использования. НО в случае, если система закроет приложение для освобождения памяти, значения экземпляра будут уничтожены, и onPause не сможет быть использован для их получения. Таким образом, имеет смысл использовать onSaveInstanceState для хранения значений экземпляра, а не onPause.

Vivek 18.01.2012 23:39

@Steve Moseley Нет. onSaveInstanceState () используется для значений преходящий - когда вам не нужно сохранять значения настойчивый. Это зависит от вашей ситуации. В любом случае это плохой ответ. -1 - Пожалуйста, не голосуйте только потому, что он процитировал часть из документации. Если вы потратите время на что-то бесполезное, я дам вам -1.

user942821 26.05.2012 15:42

вы можете отредактировать этот пост с примером, как сохранить в onPause ()?

HiB 12.01.2013 19:19

@laplasz - для onPause() вам нужно будет сохранить в SharedPreferences, в базе данных или в облаке. См. Примеры: http://developer.android.com/guide/topics/data/data-storage‌ .html # pref. Они используют onStop(), но код тот же.

Pimp Trizkit 27.01.2013 13:53

Я отредактировал этот ответ, чтобы уточнить, что onSaveInstanceState() и его друзья небезопасны, только если вы попытаетесь использовать их для сохранения постоянных данных. Надеюсь, это сделает ответ достаточно точным и информативным.

Vicky Chijwani 25.06.2015 01:57

@VVK - частично не согласен. Некоторые способы выхода из приложения не запускают onSaveInstanceState (oSIS). Это ограничивает полезность oSIS. Его стоит поддерживать для минимальных ресурсов ОС, но если приложение хочет вернуть пользователя в состояние, в котором он находился, независимо от того, как приложение было закрыто, вместо этого необходимо использовать подход постоянного хранения. Я использую onCreate для проверки пакета, и если он отсутствует, проверяю постоянное хранилище. Это централизует принятие решений. Я могу восстановиться после сбоя, или выхода с помощью кнопки «Назад», или пользовательского пункта меню «Выход», или вернуться к экрану, на котором пользователь был включен много дней спустя.

ToolmakerSteve 19.09.2015 13:38

ОБНОВЛЕНИЕ: с новой версией android nougat 24+, когда пакет saveInstance пересекает предел IPC в 1 МБ, будет выброшено исключение RuntimeException, которое потенциально может привести к сбою приложения (в отличие от журнала предупреждений в предыдущих выпусках). Если вы сохраняете большие данные в сохраненном состоянии, подумайте об альтернативах, таких как sqlite / sharedpreferences. Обходной путь для предотвращения сбоя - использование targetdkversion 23 и ниже.

Kushan 17.02.2017 15:33

Мой коллега написал статью, в которой объясняется состояние приложения на устройствах 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 27.11.2012 02:43

Данные @Tom SharefPrefs записываются в файл xml. XML - это своего рода база данных? Я бы сказал, что это так;)

MaciejGórski 02.05.2013 14:35

Насколько я понимаю, сохранение состояния - это в лучшем случае путаница. Если вам нужно сохранить постоянные данные, просто используйте базу данных 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 27.11.2012 02:10

Большое спасибо за решение, которое новичок может вырезать, вставить в свое приложение и сразу же использовать! @Tom Что касается скорости, то для хранения 1000 пар требуется около семи секунд, но вы можете сделать это в AsyncTask. Однако вам нужно добавить finally {cursor.close ()}, иначе при этом произойдет сбой из-за утечки памяти.

Noumenon 04.05.2013 15:46

Я наткнулся на это, и, хотя это кажется аккуратным, я не решаюсь попробовать использовать это в Google Glass, это устройство, над которым я работаю в последнее время.

Stephen Tetreault 31.10.2014 21:04

Оба метода полезны и действительны, и оба лучше всего подходят для разных сценариев:

  1. Пользователь завершает работу приложения и повторно открывает его позже, но приложению необходимо перезагрузить данные из последнего сеанса - для этого требуется подход постоянного хранения, такой как использование SQLite.
  2. Пользователь переключает приложение, а затем возвращается к исходному и хочет продолжить с того места, где он остановился - обычно достаточно сохранить и восстановить данные пакета (например, данные состояния приложения) в 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, если быть более конкретным ??? Ваш комментарий бесполезен, и никто не может вам помочь, основываясь на этом.

Stephen Tetreault 31.10.2014 21:00

Это ответ на другую ситуацию: два действия в одном приложении. OP - это уход приложение (например, кнопка домой или другие средства для переключения на другое приложение).

ToolmakerSteve 27.09.2015 10:41

Это именно тот ответ, который я искал!

Pamela Sillah 03.10.2020 00:11

Между тем я вообще больше не использую

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% уверенностью при повороте экрана, потому что вся конфигурация устройства изменилась, и действие необходимо воссоздать с нуля).

Vicky Chijwani 25.06.2015 02:21

Это классический «подводный камень» разработки под Android. Здесь есть две проблемы:

  • Существует тонкая ошибка Android Framework, которая значительно усложняет управление стеком приложений во время разработки, по крайней мере, в устаревших версиях (не совсем уверен, было ли / когда / как это было исправлено). Об этой ошибке я расскажу ниже.
  • «Нормальный» или предполагаемый способ решения этой проблемы сам по себе довольно сложен из-за двойственности onPause / onResume и onSaveInstanceState / onRestoreInstanceState.

Просматривая все эти темы, я подозреваю, что большую часть времени разработчики говорят об этих двух разных проблемах одновременно ... отсюда вся путаница и сообщения о том, что «это не работает для меня».

Во-первых, чтобы прояснить «предполагаемое» поведение: 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 будет вызываться только в том случае, если действие должно быть уничтожено.

В большинстве случаев, если вы просто нажимаете кнопку «Домой», а затем снова запускаете приложение, повторно создавать действие не требуется. Он уже существует в памяти, поэтому 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.

Есть два основных способа реализовать это изменение.

  1. с использованием onSaveInstanceState() и onRestoreInstanceState().
  2. В манифесте 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, чтобы найти больше примеров кода.

THANN Phearum 14.09.2016 05:28

@ChetanMehra, вы имеете в виду настраиваемый класс просмотра, верно? Если это настраиваемый вид, мы можем переопределить onSaveInstanceState и onRestoreInstanceState, как в приведенном выше примере CustomView.

THANN Phearum 17.05.2019 04:56

Я имею в виду объект класса внутри класса представления, например: класс CustomView расширяет View {@State ClassA a;} или класс CustomView расширяет View {@ State Внутренний класс {}}

Chetan Mehra 17.05.2019 13:16

@THANNPhearum Можно задать еще один вопрос?

Chetan Mehra 17.05.2019 13:16

Понятно. Если это так, ваш ClassA должен быть Parcelable. Как уже упоминалось, он работает для действий, фрагментов или любого объекта, которому необходимо сериализовать свое состояние в Bundle.

THANN Phearum 17.05.2019 14:14

Не уверен, что мое решение осуждается или нет, но я использую связанную службу для сохранения состояния 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 для облегчения просмотра модели перезагружают данные после смерти системы ".

Vyacheslav Martynenko 05.10.2018 14:10

Просто столкнулся с этим с изменением разрешений в фоновом режиме.

Brill Pappin 14.12.2018 00:07

Я согласен, из документа «если вам нужно обработать смерть процесса, инициированного системой, вы можете использовать onSaveInstanceState () в качестве резервной копии».

Zhar 25.12.2019 12:40

На чем экономить, а на чем нет?

Вы когда-нибудь задумывались, почему текст в EditText автоматически сохраняется при изменении ориентации? Что ж, этот ответ для вас.

Когда экземпляр Activity уничтожается, а Система воссоздает новый экземпляр (например, изменение конфигурации). Он пытается воссоздать его, используя набор сохраненных данных старого состояния активности (состояние экземпляра).

Состояние экземпляра - это набор пар ключ-значение, хранящихся в объекте Bundle.

By default System saves the View objects in the Bundle for example.

  • Текст в EditText
  • Положение прокрутки в ListView и т. д.

Если вам нужно сохранить другую переменную как часть состояния экземпляра, вам следует использовать метод ПЕРЕОПРЕДЕЛЕНИЕonSavedInstanceState(Bundle savedinstaneState).

Например, int currentScore в GameActivity

Подробнее об onSavedInstanceState (Bundle 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);
}

So by mistake if you forget to call super.onSaveInstanceState(savedInstanceState);the default behavior will not work ie Text in EditText will not save.

Что выбрать для восстановления состояния Activity?

 onCreate(Bundle savedInstanceState)

ИЛИ ЖЕ

onRestoreInstanceState(Bundle savedInstanceState)

Оба метода получают один и тот же объект Bundle, поэтому на самом деле не имеет значения, где вы пишете логику восстановления. Единственное отличие состоит в том, что в методе onCreate(Bundle savedInstanceState) вам нужно будет выполнить нулевую проверку, хотя в последнем случае она не нужна. В других ответах уже есть фрагменты кода. Вы можете направить их.

Подробнее об onRestoreInstanceState (Bundle 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);
}

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 справиться с этим за вас, чем ручная обработка.

Это не имеет ничего общего с сохранением состояния, вы просто отказываетесь от изменения ориентации, имейте в виду, что приложение может быть перезапущено, приостановлено и возобновлено в любое время для разных событий.

lord-ralf-adolf 07.01.2020 23:54

Этот ответ для тех, кто хочет сохранить состояние при изменении ориентации и хочет избежать понимания и реализации сложного способа.

IgniteCoders 08.01.2020 00:31

честно говоря, я понимаю вашу точку зрения, я думаю, что большинство людей, которые борются за сохранение состояния, используют фрагменты, потому что действия фактически сохраняют статистику компонентов пользовательского интерфейса, пока у них есть идентификатор, но фрагменты более особенные, я использовал фрагменты однажды, но я никогда не буду использовать их снова статистика инстанса сохранения была болью, чтобы иметь дело с

lord-ralf-adolf 08.01.2020 03:24

Теперь имеет смысл проделать 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);
  }
}

вы правы, но эта библиотека все еще находится в стадии выпуска, поэтому я думаю, нам следует подождать ...

Zhar 24.12.2019 17:24

Вам нужно переопределить 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 &amp; 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 &amp; 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

Другие вопросы по теме