Как сохранить состояние активности Android с помощью save instance state?

StackOverflow https://stackoverflow.com/questions/151777

Вопрос

Я работаю на платформе Android SDK, и немного неясно, как сохранить состояние приложения.Итак, учитывая эту незначительную доработку примера "Привет, Android":

package com.android.hello;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    if (savedInstanceState == null) {
       mTextView.setText("Welcome to HelloAndroid!");
    } else {
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

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

Я уверен, что решение так же просто, как переопределение onPause или что-то в этом роде, но я копался в документации минут 30 или около того и не нашел ничего очевидного.

Это было полезно?

Решение

Вам нужно переопределить onSaveInstanceState(Bundle savedInstanceState) и запишите значения состояния приложения, которые вы хотите изменить, в Bundle параметр, подобный этому:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  // Save UI state changes to the savedInstanceState.
  // This bundle will be passed to onCreate if the process is
  // killed and restarted.
  savedInstanceState.putBoolean("MyBoolean", true);
  savedInstanceState.putDouble("myDouble", 1.9);
  savedInstanceState.putInt("MyInt", 1);
  savedInstanceState.putString("MyString", "Welcome back to Android");
  // etc.
}

Пакет, по сути, представляет собой способ хранения карты NVP ("Пара имя-значение"), и она будет передана в onCreate() а также onRestoreInstanceState() где вы затем извлекли бы значения следующим образом:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  // Restore UI state from the savedInstanceState.
  // This bundle has also been passed to onCreate.
  boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
  double myDouble = savedInstanceState.getDouble("myDouble");
  int myInt = savedInstanceState.getInt("MyInt");
  String myString = savedInstanceState.getString("MyString");
}

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

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

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

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

Обратите внимание, что это НЕТ безопасен в использовании onSaveInstanceState и onRestoreInstanceState для постоянных данных, согласно документации о Деятельности , изложенной в http://developer.android.com/reference/android/app/Activity.html.

В документе указано (в разделе "Жизненный цикл деятельности")::

Обратите внимание, что важно сохранять постоянные данные в onPause() вместо вместо onSaveInstanceState(Bundle) поскольку более поздний не является частью обратных вызовов жизненного цикла, so не будет вызываться в каждой ситуации, как описано в его документации.

Другими словами, поместите свой код сохранения / восстановления постоянных данных в onPause() и onResume()!

Редактировать:Для получения дальнейших разъяснений, вот onSaveInstanceState() Документация:

Этот метод вызывается до того, как действие может быть убито таким образом, что при вернется когда-нибудь в будущем он может восстановить свое состояние.Для например, если действие B запускается перед действием A, и в какой-то момент действие A прекращается для восстановления ресурсов, у действия A будет шанс сохранить текущее состояние своего пользовательского интерфейса с помощью этого метод, позволяющий при возврате пользователя к действию A восстановить состояние пользовательского интерфейса с помощью onCreate(Bundle) или onRestoreInstanceState(Bundle).

Мой коллега написал статью, объясняющую состояние приложения на устройствах Android, включая пояснения к жизненному циклу активности и информации о состоянии, как хранить информацию о состоянии и сохранять в состояние Bundle и SharedPreferences и взгляните на это здесь.

В статье рассматриваются три подхода:

Хранить локальную переменную / данные управления пользовательским интерфейсом в течение срока службы приложения (т. е.временно) с использованием пакета состояний экземпляра

[Code sample – Store state in state bundle]
@Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
  // Store UI state to the savedInstanceState.
  // This bundle will be passed to onCreate on next call.  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  savedInstanceState.putString(“Name”, strName);
  savedInstanceState.putString(“Email”, strEmail);
  savedInstanceState.putBoolean(“TandC”, blnTandC);

  super.onSaveInstanceState(savedInstanceState);
}

Хранить локальные переменные / данные управления пользовательским интерфейсом между экземплярами приложения (т. е.постоянно) с использованием общих настроек

[Code sample – store state in SharedPreferences]
@Override
protected void onPause()
{
  super.onPause();

  // Store values between instances here
  SharedPreferences preferences = getPreferences(MODE_PRIVATE);
  SharedPreferences.Editor editor = preferences.edit();  // Put the values from the UI
  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  editor.putString(“Name”, strName); // value to store
  editor.putString(“Email”, strEmail); // value to store
  editor.putBoolean(“TandC”, blnTandC); // value to store
  // Commit to storage
  editor.commit();
}

