9 апреля 2014 г.

Сохранение состояния Активности. Часть 3 (практика)

Мое приложение AP003 продолжает эволюционировать.

Я добавил два поля EditText, одно с ID другое без него (бирюзового цвета). А так же еще одно поле TextView, в которое буду записывать сохраненные данные из поля EditText. Приведу скрин для наглядности:

S0001

Розовое поле EditText имеет ID, бирюзовое – нет. В элемент TextView с текстом “Просто текст”, будет писаться сохраненное значение из розового поля EditText.

Код разметки layout_a.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f20808"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".ActivityA" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/strA" />

    <Button
        android:id="@+id/buttonStartB"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/textView1"
        android:onClick="onClickStartB"
        android:text="Start Activity B" />

    <TextView
        android:id="@+id/textStateActA"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/buttonStartB"
        android:layout_below="@+id/buttonStartB"
        android:background="#fffcfc"
        android:text="Этот экземпляр АсtivityA запущен в первый раз" />

    <TextView
        android:id="@+id/textActCountA"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textStateActA"
        android:layout_below="@+id/textStateActA"
        android:layout_marginTop="15dp"
        android:background="#fffcfc"
        android:text="TextView" />

    <EditText
        android:id="@+id/editTextA"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textActCountA"
        android:layout_below="@+id/textActCountA"
        android:layout_marginTop="15dp"
        android:background="#ffaacc"
        android:ems="10" />

    <TextView
        android:id="@+id/textViewA"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/editTextA"
        android:layout_below="@+id/editTextA"
        android:layout_marginTop="14dp"
        android:background="#faaaaa"
        android:text="Просто текст" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textViewA"
        android:layout_below="@+id/textViewA"
        android:layout_marginTop="18dp"
        android:background="#22ddff"
        android:ems="10" >

        <requestFocus />
    </EditText>

</RelativeLayout>

В строке 47 определен ID для EditText android:id="@+id/editTextA". Как видно из кода от строки 67 у другого элемента EditText нет ID. Это сделано для демонстрации того, что текст в данном поле не будет сохранятся системой при смене ориентации экрана.

В поле TextView с ID android:id="@+id/textViewA" будет писаться текст, введенный в поле с ID android:id="@+id/editTextA".

Теперь код ActivityA.java:

package com.example.ap0003;

