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


2620

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


9
Когда saveInstanceState == null, а когда нет?
Trojan.ZBOT

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

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


6
Вы можете сделать это с помощью: onSaveInstanceState (Bundle saveInstanceState)
VahidHoseini

Ответы:


2568

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

Или из фрагмента.

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

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


24
Есть ли шанс, что это работает на телефоне, но не в эмуляторе? Кажется, я не могу получить ненулевое saveInstanceState.
Адам Джек

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

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

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

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

433

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

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


3
Когда saveInstanceState == null, а когда нет?
Trojan.ZBOT

6
saveInstanceState имеет значение NULL, когда система создает новый экземпляр вашей Activity, и не имеет значение NULL, когда он восстанавливает.
Габриэль Камара

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

415

Обратите внимание, что это НЕ безопасно использовать 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).


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

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

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

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

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

206

Мой коллега написал статью , описывающее состояние приложения на устройствах 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();
}

3
@ MartinBelcher-Eigo В статье говорится о данных в SharedPreferences: «Эти данные записываются в базу данных на устройстве.» Я считаю, что данные хранятся в файле в каталоге приложения файловой системы.
Том

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

148

Это классический «гоча» разработки для 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;
    }
}

87

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


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

74

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

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

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

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


70

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

9
Поскольку загрузка базы данных SQLite занимает слишком много времени, учитывая, что это критический путь к отображению пользовательского интерфейса приложения. Я на самом деле не рассчитал время, так что я рад, что поправился, но наверняка загрузка и открытие файла базы данных не будут быстрыми?
Том

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

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

61

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

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

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

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

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

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 в том же состоянии, в котором мы вышли ранее.

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


2
@bagusflyer заботиться, чтобы быть более конкретным ??? Ваш комментарий не поможет, и никто не может помочь вам, основываясь на этом.
Стивен Тетро

2
Это ответ на другую ситуацию: два действия в одном приложении. ОП - это выход из приложения (например, кнопка «Домой» или другие средства для переключения на другое приложение).
ToolmakerSteve

44

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

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

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


40

Действительно onSaveInstanceState()вызывается, когда активность переходит в фоновый режим.

Цитата из документа: «Этот метод вызывается до того, как действие может быть прекращено, чтобы, когда оно вернется в будущем, оно могло восстановить свое состояние». Источник


37

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


34

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

Bundle savedInstanceState & Co

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

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

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

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

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

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

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


31

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

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

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

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

При тестировании вашего примера "Привет, мир" есть несколько способов уйти и вернуться к занятию.

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

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

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

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


28

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


2
В вашем случае onCreateи onRestoreInstanceStateне Activityвызывается, потому что при переключении приложений вообще не уничтожается, поэтому не нужно ничего восстанавливать. Android вызывает onSaveInstanceStateна тот случай, если действие будет уничтожено позже (что происходит со 100% -ной уверенностью при повороте экрана, потому что изменилась вся конфигурация устройства, и действие должно быть воссоздано с нуля).
Вики Чиджвани,

20

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

mySavedInstanceState=savedInstanceState;

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

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

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


19

Хотя принятый ответ правильный, существует более быстрый и простой способ сохранить состояние «Активность» на 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.


16

Когда действие создается, вызывается метод 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");
    }

15

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

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

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

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

Пример: рассмотрим случай, если вы хотите сохранить объект 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); 

}

11

Вот комментарий из ответа Стива Мозли (от ToolmakerSteve ), который показывает, что все в перспективе (в целом onSaveInstanceState против onPause, восточная стоимость против западной стоимости сага)

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


10

Код Котлина:

спасти:

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

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


9

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

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


6

Просто быстро решить эту проблему с помощью 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
}

1
@ralphspoon да, это работает для фрагмента и пользовательского просмотра. Пожалуйста, проверьте пример кода. Я отредактировал свой ответ. Я предлагаю вам перейти к официальным документам здесь github.com/frankiesardo/icepick, чтобы найти больше примеров кода.
THANN Phearum

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

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

@THANNPhearum Должен ли я задать его как еще один вопрос?
Четан Мехра

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

6

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

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

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


5

Котлин

Вы должны переопределить 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
}

2

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


3
Это неправда. Из документации: «В отличие от сохраненного состояния экземпляра, ViewModel уничтожаются во время смерти процесса, инициированного системой. Вот почему вы должны использовать объекты ViewModel в сочетании с onSaveInstanceState () (или некоторой другой сохраняемостью диска), сохраняя идентификаторы в saveInstanceState для облегчения просмотра модели перезагружают данные после смерти системы ".
Вячеслав Мартыненко

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

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

2

Существует способ заставить Android сохранять состояния без реализации какого-либо метода. Просто добавьте эту строку в свой манифест в декларации активности:

android:configChanges="orientation|screenSize"

Это должно выглядеть так:

<activity
    android:name=".activities.MyActivity"
    android:configChanges="orientation|screenSize">
</activity>

Здесь вы можете найти более подробную информацию об этой собственности.

Рекомендуется, чтобы Android обрабатывал это для вас, чем ручная обработка.


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

1
Этот ответ предназначен для тех, кто хочет сохранить состояние при
смене

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

это работает ... спасибо
Fanadez

1

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

Вы когда-нибудь задумывались, почему текст в 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 Официальная документация .


1

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.