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