Поддержание работоспособности экземпляров объектов в памяти между действиями в течение срока службы приложения с использованием сохраненного экземпляра без конфигурации

[Code sample – store object instance]
private cMyClassType moInstanceOfAClass; // Store the instance of an object
@Override
public Object onRetainNonConfigurationInstance()
{
  if (moInstanceOfAClass != null) // Check that the object exists
      return(moInstanceOfAClass);
  return super.onRetainNonConfigurationInstance();
}

Это классическая "ошибка" в разработке для Android.Здесь есть две проблемы:

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

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

Во-первых, чтобы прояснить "предполагаемое" поведение:onSaveInstance и onRestoreInstance являются хрупкими и предназначены только для переходного состояния.Предполагаемое использование (afaict) - управление активностью при повороте телефона (изменении ориентации).Другими словами, предполагаемое использование - это когда ваша Активность по-прежнему логически "на высоте", но все равно должна быть восстановлена системой.Сохраненный пакет не сохраняется за пределами процесса / памяти / gc, поэтому вы не можете по-настоящему полагаться на это, если ваша активность переходит в фоновый режим.Да, возможно, память вашего действия переживет переход в фоновый режим и избежит GC, но это ненадежно (и непредсказуемо).

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

НО - есть очень запутанная ошибка, которая усложняет все это.Подробности здесь:

http://code.google.com/p/android/issues/detail?id=2373

http://code.google.com/p/android/issues/detail?id=5277

В принципе, если ваше приложение запускается с флагом singleTask, а затем позже вы запускаете его с главного экрана или из меню запуска, то этот последующий вызов создаст НОВУЮ задачу ...фактически у вас будут два разных экземпляра вашего приложения , находящихся в одном и том же стеке ...что очень быстро становится очень странным.Похоже, это происходит, когда вы запускаете свое приложение во время разработки (т. е.из Eclipse или Intellij), поэтому разработчики часто сталкиваются с этим.Но также и через некоторые механизмы обновления App Store (так что это влияет и на ваших пользователей).

Я часами разбирался в этих потоках, прежде чем понял, что моей главной проблемой была эта ошибка, а не предполагаемое поведение фреймворка.Отличная статья и обходной путь (ОБНОВЛЕНИЕ:смотрите ниже), кажется, от пользователя @kaciula в этом ответе:

Поведение при нажатии клавиши Home

ОБНОВЛЕНИЕ За июнь 2013 года:Месяцы спустя я, наконец, нашел "правильное" решение.Вам не нужно самостоятельно управлять какими-либо флагами startedApp с отслеживанием состояния, вы можете определить это из фреймворка и соответствующим образом выполнить залог.Я использую это в начале моего LauncherActivity.onCreate:

if (!isTaskRoot()) {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
        finish();
        return;
    }
}

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

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

  1. Пользователь завершает работу приложения и повторно открывает его позже, но приложению необходимо перезагрузить данные из последнего сеанса – для этого требуется подход к постоянному хранению, такой как использование SQLite.
  2. Пользователь переключает приложение, а затем возвращается к исходному и хочет продолжить с того места, на котором они остановились - сохранить и восстановить данные пакета (например, данные о состоянии приложения) в onSaveInstanceState() и onRestoreInstanceState() обычно адекватен.

Если вы сохраняете данные о состоянии постоянным образом, их можно повторно загрузить в onResume() или onCreate() (или фактически при любом вызове жизненного цикла).Это может быть, а может и не быть желательным поведением.Если вы храните его в пачке в InstanceState, тогда он является временным и подходит только для хранения данных для использования в одном и том же пользовательском "сеансе" (я использую термин "сеанс" свободно), но не между "сеансами’.

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

Насколько я понимаю, сохранение состояния - это в лучшем случае клудж.Если вам нужно сохранить постоянные данные, просто используйте SQLite - файл База данных.Android делает это ОООЧЕНЬ просто.

Что - то вроде этого:

