Настройки и состояние приложения

Сохранение состояния приложения

Последнее обновление: 17.10.2021

В одной из предыдущих тем был рассмотрен жизненный цикл 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:

Состояние activity в Android и Java

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

Состояние Bundle activity в Android и Java

И даже если мы попробуем заново получить значение из переменной name, то мы увидим, что она обнулилась:

Сохранение состояния activity в Android и Java

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

onRestoreInstanceState и onSaveInstanceState 	 в Android и Java
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850