Урок 70. onSaveInstanceState. Сохранение данных Activity при повороте экрана
В этом уроке:
- сохраняем данные при повороте экрана
Теорию по этому вопросу можно почитать тут. Я здесь вкратце дам вольный перевод.
Когда работа Activity приостанавливается(onPause или onStop), она остается в памяти и хранит все свои объекты и их значения. И при возврате в Activity, все остается, как было. Но если приостановленное Activity уничтожается, например, при нехватке памяти, то соответственно удаляются и все его объекты. И если к нему снова вернуться, то системе надо заново его создавать и восстанавливать данные, которые были утеряны при уничтожении. Для этих целей Activity предоставляет нам для реализации пару методов: первый позволяет сохранить данные – onSaveInstanceState, а второй – восстановить - onRestoreInstanceState.
Эти методы используются в случаях, когда Activity уничтожается, но есть вероятность, что оно еще будет востребовано в своем текущем состоянии. Т.е. при нехватке памяти или при повороте экрана. Если же вы просто нажали кнопку Back (назад) и тем самым явно сами закрыли Activity, то эти методы не будут выполнены.
Но даже если не реализовать эти методы, у них есть реализация по умолчанию, которая сохранит и восстановит данные в экранных компонентах. Это выполняется для всех экранных компонентов, у которых есть ID.
Создадим простое приложение, чтобы протестить все эти тезисы. Посмотрим, в какой момент вызываются эти методы, попробуем в них что-нить сохранить. Также убедимся, что необходимо вызывать соответствующие методы супер-класса, чтобы сохранялись данные экранных компонентов.
Т.к. нам надо будет поворачивать экран, используйте при разработке Android 2.2. В AVD с версией 2.3 поворот глючит.
Создадим проект:
Project name: P0701_SaveInstanceState
Build Target: Android 2.2
Application name: SaveInstanceState
Package name: ru.startandroid.develop.p0701saveinstancestate
Create Activity: MainActivity
В strings.xml пропишем тексты:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">SaveInstanceState</string>
<string name="count">Count</string>
</resources>
В main.xml нарисуем кнопку и пару полей для ввода текста:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onclick"
android:text="@string/count">
</Button>
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10">
<requestFocus>
</requestFocus>
</EditText>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10">
</EditText>
</LinearLayout>
Обратите внимание, что второй EditText без ID.
В MainActivity будем вызывать все методы Lifecycle и два вышеописанных:
package ru.startandroid.develop.p0701saveinstancestate;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends Activity {
final String LOG_TAG = "myLogs";
int cnt = 0;
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.d(LOG_TAG, "onCreate");
}
protected void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "onDestroy");
}
protected void onPause() {
super.onPause();
Log.d(LOG_TAG, "onPause");
}
protected void onRestart() {
super.onRestart();
Log.d(LOG_TAG, "onRestart");
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(LOG_TAG, "onRestoreInstanceState");
}
protected void onResume() {
super.onResume();
Log.d(LOG_TAG, "onResume ");
}
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(LOG_TAG, "onSaveInstanceState");
}
protected void onStart() {
super.onStart();
Log.d(LOG_TAG, "onStart");
}
protected void onStop() {
super.onStop();
Log.d(LOG_TAG, "onStop");
}
public void onclick(View v) {
}
}
В каждом из них пишем лог, чтобы отследить последовательность вызовов. Метод onclick пока не реализуем.
Все сохраним и запустим. Введем в текстовые поля какие-нить данные:
и повернем экран CTRL+F12.
В итоге видим:
Данные в первом поле сохранились при повороте, а во втором пропали. Это произошло потому, что дефолтовые методы сохранения/восстановления умеют работать только с компонентами, которые имеют ID. Посмотрим лог.
onCreate
onStart
onResume
Эти три метода выполнились при запуске.
Затем мы повернули экран:
onSaveInstanceState
onPause
onStop
onDestroy
onCreate
onStart
onRestoreInstanceState
onResume
Первым делом вызывается onSaveInstanceState, здесь нам надо будет реализовывать сохранение своих данных. Далее идет уничтожение Activity (onPause, onStop, onDestroy) и создание нового onCreate, onStart. И перед onResume вызывается метод восстановления данных – onRestoreInstanceState.
Последовательность мы рассмотрели - сохраняются данные перед onPause, а восстанавливаются перед onResume. Попробуем теперь что-нибудь сохранить и восстановить. У нас на экране есть кнопка, будем по ее нажатию увеличивать счетчик нажатий на единицу и выводить всплывающее сообщение с итоговым кол-вом нажатий. Переменная cnt у нас уже есть. Реализуем onclick:
public void onclick(View v) {
Toast.makeText(this, "Count = " + ++cnt, Toast.LENGTH_SHORT).show();
}
Повернем эмулятор обратно в вертикальную ориентацию. Запустим приложение, и жмем на кнопку Count. Видим сообщение с кол-вом нажатий. Нажмем еще несколько раз, получим, например 5.
Теперь повернем экран и снова нажмем кнопку.
Мы видим, что счетчик сбросился.
Это произошло потому, что текущий объект Activity был уничтожен и потерял значения всех переменных, в том числе и cnt. При создании нового Activity значение cnt равно 0 и отсчет пошел заново. Давайте это пофиксим. Реализуем метод сохранения onSaveInstanceState:
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("count", cnt);
Log.d(LOG_TAG, "onSaveInstanceState");
}
В объект outState мы пишем значение переменной cnt. Механизм аналогичен помещению данных в Intent.
Метод восстановления onRestoreInstanceState:
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
cnt = savedInstanceState.getInt("count");
Log.d(LOG_TAG, "onRestoreInstanceState");
}
Из savedInstanceState вытаскиваем значение и помещаем в переменную cnt. Теперь при уничтожении и воссоздании Activity переменная cnt сохранит свое значение, и наш счетчик продолжит работать.
Проверим. Вернем AVD в вертикальную ориентацию. Все сохраним, запустим приложение. Понажимаем на кнопку, немного накрутим счетчик
и поворачиваем экран.
Жмем снова кнопку
счетчик не сбросился, а продолжил увеличиваться с последней позиции.
Итак, методы onSaveInstanceState и onRestoreInstanceState по дефолту сохраняют данные в экранных компонентах. Если мы реализуем их самостоятельно, то вызываем методы супер-класса и пишем свой код для своих переменных. Ради интереса, можете попробовать убрать вызовы методов суперкласса из onSaveInstanceState и onRestoreInstanceState. Данные в текстовом поле перестанут сохраняться при повороте экрана.
Кроме метода onRestoreInstanceState, доступ к сохраненным данным также можно получить в методе onCreate. На вход ему подается тот же самый Bundle. Если восстанавливать ничего не нужно, он будет = null.
Есть еще один полезный механизм сохранения данных. Android дает нам возможность сохранить ссылку на какой-либо объект и вернуть ее в новый созданный Activity. Для этого существуют методы:
onRetainNonConfigurationInstance – в нем мы сохраняем ссылку, передавая ее на выход (return) метода
getLastNonConfigurationInstance – этот метод ссылку нам возвращает
Т.е., например, у нас есть какой то объект myObj (класс MyObject) и нам надо сохранить ссылку на него при повороте экрана.
Мы реализуем в Activity метод onRetainNonConfigurationInstance:
public Object onRetainNonConfigurationInstance() {
return myObj;
}
Этот метод будет вызван перед уничтожением Activity. От нас требуется дать на выход этому методу наш объект, который надо сохранить.
А, при создании нового Activity, в onCreate (например) мы используем метод getLastNonConfigurationInstance:
myObj = (MyObject) getLastNonConfigurationInstance();
Мы получили обратно объект класса Object и привели его к нашему классу MyObject.
На следующем уроке:
- используем Preferences для работы с настройками приложения