import java.util.List;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class ActivityA extends Activity {

 final String TAG = "States";
 TextView tvTextLife, MyTextA, vTextA;
 List<ActivityManager.RunningTaskInfo> list;
 ActivityManager am;
 Integer TotalActCount;
 Boolean FirstStart;
 Boolean NextAct;
 static final String SaveTextEditA = "TEXTEDIT_A_STATE";
 SharedPreferences sPref;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState); //супер класс всегда вызывайте первым
  setContentView(R.layout.layout_a);
  
  // флаг что активность запущена впервые
  FirstStart = true;
  // кнопка запуска следующей Активности не нажималась
  NextAct = false;
  if (savedInstanceState != null) {
         // Restore value of members from saved state
   // находим текстовое поле по его идентификатору
   vTextA = (TextView) findViewById(R.id.textViewA);
   // и присваиваем ему сохраненное в методе onSaveInstanceState значение
   vTextA.setText(savedInstanceState.getString(SaveTextEditA));
   Log.d(TAG, "ActivityA: onCreate() NOT NULL: " + vTextA.getText().toString());
     } else {
         // Probably initialize members with default values for a new instance
      Log.d(TAG, "ActivityA: onCreate() NULL");
     }
  
  Log.d(TAG, "ActivityA: onCreate()");

 }

 @Override
 protected void onStart() {
  super.onStart();
  Log.d(TAG, "ActivityA: onStart()");
 }

 @Override
 protected void onResume() {
  super.onResume();
  Log.d(TAG, "ActivityA: onResume()");
  // получаем список 10 последних задач
  am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
  list = am.getRunningTasks(10);
  // перебираем список задач и выбираем свою по имени пакета
  // com.example.ap0003
  for (RunningTaskInfo task : list) {
   if (task.baseActivity.flattenToShortString().startsWith(
     "com.example.ap0003")) {
    // находим поле для вывода информации о количестве запущенных
    // Активностей
    tvTextLife = (TextView) findViewById(R.id.textActCountA);
    TotalActCount = task.numActivities;
    // коррекция счетчика для кнопки ОБРАТНО
    if (NextAct == true & FirstStart == false)
     TotalActCount = TotalActCount - 1;
    // выводим количество Активностей в задаче
    tvTextLife.setText("Activites in task " + TotalActCount);
    // коррекция счетчика для кнопки ДОМОЙ
    NextAct = false;
   }
  }
 }

 @Override
 protected void onPause() {
  super.onPause();
  Log.d(TAG, "ActivityA: oPause()");
  // флаг что активность уже была запущена
  FirstStart = false;
  // находим текстовое поле по его идентификатору
  tvTextLife = (TextView) findViewById(R.id.textStateActA);
  // присваиваем значение атрибуту Text для выбранного TextView
  tvTextLife.setText("Этот экземпляр ActivityA уже был запущен!");

 }

 @Override
 protected void onStop() {
  super.onStop();
  Log.d(TAG, "ActivityA: onStop()");
 }

 @Override
 protected void onRestart() {
  super.onRestart();
  Log.d(TAG, "ActivityA: onRestart()");
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();
  Log.d(TAG, "ActivityA: onDestroy()");
 }

 public void onClickStartB(View v) {
  Intent intent = new Intent(ActivityA.this, ActivityB.class);
  startActivity(intent);
  // кнопка запуска следующей Активности была нажата
  NextAct = true;

 }

 @Override
 protected void onSaveInstanceState(Bundle savedInstanceState) {
  // получаем ссылку на поле ввода текста
  MyTextA = (EditText) findViewById(R.id.editTextA);
  // сохраняем состояние поля ввода текста
  savedInstanceState.putString(SaveTextEditA, MyTextA.getText()
    .toString());
  Log.d(TAG, "onSaveInstanceState A text: "
    + MyTextA.getText().toString());
  // всегда вызывайте супер класс чтобы сохранить
  // состояние всех view активности
  super.onSaveInstanceState(savedInstanceState);

 }

 @Override
 protected void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  // находим текстовое поле по его идентификатору
  vTextA = (TextView) findViewById(R.id.textViewA);
  // и присваиваем ему сохраненное в методе onSaveInstanceState значение
  vTextA.setText(savedInstanceState.getString(SaveTextEditA));
  Log.d(TAG, "onRestoreInstanceState A: " + vTextA.getText().toString());
 }

}

Теперь приведу скрины и логи работы приложения.

И так запускаем приложение и вводим в поля для ввода текста следующий текст:

S0002

И так приложение было запущенно впервые. Мы ввели текст в соответствующие поля. Теперь посмотрим логи работы приложения.

S0003

В логах видим как отработали строки кода 46 (поскольку еще не было сохранения данных в Bundle) и строка 49.

Теперь нажмем кнопку HOME и посмотрим что будет.

S0004

И так мы видим что сработал метод onSaveInstanceState() перед методом onPause(). При выводе в отладке текста который мы ввели в поле сработала строка 132. Теперь наша Активность А находится в остановленном состоянии. Вернемся к ней жмакнув по ее иконке в списке приложений.

Мы увидим следующее

S0006

Текст сохранился в обоих полях ввода, так как Активность А не была уничтожена, а была просто остановлена. Посмотрим логи.

S0005

Логи это же и показывают, что активность была выведена из остановленного состояния в активное. Метод onCreate() не был запущен, так как Активность А не уничтожалась.

Теперь повернем экран. И посмотрим что будет.

S0007

Посмотрим логи.

S0008

Как видно из логов, при повороте экрана Активность А уничтожается, но перед этим вызывается метод onSaveInstanceState(). Затем в методе onCreate() происходит восстановление данных и их запись в поле TextView (строки 30 и 42), так как объект Bundle у нас уже не равен null. Так же восстановление данных происходит в методе onRestoreInstanceState(). Эти два метода дублирую друг друга, можно было бы обойтись любым из них, но так как это учебный пример, то это сделано для наглядности и пущего понимания процессов.

Теперь добавим в розовое поле ввода текста три восклицательных знака, а в бирюзовое снова введем любой текст.

S0009

А затем снова повернем экран.

S0010

Как видим текст в полях стал одинаковым. Так как сработали вышеописанные методы.

Посмотрим логи.

S0011

Собственно из логов видно, что перед уничтожением Активности А, измененный текст (с добавленными !!!) был сохранен, а затем восстановлен. Текст же в бирюзовом поле все время, после уничтожения Активности А, исчезает, так как у этого поля нет ID.