import java.util.Date;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class dataHelper {

    private static final String DATABASE_NAME = "autoMate.db";
    private static final int DATABASE_VERSION = 1;

    private Context context;
    private SQLiteDatabase db;
    private OpenHelper oh ;

    public dataHelper(Context context) {
        this.context = context;
        this.oh = new OpenHelper(this.context);
        this.db = oh.getWritableDatabase();
    }

    public void close() {
        db.close();
        oh.close();
        db = null;
        oh = null;
        SQLiteDatabase.releaseMemory();
    }


    public void setCode(String codeName, Object codeValue, String codeDataType) {
        Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        String cv = "" ;

        if (codeDataType.toLowerCase().trim().equals("long") == true){
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            cv = String.valueOf(((Date)codeValue).getTime());
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            String.valueOf(codeValue);
        }
        else
        {
            cv = String.valueOf(codeValue);
        }

        if(codeRow.getCount() > 0) //exists-- update
        {
            db.execSQL("update code set codeValue = '" + cv +
                "' where codeName = '" + codeName + "'");
        }
        else // does not exist, insert
        {
            db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" +
                    "'" + codeName + "'," +
                    "'" + cv + "'," +
                    "'" + codeDataType + "')" );
        }
    }

    public Object getCode(String codeName, Object defaultValue){

        //Check to see if it already exists
        String codeValue = "";
        String codeDataType = "";
        boolean found = false;
        Cursor codeRow  = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        if (codeRow.moveToFirst())
        {
            codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue"));
            codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType"));
            found = true;
        }

        if (found == false)
        {
            return defaultValue;
        }
        else if (codeDataType.toLowerCase().trim().equals("long") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (long)0;
            }
            return Long.parseLong(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (int)0;
            }
            return Integer.parseInt(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            if (codeValue.equals("") == true)
            {
                return null;
            }
            return new Date(Long.parseLong(codeValue));
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            if (codeValue.equals("") == true)
            {
                return false;
            }
            return Boolean.parseBoolean(codeValue);
        }
        else
        {
            return (String)codeValue;
        }
    }


    private static class OpenHelper extends SQLiteOpenHelper {

        OpenHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE IF  NOT EXISTS code" +
            "(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }
}

Простой звонок после этого

dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;

Я думаю, что нашел ответ.Позвольте мне простыми словами рассказать о том, что я сделал:

Предположим, у меня есть два действия, activity1 и activity2, и я перехожу от activity1 к activity2 (я выполнил некоторые работы в activity2) и снова возвращаюсь к activity 1, нажав на кнопку в activity1.Теперь, на этом этапе, я хотел вернуться к activity2, и я хочу видеть свою activity2 в том же состоянии, в котором я в последний раз покидал activity2.

Для приведенного выше сценария то, что я сделал, это то, что в манифесте я внес некоторые изменения, подобные этому:

<activity android:name=".activity2"
          android:alwaysRetainTaskState="true"      
          android:launchMode="singleInstance">
</activity>

И в activity1 на событии нажатия кнопки я сделал вот так:

Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);

И в activity2 при событии нажатия кнопки я сделал вот так:

Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);

Теперь произойдет то, что любые изменения, которые мы внесли в activity2, не будут потеряны, и мы сможем просматривать activity2 в том же состоянии, в котором мы оставили ранее.

Я верю, что это ответ, и у меня это прекрасно работает.Поправьте меня, если я ошибаюсь.

onSaveInstanceState() для временных данных (восстановленных в onCreate()/onRestoreInstanceState()), onPause() для постоянных данных (восстановленных в onResume()).Из технических ресурсов Android:

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

onPause() Включение паузы () и onResume() есть также дополнительные методы.onPause() всегда вызывается, когда действие заканчивается, даже если мы сами спровоцировали это (например, с помощью вызова finish()).Мы будем использовать это, чтобы сохранить текущую заметку обратно в базу данных.Хорошей практикой является освобождение любых ресурсов, которые также могут быть освобождены во время onPause(), чтобы занимать меньше ресурсов в пассивном состоянии.

В самом деле onSaveInstance укажите callen, когда Действие переходит в фоновый режим

Цитата из документов:"метод onSaveInstanceState(Bundle) вызывается перед переводом действия в такое фоновое состояние"

Чтобы уменьшить шаблонность, я использую следующее interface и class для чтения / записи в Bundle для сохранения состояния экземпляра.


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

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.FIELD
})
public @interface SaveInstance {

}

Затем создайте класс, в котором отражение будет использоваться для сохранения значений в пакете:

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;

import java.io.Serializable;
import java.lang.reflect.Field;

/**
 * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
 * SaveInstance}.</p>
 */
public class Icicle {

