Как сохранить состояние активности Android с помощью сохранения состояния экземпляра? - PullRequest
2425 голосов
/ 30 сентября 2008

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

Ответы [ 28 ]

37 голосов
/ 24 мая 2010

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

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

32 голосов
/ 20 августа 2015

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

31 голосов
/ 31 марта 2012

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

Bundle savedInstanceState & Co

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

И Google заявляет, что он даже НЕ надежен.

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

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

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

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

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

30 голосов
/ 21 января 2015

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

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

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

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

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

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

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

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

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

26 голосов
/ 05 сентября 2012

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

18 голосов
/ 16 апреля 2014

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

mySavedInstanceState=savedInstanceState;

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

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

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

17 голосов
/ 26 января 2016

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

14 голосов
/ 23 января 2017

Когда создается действие, вызывается метод 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");
    }
13 голосов
/ 18 декабря 2015

Существует два основных способа реализации этого изменения.

  1. с использованием onSaveInstanceState() и onRestoreInstanceState().
  2. В декларации android:configChanges="orientation|screenSize".

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

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

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

}
9 голосов
/ 07 февраля 2017

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

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

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