Як зберегти стан активності, використовуючи стан збереження екземпляра?


2620

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

package com.android.hello;

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

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

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

    mTextView = new TextView(this);

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

    setContentView(mTextView);
  }
}

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

Я впевнений, що рішення настільки ж просто, як переосмислення onPauseчи щось подібне, але я відштовхуюсь у документації протягом 30 хвилин або близько того і не знайшов нічого очевидного.


9
Коли зберігаєтьсяInstanceState == null, а коли це не null?
Trojan.ZBOT

90
Ви явно знищуєте свою діяльність, як ви вже сказали, відводячись від неї, наприклад, натискаючи назад. Насправді, сценарій, в якому використовується ця збережена стаття, є те, коли Android знищує вашу активність для відпочинку. На приклад: якщо ви змінили мову свого телефону під час активності (і так потрібно завантажувати різні ресурси з вашого проекту). Ще один дуже поширений сценарій - це коли ви повертаєте телефон убік, щоб активність відтворювалася та відображалася у пейзажі.
villoren

16
Щоб отримати друге повідомлення, увімкніть "Не тримати діяльність" у параметрах розробників. Натисніть домашню кнопку та поверніться із залишків.
Ярослав Миткалик


6
Ви можете це зробити за допомогою: onSaveInstanceState (Пакет збереженийInstanceState)
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.
}

Пакет по суті є способом зберігання карти 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
Якийсь шанс це працює по телефону, але не в емуляторі? Я, здається, не можу отримати ненульовий збереженийInstanceState.
Адам Джек

491
ДЕРЖАВНО: перед тим, як додати свої значення до пакету, вам потрібно зателефонувати super.onSaveInstanceState (saveInstanceState), інакше вони будуть вимкнені під час цього виклику (Droid X Android 2.2).
jkschneider

121
Обережно: в офіційній документації зазначено, що слід зберігати важливу інформацію в методі onPause, оскільки метод onsaveinstance не є частиною життєвого циклу андроїда. developer.android.com/reference/android/app/Activity.html
schlingel

32
Цей факт фактично робить onSaveInstanceStateпрактично марним, крім випадків зміни орієнтації екрана. Майже у всіх інших випадках ви ніколи не можете покластися на це, і вам потрібно буде вручну зберегти свій стан інтерфейсу десь в іншому місці. Або запобігання вбиттю вашої програми за допомогою переважаючої поведінки кнопки НАЗАД. Я не розумію, чому вони взагалі так реалізували це. Зовсім неінтуїтивні. І ви не можете мати пакет Bundle, який дозволяє вам зберігати речі, за винятком цього конкретного способу.
чакрит

12
Зауважте, що збереження / відновлення стану користувальницького інтерфейсу до / з пакета автоматично береться за Views, яким було призначено ідентифікатори . З onSaveInstanceStateдокументів: "За замовчуванням реалізація забезпечує більшу частину стану інтерфейсу користувача для вас, викликаючи onSaveInstanceState()кожен перегляд в ієрархії, що має ідентифікатор, і зберігає ідентифікатор поточного зосередженого перегляду (усе це відновлено за замовчуванням реалізація onRestoreInstanceState(Bundle)) "
Vicky Chijwani

433

Це savedInstanceStateлише для збереження стану, пов’язаного з поточним екземпляром активності, наприклад, поточною інформацією про навігацію або вибір, так що якщо Android знищує та відтворює активність, вона може повернутися як раніше. Дивіться документацію для onCreateтаonSaveInstanceState

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


3
Коли зберігаєтьсяInstanceState == null, а коли це не null?
Троян.ЗБОТ

6
saveInstanceState є нульовим, коли система створює новий екземпляр вашої активності, а не null під час відновлення.
Габріель Камара

6
... що ставить питання про те, коли системі потрібно створити новий екземпляр діяльності. Деякі способи виходу з програми не створюють пакет, тому потрібно створити новий екземпляр. Це основна проблема; це означає, що не можна покладатися на існування пакету, і потрібно використовувати альтернативні способи постійного зберігання. Перевага OnSave / onRestoreInstanceState полягає в тому, що це механізм, який система може робити різко , не витрачаючи багато системних ресурсів. Тому добре підтримати це, а також мати постійне сховище для більш витонченого виходу з програми.
ToolmakerSteve

415

Зауважте, що це НЕ безпечно для використання onSaveInstanceStateта onRestoreInstanceState для постійних даних , згідно з документацією про Стани активності в http://developer.android.com/reference/android/app/Activity.html .

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