    private static final String TAG = "Icicle";

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #load(Bundle, Object)
     */
    public static void save(Bundle outState, Object classInstance) {
        save(outState, classInstance, classInstance.getClass());
    }

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #load(Bundle, Object, Class)
     */
    public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
        if (outState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    field.setAccessible(true);
                    String key = className + "#" + field.getName();
                    try {
                        Object value = field.get(classInstance);
                        if (value instanceof Parcelable) {
                            outState.putParcelable(key, (Parcelable) value);
                        } else if (value instanceof Serializable) {
                            outState.putSerializable(key, (Serializable) value);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not added to the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #save(Bundle, Object)
     */
    public static void load(Bundle savedInstanceState, Object classInstance) {
        load(savedInstanceState, classInstance, classInstance.getClass());
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #save(Bundle, Object, Class)
     */
    public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
        if (savedInstanceState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    String key = className + "#" + field.getName();
                    field.setAccessible(true);
                    try {
                        Object fieldVal = savedInstanceState.get(key);
                        if (fieldVal != null) {
                            field.set(classInstance, fieldVal);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

}

Пример использования:

public class MainActivity extends Activity {

    @SaveInstance
    private String foo;

    @SaveInstance
    private int bar;

    @SaveInstance
    private Intent baz;

    @SaveInstance
    private boolean qux;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icicle.load(savedInstanceState, this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icicle.save(outState, this);
    }

}

Примечание: Этот код был адаптирован из библиотечного проекта под названием AndroidAutowire - автоматический провод который лицензирован в соответствии с MIT license.

Между тем от меня, в общем-то, больше нет никакой пользы

Bundle savedInstanceState & Co

Жизненный цикл для большинства видов деятельности слишком сложен и не является необходимым.

И Google заявляет сам, что это даже ненадежно.

Мой способ - немедленно сохранять любые изменения в настройках:

 SharedPreferences p;
 p.edit().put(..).commit()

В некотором роде SharedPreferences работают аналогично пакетам.И естественно, что сначала такие значения должны быть считаны из настроек.

В случае сложных данных вы можете использовать SQLite вместо использования настроек.

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

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

Ваша активность будет воссоздана с помощью пакета состояний только тогда, когда:

  • Изменения конфигурации, такие как изменение ориентации или языка телефона, что может потребовать создания нового экземпляра activity.
  • Вы возвращаетесь в приложение из фонового режима после того, как операционная система уничтожила действие.

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

При тестировании вашего примера hello world есть несколько способов выйти из этого упражнения и вернуться к нему.

  • Когда вы нажимаете кнопку "Назад", действие завершается.Повторный запуск приложения - это совершенно новый экземпляр.Вы вообще не возвращаетесь к фоновой работе.
  • Когда вы нажимаете кнопку "Домой" или используете переключатель задач, Действие переходит в фоновый режим.При обратном переходе к приложению onCreate будет вызываться только в том случае, если Действие должно было быть уничтожено.

В большинстве случаев, если вы просто нажимаете кнопку "Домой", а затем снова запускаете приложение, действие не нужно будет создавать заново.Он уже существует в памяти, поэтому onCreate() вызываться не будет.

В разделе Настройки -> Параметры разработчика есть опция под названием "Не сохранять действия".Когда он включен, Android всегда будет уничтожать действия и воссоздавать их заново, когда они будут созданы на заднем плане.Это отличный вариант, который можно оставить включенным при разработке, поскольку он имитирует наихудший сценарий.(Устройство с низким объемом памяти постоянно перерабатывает ваши действия).

Другие ответы ценны тем, что они учат вас правильным способам сохранения состояния, но я не почувствовал, что они действительно отвечают, ПОЧЕМУ ваш код работает не так, как вы ожидали.

Тот Самый onSaveInstanceState(bundle) и onRestoreInstanceState(bundle) методы полезны для сохранения данных просто при вращении экрана (изменении ориентации).
Они не работают даже при переключении между приложениями (поскольку onSaveInstanceState() метод вызывается, но onCreate(bundle) и onRestoreInstanceState(bundle) больше не вызывается.
Для большей настойчивости используйте общие настройки. прочтите эту статью

Моя проблема заключалась в том, что мне нужна была настойчивость только в течение срока службы приложения (т. е.однократное выполнение, включая запуск других подзадач в том же приложении, поворот устройства и т.д.).Я пробовал различные комбинации приведенных выше ответов, но не во всех ситуациях получал то, что хотел.В конце концов, у меня получилось получить ссылку на savedInstanceState во время onCreate:

mySavedInstanceState=savedInstanceState;

и используйте это, чтобы получить содержимое моей переменной, когда мне это нужно, следующим образом:

if (mySavedInstanceState !=null) {
   boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}

Я использую onSaveInstanceStateи onRestoreInstanceState как было предложено выше, но я думаю, я мог бы также или альтернативно использовать свой метод для сохранения переменной при ее изменении (напримериспользуя putBoolean)

Хотя принятый ответ правильный, существует более быстрый и простой способ сохранения состояния активности на Android с использованием библиотеки под названием Ледоруб.Icepick - это обработчик аннотаций, который обрабатывает весь шаблонный код, используемый для сохранения и восстановления состояния для вас.

Делаем что-то подобное с помощью Icepick:

class MainActivity extends Activity {
  @State String username; // These will be automatically saved and restored
  @State String password;
  @State int age;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}

Это то же самое, что делать это:

class MainActivity extends Activity {
  String username;
  String password;
  int age;

  @Override
  public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString("MyString", username);
    savedInstanceState.putString("MyPassword", password);
    savedInstanceState.putInt("MyAge", age); 
    /* remember you would need to actually initialize these variables before putting it in the
    Bundle */
  }

  @Override
  public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    username = savedInstanceState.getString("MyString");
    password = savedInstanceState.getString("MyPassword");
    age = savedInstanceState.getInt("MyAge");
  }
}

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

Когда действие создается, вызывается его метод onCreate().

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

savedInstanceState - это объект класса Bundle, который в первый раз имеет значение null, но при повторном создании содержит значения.Чтобы сохранить состояние Activity, вы должны переопределить onSaveInstanceState().

   @Override
    protected void onSaveInstanceState(Bundle outState) {
      outState.putString("key","Welcome Back")
        super.onSaveInstanceState(outState);       //save state
    }

поместите ваши значения в объект пакета "outState", например outState.putString("ключ","Добро пожаловать обратно"), и сохраните, вызвав super .Когда activity будет уничтожена, ее состояние сохраняется в объекте Bundle и может быть восстановлено после повторного создания в onCreate() или onRestoreInstanceState().Пакет, полученный в onCreate() и onRestoreInstanceState(), одинаковый.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

          //restore activity's state
         if(savedInstanceState!=null){
          String reStoredString=savedInstanceState.getString("key");
            }
    }

или

  //restores activity's saved state
 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
      String restoredMessage=savedInstanceState.getString("key");
    }

В основном существует два способа реализовать это изменение.

  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); 

}

Вот комментарий от Стив Мозлиответ автора (by Инструментальщики) это ставит вещи в перспективу (во всей саге о onSaveInstanceState vs onPause, east cost vs west cost)

@VVK - Я частично не согласен.Некоторые способы выхода из приложения не запускают onSaveInstanceState (oSIS).Это ограничивает полезность oSIS.Это стоит поддерживать при минимальных ресурсах ОС, но если приложение хочет вернуть пользователя в состояние, в котором он находился, независимо от того, как приложение было завершено, вместо этого необходимо использовать подход с постоянным хранилищем. Я использую onCreate для проверки наличия пакета, и если он отсутствует, то проверьте постоянное хранилище. Это централизует процесс принятия решений.Я могу восстановиться после сбоя, или выйти из кнопки "Назад", или выйти из пункта пользовательского меню, или вернуться к экрану, на котором пользователь был включен много дней спустя.– ToolmakerSteve сентябрь 19 '15 в 10:38

Код Kotlin:

Сохранить:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState.apply {
        putInt("intKey", 1)
        putString("stringKey", "String Value")
        putParcelable("parcelableKey", parcelableObject)
    })
}

