В одной из предыдущих тем был рассмотрен жизненный цикл Activity в приложении на Android, где после создания Activity вызывался метод onRestoreInstanceState, который восстанавливал ее состояние, а перед завершением работы вызывался метод onSaveInstanceState, который сохранял состояние Actiity. Оба этих метода в качестве параметра принимают объект Bundle, который как раз и хранит состояние activity:
protected void onRestoreInstanceState(Bundle saveInstanceState); protected void onSaveInstanceState(Bundle saveInstanceState);
В какой ситуации могут быть уместно использование подобных методов? Банальная ситуация - переворот экрана и переход от портретной ориентации к альбомной и наоборот. Если, к примеру, графический интерфейс содержит текстовое поле для вывода TextView, и мы программно изменяем его текст, то после изменения ориентации экрана его текст может исчезнуть. Или если у нас глобальные переменные, то при изменении ориентации экрана их значения могут быть сброшены до значений по умолчанию.
Чтобы точнее понять проблему, с которой мы можем столкнуться, рассмотрим пример. Изменим файл activity_main следующим образом:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <EditText android:id="@+id/nameBox" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="Введите имя" app:layout_constraintBottom_toTopOf="@id/saveButton" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/saveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Сохранить" android:onClick="saveName" app:layout_constraintBottom_toTopOf="@id/nameView" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/nameBox"/> <TextView android:id="@+id/nameView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" app:layout_constraintBottom_toTopOf="@id/getButton" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/saveButton"/> <Button android:id="@+id/getButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Получить имя" android:onClick="getName" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/nameView"/> </androidx.constraintlayout.widget.ConstraintLayout>
Здесь определено поле EditText, в которое вводим имя. И также определена кнопка для его сохранения.
Далее для вывода сохраненного имени предназначено поле TextView, а для получения сохраненного имени - вторая кнопка.
Теперь изменим класс MainActivity:
package com.example.settingsapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity { String name ="undefined"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void saveName(View view) { // получаем введенное имя EditText nameBox = findViewById(R.id.nameBox); name = nameBox.getText().toString(); } public void getName(View view) { // получаем сохраненное имя TextView nameView = findViewById(R.id.nameView); nameView.setText(name); } }
Для хранения имени в программе определена переменная name. При нажатии на первую кнопку сохраняем текст из EditText в переменную name, а при нажатии на вторую кнопку - обратно получаем текст из переменной name в поле TextView.
Запустим приложение введем какое-нибудь имя, сохраним и получим его в TextView:
Но если мы перейдем к альбомному режиму, то TextView окажется пустым, несмотря на то, что в него вроде бы уже получили нужное значение:
И даже если мы попробуем заново получить значение из переменной name, то мы увидим, что она обнулилась:
Чтобы избежать подобных ситуаций как раз и следует сохранять и восстанавливать состояние activity. Для этого изменим код MainActivity:
package com.example.settingsapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity { String name ="undefined"; final static String nameVariableKey = "NAME_VARIABLE"; TextView nameView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); nameView = findViewById(R.id.nameView); } // сохранение состояния @Override protected void onSaveInstanceState(Bundle outState) { outState.putString(nameVariableKey, name); super.onSaveInstanceState(outState); } // получение ранее сохраненного состояния @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); name = savedInstanceState.getString(nameVariableKey); nameView.setText(name); } public void saveName(View view) { // получаем введенное имя EditText nameBox = findViewById(R.id.nameBox); // сохраняем его в переменную name name = nameBox.getText().toString(); } public void getName(View view) { // выводим сохраненное имя nameView.setText(name); } }
В методе onSaveInstanceState()
сохраняем состояние. Для этого вызываем у параметра Bundle метод putString(key, value),
первый параметр которого - ключ, а второй - значение сохраняемых данных. В данном случае мы сохраняем строку, поэтому вызываем метод putString()
. Для
сохранения объектов других типов данных мы можем вызвать соответствующий метод:
put(): универсальный метод, который добавляет значение типа Object. Соответственно поле получения данное значение необходимо преобразовать к нужному типу
putString(): добавляет объект типа String
putInt(): добавляет значение типа int
putByte(): добавляет значение типа byte
putChar(): добавляет значение типа char
putShort(): добавляет значение типа short
putLong(): добавляет значение типа long
putFloat(): добавляет значение типа float
putDouble(): добавляет значение типа double
putBoolean(): добавляет значение типа boolean
putCharArray(): добавляет массив объектов char
putIntArray(): добавляет массив объектов int
putFloatArray(): добавляет массив объектов float
putSerializable(): добавляет объект интерфейса Serializable
putParcelable(): добавляет объект Parcelable
Каждый такой метод также в качестве первого параметра принимает ключа, а в качестве второго - значение.
В методе onRestoreInstanceState происходит обратный процесс - с помощью метода getString(key) по ключу получаем из сохраненного состояния строку по ключу. Соответственно для получения данных других типов мы можем использовать аналогичные методы:
get(): универсальный метод, который возвращает значение типа Object. Соответственно поле получения данное значение необходимо преобразовать к нужному типу
getString(): возвращает объект типа String
getInt(): возвращает значение типа int
getByte(): возвращает значение типа byte
getChar(): возвращает значение типа char
getShort(): возвращает значение типа short
getLong(): возвращает значение типа long
getFloat(): возвращает значение типа float
getDouble(): возвращает значение типа double
getBoolean(): возвращает значение типа boolean
getCharArray(): возвращает массив объектов char
getIntArray(): возвращает массив объектов int
getFloatArray(): возвращает массив объектов float
getSerializable(): возвращает объект интерфейса Serializable
getParcelable(): возвращает объект Parcelable
Для примера рассмотрим сохранение-получение более сложных данных. Например, объектов определенного класса. Пусть у нас есть класс User:
package com.example.settingsapp; import java.io.Serializable; public class User implements Serializable { private String name; private int age; public User(String name, int age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
Класс User реализует интерфейс Serializable, поэтому мы можем сохранить его объекты с помощью метода putSerializable()
, а получить
с помощью метода getSerializable()
.
Пусть у нас будет следующий интерфейс в activity_main.xml:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <EditText android:id="@+id/nameBox" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="Введите имя" app:layout_constraintBottom_toTopOf="@id/yearBox" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/yearBox" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="Введите возраст" android:inputType="numberDecimal" app:layout_constraintBottom_toTopOf="@id/saveButton" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/saveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Сохранить" android:onClick="saveData" app:layout_constraintBottom_toTopOf="@id/dataView" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/yearBox"/> <TextView android:id="@+id/dataView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" app:layout_constraintBottom_toTopOf="@id/getButton" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/saveButton"/> <Button android:id="@+id/getButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Получить данные" android:onClick="getData" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/dataView"/> </androidx.constraintlayout.widget.ConstraintLayout>
Здесь определены два поля ввода для имени и возраста соответственно.
В классе MainActivity пропишем логику сохранения и получения данных:
package com.example.settingsapp; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity { User user = new User("undefined", 0); final static String userVariableKey = "USER_VARIABLE"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } // сохранение состояния @Override protected void onSaveInstanceState(Bundle outState) { outState.putSerializable(userVariableKey, user); super.onSaveInstanceState(outState); } // получение ранее сохраненного состояния @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // получаем объект User в переменную user = (User)savedInstanceState.getSerializable(userVariableKey); TextView dataView = findViewById(R.id.dataView); dataView.setText("Name: " + user.getName() + " Age: " + user.getAge()); } public void saveData(View view) { // получаем введенные данные EditText nameBox = findViewById(R.id.nameBox); EditText yearBox = findViewById(R.id.yearBox); String name = nameBox.getText().toString(); int age = 0; // значение по умолчанию, если пользователь ввел некорректные данные try{ age = Integer.parseInt(yearBox.getText().toString()); } catch (NumberFormatException ex){} user = new User(name, age); } public void getData(View view) { // получаем сохраненные данные TextView dataView = findViewById(R.id.dataView); dataView.setText("Name: " + user.getName() + " Age: " + user.getAge()); } }
Здесь также сохраняем данные в переменную User, которая предварительно инициализированна некоторыми данными по умолчанию. А при нажатии на кнопку получения получем данные из переменной и передаем их для вывода в текстовое поле.