Зауважте, що важливо зберігати постійні дані onPause()замість того, onSaveInstanceState(Bundle) що пізніше не є частиною зворотного виклику життєвого циклу, тому він не буде викликаний у будь-якій ситуації, як описано в його документації.

Іншими словами, введіть код збереження / відновлення для постійних даних у onPause()та onResume()!

EDIT : Для подальшого роз'яснення ось onSaveInstanceState()документація:

Цей метод називається перед тим, як діяльність може бути вбита, щоб, коли він повернувся деякий час у майбутньому, він міг відновити свій стан. Наприклад, якщо активність B запускається перед активністю A, а в якийсь момент активність A вбивається для повернення ресурсів, активність A матиме шанс зберегти поточний стан свого користувальницького інтерфейсу за допомогою цього методу, так що коли користувач повернеться до активності А стан інтерфейсу користувача можна відновити через onCreate(Bundle)або onRestoreInstanceState(Bundle).


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

30
Це слід заборонити. Використовувати на (Збереження | Відновлення) InstanceState не безпечно, як методи життєвого циклу (тобто робити все інше в них, ніж зберегти / відновити стан). Вони ідеально підходять для збереження / відновлення стану. Крім того, як ви хочете зберегти / відновити стан у onPause та onResume? Ви не отримуєте пакетів у тих методах, якими ви можете скористатися, тому вам доведеться використовувати інші економії стану, в базах даних, файлах тощо, що є дурним.
Фелікс

141
Ми не повинні голосувати за цю людину, принаймні він доклав зусиль, щоб розглянути документацію, і я думаю, що ми, люди, для того, щоб насправді створити спільноту, яка обізнана, і допомагати один одному не ЗНАЙТИ ГОЛОСУ. тож 1 голос за зусилля, і я попрошу вас, щоб люди не проголосували, а голосували або не голосували .... ця людина очистить плутанину, яку хотілося б мати, переглядаючи документацію. 1 голос вгору :)
AZ_

21
Я не думаю, що ця відповідь заслуговує на ослаблення. Принаймні, він намагався відповісти і цитував розділ з doco.
GSree

34
Ця відповідь абсолютно правильна і заслуговує на голосування ВГО, а не! Дозвольте мені уточнити різницю між станами для тих хлопців, які цього не бачать. Стан GUI, як вибрані радіо кнопки та текст у полі введення, є набагато менш важливим, ніж стан даних, як записи, додані до списку, що відображається у 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) - обробка активного відпочинку під час обертання телефону (зміна орієнтації). Іншими словами, передбачуване використання - це коли Ваша діяльність все ще логічно "на вершині", але все одно має бути відновлена ​​системою. Збережений пакет не зберігається поза процесом / пам'яттю / gc, тому ви не можете по-справжньому розраховувати на це, якщо ваша діяльність піде на другий план. Так, можливо, пам’ять вашої активності переживе свою поїздку на другий план і вийде з GC, але це не є надійним (і це не передбачувано).

Отже, якщо у вас є сценарій, коли є значущий "прогрес користувача" або стан, який повинен зберігатися між "запусками" вашої програми, вказівки повинні використовувати onPause та onResume. Ви повинні вибрати і підготувати стійкий магазин самостійно.

АЛЕ - є дуже заплутана помилка, яка ускладнює все це. Деталі тут:

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

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

В основному, якщо ваш додаток запускається прапором SingleTask, а потім ви запускаєте його з головного екрана або меню запуску, то наступне виклик створить НОВЕ завдання ... у вас буде фактично два різних екземпляри вашої програми що мешкають у тій же стеці ... яка стає дуже дивною дуже швидко. Це здається, що ви запускаєте додаток під час розробки (тобто від Eclipse або Intellij), тому розробники стикаються з цим багато. Але також через деякі механізми оновлення магазину додатків (так це впливає і на ваших користувачів).

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

Поведінка натискання клавіш на дому

ОНОВЛЕННЯ Червень 2013 : Через кілька місяців я нарешті знайшов «правильне» рішення. Вам не потрібно самостійно керувати жодними державними прапорами startApp, ви можете виявити це з фреймворку та під заставу належним чином. Я використовую це на початку мого LauncherActivity.onCreate:

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

87

onSaveInstanceStateвикликається, коли системі потрібна пам'ять і вбиває додаток. Він не викликається, коли користувач просто закриває програму. Тому я думаю, що стан програми також має бути збережений у onPauseньому. Він повинен бути збережений у деякому постійному сховищі, як PreferencesабоSqlite


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