а потом в onCreate() или onRestoreInstanceState()

    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

Добавьте значения по умолчанию, если вы не хотите использовать Дополнительные параметры

Чтобы получить данные о состоянии активности, хранящиеся в onCreate(), сначала вы должны сохранить данные в savedInstanceState , переопределив SaveInstanceState(Bundle savedInstanceState) способ.

Когда активность уничтожает SaveInstanceState(Bundle savedInstanceState) вызывается метод, и там вы сохраняете данные, которые хотите сохранить.И вы получаете то же самое в onCreate() при перезапуске activity. (savedInstanceState не будет иметь значения null, поскольку вы сохранили в нем некоторые данные до того, как activity будет уничтожена)

Простым и быстрым способом решения этой проблемы является использование Ледоруб

Во-первых, настройте библиотеку в app/build.gradle

repositories {
  maven {url "https://clojars.org/repo/"}
}
dependencies {
  compile 'frankiesardo:icepick:3.2.0'
  provided 'frankiesardo:icepick-processor:3.2.0'
}

Теперь давайте проверим приведенный ниже пример того, как сохранить состояние в Activity

public class ExampleActivity extends Activity {
  @State String username; // This will be automatically saved and restored

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}

Это работает для действий, фрагментов или любого объекта, которому необходимо сериализовать свое состояние в пакете (напримерпросмотрщики миномета)