В общем все как было описано в матчасти.

Теперь, ради более полного понимания, закомментируем строки 136 и 142 которые вызывают суперкласс в методах onSaveInstanceState() и onRestoreInstanceState(). И посмотрим что будет.

Примечание: Вызов суперкласса в методе onCreate() закомментировать нельзя, иначе приложение просто не запуститься.

И так запускаем измененное приложение и вводим текст в поля.

S0012

Логи:

S0013

Теперь повернем экран

S0014

И посмотрим логи

S0015

Как видим сработали все наши методы, НО текст в розовом поле не был восстановлен, так как мы не вызвали СУПЕРКЛАСС и поэтому было сохранено и сделано только то, что мы явно указали.

И так, опять, все как в матчасти. Улыбка

Далее еще чуть чуть модифицируем приложение, чтобы еще глубже понять данную тему. И немного попрактиковаться.

И на последок еще линки для внимательного изучения по данной теме, и немного текста с этих источников, а то вдруг они исчезнут.

Методы Активности.

И текст по теме от туда.

Метод onSaveInstanceState()

Когда система завершает активность в принудительном порядке, чтобы освободить ресурсы для других приложений, пользователь может снова вызвать эту активность с сохранённым предыдущим состоянием. Чтобы зафиксировать состояние активности перед её уничтожением, в классе активности необходимо реализовать метод onSaveinstancestate().

Сам метод вызывается прямо перед методом onPause(). Он предоставляет возможность сохранять состояние пользовательского интерфейса активности в объект Bundle, который потом будет передаваться в методы onCreate() и onRestoreInstanceState(). В объект Bundle можно записать параметры, динамическое состояние активности как пары имя-значение. Когда активность будет снова вызвана, объект Bundle передается системой в качестве параметра в метод onCreate() и в метод onRestoreInstanceState(), которыЙ вызывается после onStart(), чтобы один из них или они оба могли установить активность в предыдущее состояние. Прежде чем передавать изменённый параметр Bundle в обработчик родительского класса, сохраните значения с помощью методов getXXX() и putXXX().

Используйте обработчик onSaveInstanceState() для сохранения состояния интерфейса (например, состояния флажков, текущего выделенного элемента или введенных, но не сохраненных данных), чтобы объект Activity при следующем входе в активное состояние мог вывести на экран тот же UI. Рассчитывайте, что перед завершением работы процесса во время активного состояния будут вызваны обработчики onSaveInstanceState и onPause.

В отличие от базовых методов, методы onSaveInstanceState() и onRestoreInstanceState() не относятся к методам жизненного цикла активности. Система будет вызывать их не во всех случаях. Например, Android вызывает onSaveinstancestate() прежде, чем активность становится уязвимой к уничтожению системой, но не вызывает его, когда экземпляр активности разрушается пользовательским действием (при нажатии клавиши BACK). В этом случае нет никаких причин для сохранения состояния активности.

Метод onSaveInstanceState() вызывается системой в случае изменения конфигурации устройства в процессе выполнения приложения (например, при вращении устройства пользователем или выдвижении физической клавиатуры устройства.

Поскольку метод onSaveinstanceState() вызывается не во всех случаях, его необходимо использовать только для сохранения промежуточного состояния активности. Для сохранения данных лучше использовать метод onPause().


Метод onRestoreInstanceState()

У метода onRestoreInstanceState() есть такой же параметр Bundle, как у onCreate(), и вы можете восстанавливать сохранённые значения из метода onSaveInstanceState().

Метод вызывается после метода onStart(). Система вызывает метод onRestoreInstanceState() только в том случае, если имеются сохранённые данные для восстановления. Таким образом вам не нужно проверять Bundle на null, как в методе onCreate().

1 комментарий:

  1. В отладчике поставил точку останова
    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
    if (chbOne.isChecked())
    outState.putString(KEY_CHB_RELAY_ONE, "true");
    else outState.putString(KEY_CHB_RELAY_ONE, "false");
    ....

    Соответственно при выбранном чекбоксе на главной активити и переходе на вторую, отладчик заходит в
    outState.putString(KEY_CHB_RELAY_ONE, "true");
    но не переприсваивает значение ключа на true
    в чем дело не пойму

    ОтветитьУдалить