74

Обидва методи корисні та дійсні, і обидва найкраще підходять для різних сценаріїв:

  1. Користувач припиняє додаток та повторно відкриває його на більш пізній термін, але програмі потрібно перезавантажити дані з останнього сеансу - для цього потрібен стійкий підхід для зберігання даних, наприклад використання SQLite.
  2. Користувач перемикає програму, а потім повертається до оригіналу і хоче забрати місце, де вони зупинилися - зберегти та відновити дані пакету (наприклад, дані про стан додатків) onSaveInstanceState()і onRestoreInstanceState()зазвичай є достатніми.

Якщо зберегти дані стану постійно, вони можуть бути перезавантажені onResume()або onCreate()(або фактично під час будь-якого дзвінка життєвого циклу). Це може бути або не бажана поведінка. Якщо ви зберігаєте його в пакеті в InstanceState, тоді він є тимчасовим і підходить тільки для зберігання даних для використання в тому ж сеансі користувача (я термін "сесія" я використовую ", але не між" сеансами ".

Справа не в тому, що один підхід кращий за інший, як і все, просто важливо зрозуміти, яка поведінка вам потрібна, і вибрати найбільш відповідний підхід.


70

Що стосується мене, економія держави - це краща проблема в кращому випадку. Якщо вам потрібно зберегти стійкі дані, просто використовуйте базу даних SQLite . Android робить його SOOO легким.

Щось на зразок цього:

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. Однак вам потрібно додати нарешті {cursor.close ()}, інакше воно вийде з місця витоку пам'яті під час цього.
Номенон

3
Я натрапив на це, і хоча це здається акуратним, я не вагаюся спробувати використовувати це на Google Glass, який є пристроєм, над яким я працюю / з останнім часом.
Стівен Тетро

61

Я думаю, що знайшов відповідь. Дозвольте мені сказати, що я зробив простими словами:

Припустимо, у мене є дві дії, активність1 і активність2, і я переходжу від діяльності1 до діяльності2 (я виконав деякі роботи в діяльності2) і знову повернувся до діяльності 1, натиснувши кнопку в діяльності1. Тепер на цьому етапі я хотів повернутися до Activity2, і я хочу бачити свою активність2 в тому ж стані, коли я востаннє залишив активність2.

Для вищезазначеного сценарію я зробив те, що в маніфесті я вніс деякі зміни на зразок цього:

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

І в aktivnosti1 на події натискання кнопки я зробив так:

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
Це відповідь на іншу ситуацію: дві дії в одному додатку. OP - це вихід із програми (наприклад, кнопка додому чи інші засоби для переходу на інший додаток).
ToolmakerSteve

44

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

onSaveInstanceState () викликає Android, якщо діяльність припиняється і може бути вбита до її відновлення! Це означає, що він повинен зберігати будь-який стан, необхідний для повторної ініціалізації до того ж стану при повторному запуску Діяльності. Це аналог методу onCreate (), і насправді пакет пакета збережених інстанцій передається в onCreate () - це той самий набір, який ви побудуєте як outState у методі onSaveInstanceState ().

onPause () та onResume () також є безкоштовними методами. onPause () завжди викликається, коли активність закінчується, навіть якщо ми спонукали до цього (наприклад, виклик закінчення ()). Ми будемо використовувати це для того, щоб зберегти поточну нотатку назад до бази даних. Доброю практикою є звільнення будь-яких ресурсів, які можуть бути випущені під час 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 недійсне, оскільки ваша активність ніколи не створюється заново.

Ваша активність буде відтворена лише з пакетом стану, коли:

  • Зміни конфігурації, такі як зміна орієнтації або мови телефону, що може вимагати створення нового екземпляра активності.
  • Ви повертаєтесь у додаток із фону після того, як ОС знищила активність.

Android знищить фонові дії під тиском пам’яті або після того, як вони знаходяться у фоновому режимі протягом тривалого періоду часу.

Перевіряючи приклад свого привітного світу, існує кілька способів залишити та повернутися до Діяльності.

  • Після натискання кнопки "назад" активність закінчена. Повторний запуск програми - це абсолютно новий екземпляр. Ви взагалі не поновлюєтесь із фону.
  • Якщо ви натиснете кнопку додому або використовуєте перемикач завдань, активність відійде на другий план. Під час повернення до програми onCreate буде викликатися лише у випадку, якщо Діяльність довелося знищити.

У більшості випадків, якщо ви просто натискаєте будинок, а потім знову запускаєте додаток, активність не потрібно буде створювати заново. Він уже існує в пам'яті, тому onCreate () не буде викликатися.

Є параметр у розділі Налаштування -> Параметри розробника під назвою "Не тримати діяльність". Коли він увімкнено, Android завжди буде знищувати дії та відтворювати їх у фоновому режимі. Це чудовий варіант, який можна залишати ввімкненим під час розробки, оскільки він імітує найгірший сценарій. (Пристрій із низькою пам’яттю весь час переробляє вашу діяльність).

Інші відповіді цінні тим, що вони навчають вас правильних способів зберігання стану, але я не відчував, що вони дійсно відповіли, Чому ваш код не працює так, як ви очікували.


28

onSaveInstanceState(bundle)І onRestoreInstanceState(bundle)методи можуть бути використані для зберігання даних тільки при обертанні екрану (зміна орієнтації).
Вони навіть не добре , а перемикання між додатками (оскільки onSaveInstanceState()метод викликаються , але onCreate(bundle)і onRestoreInstanceState(bundle)не викликається знову.
Для більшого використання збереження загальних переваг. Читати цю статтю


2
У вашому випадку onCreateі onRestoreInstanceStateвас не викликають, тому що це Activityвзагалі не знищується при переключенні програм, тому не потрібно нічого відновляти. Android дзвонить onSaveInstanceStateна випадок, якщо активність пізніше знищиться (що відбувається зі 100% впевненістю при обертанні екрана, оскільки вся конфігурація пристрою змінилася і активність потрібно знову створити з нуля).
Vicky Chijwani

20

Моя проблема полягала в тому, що мені потрібна була наполегливість лише протягом життя програми (тобто одне виконання, включаючи запуск інших суб-заходів у межах тієї самої програми та обертання пристрою тощо). Я спробував різні комбінації вищезазначених відповідей, але не отримав того, що хотів у будь-яких ситуаціях. Зрештою, для мене працювало - отримати посилання на збереженийInstanceState під час 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
    }

помістіть свої значення в об'єкт "outState", як-от outState.putString ("ключ", "Ласкаво просимо"), і збережіть, подзвонивши супер. Коли діяльність буде знищена, стан зберігається в об'єкті 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".

Я дійсно не рекомендую використовувати другий метод. Оскільки в моєму досвіді це спричиняло половину екрану пристрою чорним під час обертання від портрета до пейзажу та навпаки.

Використовуючи перший метод, згаданий вище, ми можемо зберігати дані, коли зміна орієнтації або будь-яка зміна конфігурації. Я знаю спосіб, коли ви можете зберігати будь-які типи даних всередині об'єкта стану збереженого Інстанції.

Приклад: Розгляньте випадок, якщо ви хочете зберегти об'єкт 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

Ось коментар від Steve Моз відповіді «s (по ToolmakerSteve ) , що ставить речі в перспективу (в цілому onSaveInstanceState проти OnPause, схід вартості проти західної саги вартості)

@VVK - я частково не згоден. Деякі способи виходу з програми не запускають OnSaveInstanceState (oSIS). Це обмежує корисність oSIS. Його варто підтримувати, для мінімальних ресурсів ОС, але якщо програма хоче повернути користувача до стану, в якому вони були, незалежно від того, як програма виходила, замість цього потрібно використовувати стійкий підхід для зберігання. Я використовую onCreate для перевірки наявності пакету, а якщо він відсутній, то перевіряю постійне зберігання. Це централізує процес прийняття рішень. Я можу відновитись після аварії, виходу із кнопки "Назад" або спеціального пункту меню "Вихід", або повернутися до екрана користувач був через багато днів. - ToolmakerSteve 19 вересня 1515 о 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(), спочатку потрібно зберегти дані в збереженомуInstanceState шляхом зміниSaveInstanceState(Bundle savedInstanceState) .

Коли SaveInstanceState(Bundle savedInstanceState)метод знищення діяльності викликається, ви там зберігаєте дані, які хочете зберегти. І ви отримуєте те ж саме, onCreate()коли активація перезапускається. (ЗбереженийInstanceState не буде нульовим, оскільки ви зберегли в ньому деякі дані до того, як активність знищиться)


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'
}