Icepick также может генерировать код состояния экземпляра для пользовательских представлений:

class CustomView extends View {
  @State int selectedPosition; // This will be automatically saved and restored

  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
  }

  // You can put the calls to Icepick into a BaseCustomView and inherit from it
  // All Views extending this CustomView automatically have state saved/restored
}

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

Из-за ограничений памяти и обработки, присущих мобильным устройствам, я обрабатываю представления Android аналогично веб-странице.Страница не поддерживает состояние, это чисто компонент уровня представления, единственной целью которого является представление состояния приложения и прием пользовательского ввода.Последние тенденции в архитектуре веб-приложений используют устаревший шаблон Model, View, Controller (MVC), где страница является представлением, данные домена - моделью, а контроллер находится за веб-службой.Тот же шаблон может быть использован и в Android с таким видом, ну ...представление, модель - это данные вашего домена, а контроллер реализован как служба, привязанная к Android.Всякий раз, когда вы хотите, чтобы представление взаимодействовало с контроллером, привяжитесь к нему при запуске / возобновлении и отмените привязку при остановке / паузе.

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

Теперь Android предоставляет Модели просмотра для сохранения состояния вы должны попытаться использовать это вместо saveInstanceState .

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

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

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

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

По умолчанию система сохраняет объекты просмотра, например, в Пакете.

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

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

Например, int currentScore в игровой активности

Более подробная информация о onSavedInstanceState(пакет savedinstaneState) при сохранении данных

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

Так что по ошибке, если вы забудете позвонить super.onSaveInstanceState(savedInstanceState);поведение по умолчанию не будет работать, т.е. текст в EditText сохраняться не будет.

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

 onCreate(Bundle savedInstanceState)

или

onRestoreInstanceState(Bundle savedInstanceState)

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

Более подробная информация об onRestoreInstanceState(пакет savedinstaneState)

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from the saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}

Всегда звони super.onRestoreInstanceState(savedInstanceState); таким образом, Система восстановит иерархию представлений по умолчанию

Бонус

Тот Самый onSaveInstanceState(Bundle savedInstanceState) вызывается системой только тогда, когда пользователь намеревается вернуться к выполнению Действия.Например, вы используете приложение X, и вдруг вам звонят.Вы переходите в приложение вызывающего абонента и возвращаетесь в приложение X.В этом случае onSaveInstanceState(Bundle savedInstanceState) будет вызван метод.

Но учтите это, если пользователь нажмет кнопку "Назад".Предполагается, что пользователь не намерен возвращаться к своему Действию, следовательно, в данном случае onSaveInstanceState(Bundle savedInstanceState) не будет вызван системой.Дело в том, что вы должны учитывать все сценарии при сохранении данных.

Релевантные ссылки:

Демонстрация поведения по умолчанию
Официальная документация Android.

Котлин

Вы должны переопределить onSaveInstanceState и onRestoreInstanceState чтобы хранить и извлекать ваши переменные, вы должны быть постоянными

График жизненного цикла

Храните переменные

public override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)

    // prepare variables here
    savedInstanceState.putInt("kInt", 10)
    savedInstanceState.putBoolean("kBool", true)
    savedInstanceState.putDouble("kDouble", 4.5)
    savedInstanceState.putString("kString", "Hello Kotlin")
}

Извлечение переменных

public override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    val myInt = savedInstanceState.getInt("kInt")
    val myBoolean = savedInstanceState.getBoolean("kBool")
    val myDouble = savedInstanceState.getDouble("kDouble")
    val myString = savedInstanceState.getString("kString")
    // use variables here
}

У меня есть идея получше.Было бы лучше сохранить ваши данные, не вызывая onCreate повторно.Вы можете отключить его от активности при смене ориентации.

В вашем манифесте:

<activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize">

Редактировать: это решение не будет работать, когда Android завершит процесс из-за нехватки памяти.если вам нужно сохранить данные наверняка, в этом случае вам нужно использовать savedInstanceState , в противном случае я предлагаю использовать этот способ.классный и простой в использовании!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top