Вопрос:

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

android android-activity application-state

689984 просмотра

27 ответа

21847 Репутация автора

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

Автор: Bernard Источник Размещён: 30.09.2008 04:41

Ответы (27)


396 плюса

35740 Репутация автора

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

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

Автор: Dave L. Размещён: 30.09.2008 05:03

2377 плюса

88063 Репутация автора

Решение

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

Bundle - это, по сути, способ хранения карты 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");
}

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

Автор: Reto Meier Размещён: 30.09.2008 06:12

73 плюса

40101 Репутация автора

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

Автор: Fedor Размещён: 07.05.2010 12:21

34 плюса

527 Репутация автора

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

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

Автор: u-foka Размещён: 23.05.2010 10:32

383 плюса

4531 Репутация автора

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

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

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

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

РЕДАКТИРОВАТЬ : Для дальнейшего уточнения, вот onSaveInstanceState()документация:

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

Автор: Steve Moseley Размещён: 25.05.2010 11:22

180 плюса

3596 Репутация автора

Мой коллега написал статью , описывающее состояние приложения на 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();
}
Автор: Martin Belcher - Eigo Размещён: 27.08.2010 01:54

54 плюса

935 Репутация автора

Насколько я понимаю, сохранение состояния - в лучшем случае клудж. Если вам нужно сохранить постоянные данные, просто используйте базу данных 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;
Автор: Mike A. Размещён: 23.06.2011 05:07

62 плюса

621 Репутация автора

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

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

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

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

Автор: David Размещён: 27.06.2011 04:17

37 плюса

16888 Репутация автора

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

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

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

Автор: Ixx Размещён: 17.01.2012 06:28

51 плюса

7127 Репутация автора

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

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

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

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

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

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

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

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

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

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

Автор: roy mathew Размещён: 05.02.2012 11:35

29 плюса

7361 Репутация автора

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

Bundle savedInstanceState & Co

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

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

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

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

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

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

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

Автор: stefan bachert Размещён: 31.03.2012 01:36

24 плюса

928 Репутация автора

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

Автор: Mahorad Размещён: 05.09.2012 08:27

132 плюса

5493 Репутация автора

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

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

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

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

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

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

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

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

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

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

Поведение нажатия клавиши «Домой»

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

if (!isTaskRoot()) {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
        finish();
        return;
    }
}
Автор: Mike Repass Размещён: 19.10.2012 11:47

16 плюса

181 Репутация автора

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

mySavedInstanceState=savedInstanceState;

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

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

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

Автор: torwalker Размещён: 16.04.2014 05:11

28 плюса

4606 Репутация автора

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

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

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

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

Когда вы тестируете пример «Привет, мир», есть несколько способов уйти и вернуться к действию.

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

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

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

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

Автор: Jared Kells Размещён: 21.01.2015 02:19

29 плюса

29603 Репутация автора

Чтобы помочь уменьшить шаблон, я использую следующее 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 .

Автор: Jared Rummler Размещён: 20.08.2015 02:04

12 плюса

1860 Репутация автора

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

  1. используя onSaveInstanceState()и onRestoreInstanceState().
  2. В манифесте android:configChanges="orientation|screenSize".

I really do not recommend to use second method. Since in one of my experience it was causing half of the device screen black while rotating from portrait to landscape and vice versa.

Using first method mentioned above , we can persist data when orientation is changed or any config change happens. I know a way in which you can store any type of data inside savedInstance state object.

Example: Consider a case if you want to persist Json object. create a model class with getters and setters .

class MyModel extends Serializable{
JSONObject obj;

setJsonObject(JsonObject obj)
{
this.obj=obj;
}

JSONObject getJsonObject()
return this.obj;
} 
}

Now in your activity in onCreate and onSaveInstanceState method do the following. It will look something like this:

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

}
Автор: Krishna Размещён: 18.12.2015 11:43

15 плюса

1003 Репутация автора

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

Делать что-то вроде этого с Icepick:

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

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

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

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

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

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

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

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

Автор: Kevin Cronly Размещён: 26.01.2016 03:07

5 плюса

1131 Репутация автора

Просто быстро решить эту проблему с помощью IcePick

Сначала настройте библиотеку в app/build.gradle

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

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

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

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

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

Это работает для Деятельностей, Фрагментов или любого объекта, который должен сериализовать свое состояние на Пакете (например, ViewPresenters раствора)

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

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

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

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

  // You can put the calls to Icepick into a BaseCustomView and inherit from it
  // All Views extending this CustomView automatically have state saved/restored
}
Автор: THANN Phearum Размещён: 08.08.2016 02:07

5 плюса

882 Репутация автора

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

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

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

Автор: ComeIn Размещён: 09.08.2016 12:57

5 плюса

436 Репутация автора

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

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

Автор: ascii_walker Размещён: 28.09.2016 11:46

13 плюса

868 Репутация автора

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

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

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

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

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

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

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

или же

  //restores activity's saved state
 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
      String restoredMessage=savedInstanceState.getString("key");
    }
Автор: Mansuu.... Размещён: 23.01.2017 10:29

8 плюса

3493 Репутация автора

Here is a comment from Steve Moseley's answer (by ToolmakerSteve) that puts things into perspective (in the whole onSaveInstanceState vs onPause, east cost vs west cost saga)

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

Автор: samis Размещён: 07.02.2017 03:56

7 плюса

391 Репутация автора

Код Котлина:

спасти:

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

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

Автор: Rafols Размещён: 12.02.2018 07:42

2 плюса

1161 Репутация автора

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

Автор: M Abdul Sami Размещён: 27.08.2018 11:51

0 плюса

3102 Репутация автора

Что сохранить, а что нет?

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

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

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

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

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

Если вам нужна другая переменная, которая будет сохранена как часть состояния экземпляра, вам следует переопределить onSavedInstanceState(Bundle savedinstaneState) метод.

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

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

@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 (Bundle saveinstaneState)

@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 Официальная документация .

Автор: Rohit Singh Размещён: 10.01.2019 08:48

0 плюса

21 Репутация автора

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

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

<activity android:name=".MainActivity"
        android:configChanges="orientation|screenSize">
Автор: Hossein Karami Размещён: 27.02.2019 10:12
Вопросы из категории :
32x32