Тепер давайте перевіримо цей приклад нижче, як зберегти стан в Активності

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, щоб знайти більше зразка коду.
ДЯКУЮ Phereum

@ChetanMehra ви маєте на увазі клас перегляду, так? Якщо це власний вигляд, ми можемо замінити onSaveInstanceState і onRestoreInstanceState, як вище приклад CustomView.
ДЯКУЮ Phereum,

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

@THANNPhearum Чи варто задати це як інше питання?
Четан Мехра

Розумію. Якщо так, ваш ClassA повинен бути розбірним. Як згадувалося, він працює для діяльності, фрагментів або будь-якого об'єкта, який потребує серіалізації свого стану на
пачці

6

Не впевнений, чи моє рішення спохмурнів чи ні, але я використовую прив’язану службу до збереження стану ViewModel. Незалежно від того, зберігаєте ви його в пам'яті в сервісі або зберігаєте і отримуєте її з бази даних SQLite, залежить від ваших вимог. Це те, що роблять послуги будь-якого смаку, вони надають такі послуги, як підтримка стану програми та абстрактна загальна бізнес-логіка.

Через пам'ять та обмеження обробки, властиві мобільним пристроям, я переглядаю перегляди Android аналогічно веб-сторінці. Сторінка не підтримує стан, це лише компонент шару презентації, єдиною метою якого є представлення стану програми та прийняття вводу користувача. Останні тенденції в архітектурі веб-додатків використовують використання вікової моделі Model, View, Controller (MVC), де сторінка - Перегляд, дані домену - модель, а контролер - за веб-службою. Таку ж схему можна використати в Android, якщо View є, ну ... Перегляд, модель - це ваші дані домену, а Контролер реалізований як Android-сервіс. Щоразу, коли ви хочете, щоб перегляд взаємодіяв з контролером, прив'яжіть його до запуску / відновлення та від'єднайте під час зупинки / паузи.

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


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
Це не правда. З документації: "На відміну від збереженого стану екземпляра, ViewModels знищуються під час загибелі процесу, ініційованої системою. Ось чому ви повинні використовувати об'єкти ViewModel у поєднанні з onSaveInstanceState () (або іншою стійкістю диска), зберігаючи ідентифікатори в збереженомуInstanceState, щоб допомогти переглянути моделі перезавантажують дані після смерті системи. "
В’ячеслав Мартиненко

Щойно натрапив на це з дозволами, що змінюються на задньому плані.
Брілл Паппін

Я погоджуюсь, з doc "якщо вам потрібно обробити ініційовану системою смерть, ви можете використовувати onSaveInstanceState () як резервну копію."
Жар

2

Існує спосіб змусити Android зберігати держави, не застосовуючи жодного методу. Просто додайте цей рядок у свій Маніфест у декларації про діяльність:

android:configChanges="orientation|screenSize"

Це повинно виглядати так:

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

Ось ви можете знайти більше інформації про цю власність.

Рекомендується дозволити Android впоратися з цим замість вас, ніж вручну.


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

1
Ця відповідь призначена для тих, хто хоче врятувати стан, коли орієнтація змінилася і хоче уникнути розуміння та впровадження складного способу
IgniteCoders

Досить справедливо. Я бачу вашу думку, я думаю, що більшість людей, які намагаються зберегти стан, використовують фрагменти, оскільки діяльність фактично зберігає статистику компонентів інтерфейсу, якщо вони мають ідентифікатор, але фрагменти є більш спеціальними, я використовував фрагменти один раз, але я ніколи не використовую З ними знову зберегти статистику екземпляра було болем боротися
lord-ralf-adolf

працює ... дякую
Фанадес

1

Що економити, а чого не робити?

Ніколи не замислювалися, чому текст у програмі EditTextавтоматично зберігається під час зміни орієнтації? Ну, ця відповідь саме для вас.

Коли екземпляр діяльності знищується і система відтворює новий екземпляр (наприклад, зміна конфігурації). Він намагається відтворити його за допомогою набору збережених даних старого стану активності ( стан екземпляра ).

Стан екземпляра - це сукупність пар ключів і значень, що зберігаються в Bundleоб'єкті.

За замовчуванням система зберігає, наприклад, об'єкти View у пакеті.

  • Текст в EditText
  • Положення прокрутки в а ListViewтощо

Якщо вам потрібна інша змінна, яку потрібно зберегти як частину стану екземпляра, слід застосувати метод OVERRIDE onSavedInstanceState(Bundle savedinstaneState) .

Наприклад, int currentScoreв GameActivity

Більш детально про onSavedInstanceState (пакет збереженихinstaneState) при збереженні даних

@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 (пакет збереженихinstaneState)

@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)викликає систему лише тоді, коли користувач має намір повернутися до Діяльності. Наприклад, ви використовуєте App 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.