А пока продолжим практику по сохранению состояния Активности для лучшего понимания этой темы ну и сопутствующих ей.
Приложение AP0003 продолжает эволюционировать
Чтобы не загромождать функционал и интерфейс Активности А, я модифицировал Активность В и ее разметку. Как показано на скрине:
По нажатию на кнопке Count происходит увеличение счетчика (переменной MyCount) на единицу.
Затем это значение пишется в текстовые атрибуты самой кнопки, текстового поля и поля для ввода текста.
Это сделано для того чтобы посмотреть, что из измененных данных будет сохраняться в Активности А при смене ориентации экрана.
Сперва посмотрим, что и как сохраняется в Активности по умолчанию системой, а так же как ведет себя интерфейс при смене ориентации экрана.
Затем модифицируем если что-то отображается не так как нам хотелось бы.
А так же исправим то, что не сохраняется по умолчанию и будем это сохранять и восстанавливать.
Собственно некоторые ошибки проектирования тут уже есть и я их покажу специально, чтобы было лучшее понимание как и что происходит.
А уж потом исправим все это дело и приведем к желаемому состоянию.
Кроме того, в этой теме, мы все же чуть коснемся темы жизненного цикла Активности. В прошлый раз когда мы разбирали эту тему на примере этого же приложения, мы не вращали экран устройства, поэтому упустили один тонкий момент который стоит упомянуть, что мы и сделаем ниже.
Вот так практика и ведет к совершенству.
Рассмотрим код разметки layout_b.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="#faf20d" 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=".ActivityB" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/strB" /> <Button android:id="@+id/buttonStartC" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView1" android:onClick="onClickStartC" android:text="Start Activity C" /> <TextView android:id="@+id/textStateActB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/buttonStartC" android:layout_below="@+id/buttonStartC" android:background="#fffcfc" android:text="Этот экземпляр АсtivityB запущен в первый раз" /> <TextView android:id="@+id/textActCountB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textStateActB" android:layout_below="@+id/textStateActB" android:layout_marginTop="14dp" android:background="#fffcfc" android:text="TextView" /> <EditText android:id="@+id/editTextB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textActCountB" android:layout_below="@+id/textActCountB" android:layout_marginTop="14dp" android:background="#fcf5ab" android:ems="10" /> <Button android:id="@+id/buttonCount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/editTextB" android:layout_centerVertical="true" android:onClick="onClickCount" android:text="Count" /> <TextView android:id="@+id/textViewCount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@+id/buttonCount" android:text="Count=" /> </RelativeLayout>
Мы видим что все элементы разметки имеют уникальные ID и по идее их состояние должно сохраняться и восстанавливаться системой по умолчанию. Но как покажет практика не все будет работать так как ожидается. Хотя в принципе все логично.
В строке 61 происходит вызов обработчика нажатия кнопки Count.
Теперь код ActivityB.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.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class ActivityB extends Activity { final String TAG = "States"; TextView tvTextLife; List<ActivityManager.RunningTaskInfo> list; ActivityManager am; Integer TotalActCount; Boolean FirstStart; Boolean NextAct; TextView MyTextB, MyTextCount; Integer MyCount = 0; Button MyCountButton; EditText MyEditTextB; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_b); Log.d(TAG, "ActivityB: onCreate()"); // флаг что активность запущена впервые FirstStart = true; // кнопка запуска следующей Активности не нажималась NextAct = false; } @Override protected void onStart() { super.onStart(); Log.d(TAG, "ActivityB: onStart()"); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "ActivityB: 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.textActCountB); 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, "ActivityB: onPause()"); // флаг что активность уже была запущена FirstStart = false; // находим текстовое поле по его идентификатору tvTextLife = (TextView) findViewById(R.id.textStateActB); // присваиваем значение атрибуту Text для выбранного TextView tvTextLife.setText("Этот экземпляр ActivityB уже был запущен!"); } @Override protected void onStop() { super.onStop(); Log.d(TAG, "ActivityB: onStop()"); } @Override protected void onRestart() { super.onRestart(); Log.d(TAG, "ActivityB: onRestart()"); } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "ActivityB: onDestroy()"); } public void onClickStartC(View v) { Intent intent = new Intent(ActivityB.this, ActivityC.class); startActivity(intent); // кнопка запуска следующей Активности была нажата NextAct = true; } @Override protected void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); // получаем ссылку на поле ввода текста MyTextB = (EditText) findViewById(R.id.editTextB); Log.d(TAG, "onSaveInstanceState B text: " + MyTextB.getText().toString()); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // получаем ссылку на поле ввода текста MyTextB = (EditText) findViewById(R.id.editTextB); Log.d(TAG, "onRestoreInstanceState B text: " + MyTextB.getText().toString()); } public void onClickCount(View v) { Log.d(TAG, "Click Count Button"); MyCount = MyCount + 1; // находим текстовое поле по его идентификатору MyTextCount = (TextView) findViewById(R.id.textViewCount); // присваиваем значение атрибуту Text для выбранного TextView MyTextCount.setText("Count = " + MyCount); // находим кнопку по ее идентификатору MyCountButton = (Button) findViewById(R.id.buttonCount); // присваиваем значение атрибуту Text для выбранной кнопки MyCountButton.setText("Count " + MyCount); // находим поле ввода текста по его идентификатору MyEditTextB = (EditText) findViewById(R.id.editTextB); // присваиваем значение атрибуту Text для выбранного поля ввода текста MyEditTextB.setText("Count = " + MyCount); } }
Код со 129 строки и до 143 обрабатывает нажатие кнопки Count.
Теперь рассмотрим как работает наше измененное приложение. Запускаем его
И сразу же стартуем Активность В
Посмотрим логи
И так пока все как доктор прописал. Обращаем внимание что в Активности В написано что она запущена впервые, что есть правда.
Теперь нажмем кнопку HOME (ДОМОЙ) и снова вернемся в приложение кликнув по его иконке в списке приложений.
Наша Активность В сообщила нам что она была уже запущена, что тоже есть правда. Посмотрим логи.
Метод onSaveInstanceState() был запущен Активностью В при ее переходе в остановленное состояние. И затем Активность В снова была нами запущена. И так пока все гладко.
Теперь жмакнем по кнопочке Count
Видно что после нажатия кнопки Count изменилось состояние элементов. Так как мы нажали один раз, то счетчик с нуля увеличился на единичку.
Глянем логи
Вот видно что кликнули по кнопке.
Теперь повернем экран и что мы видим?????
Во первых, обращаем внимание что наша Активность В, сообщает нам что она была запущена в первый раз. И она не врет. Это действительно так. Посмотрим логи.
И так видно, что при смене ориентации экрана, Активность уничтожается и создается вновь. Делается это не для того, чтобы добавить разработчикам головной боли, а для того, что при смене ориентации экрана может отображаться другая разметка, предназначенная и оптимизированная именно для этой ориентации экрана.
Все как в матчасти было описано. Перед методом onPause() вызывается метод onSaveInstanceState(), затем Активность останавливается, уничтожается, создается вновь, вызывается метод onRestoreInstanceState() и т.д.
Как мы видим сохранилось только значение которое было в поле для ввода текста. А измененная надпись на кнопке и в поле TextView не были сохранены, хотя у этих элементов и есть ID. Собственно об этом уже тоже говорилось в альма-матер
some of the activity state is restored by the Activity class's default implementation
некоторые данные состояния Активности восстанавливаются классом Активности по умолчанию
Собственно какие это, некоторые данные, видимо придется узнавать на собственном опыте
Зато интересно и весело
Видимо предполагается, что значения надписей на кнопках и в полях TextView меняются редко и их незачем сохранять по умолчанию. Хотя может и не так.
Теперь исправим разметку для горизонтальной ориентации экрана, чтобы дальше все наши опыты были более наглядны и читабельны.
Для этого в дереве папок проекта, в папке res создаем папку layout-land и в ней файл layout_b.xml, в который копируем содержимое файла layout_b.xml из папки layout. И приводим все вот к такому виду
Среда разработки сама понимает, что в данной папке должны находится разметки для горизонтальной ориентации экрана.
Если не все понятно как это сделать, то рекомендую посмотреть этот урок
Layout файл для разных ориентаций экрана
На всякий случай привожу код файла layout_b.xml из папки layout-land:
<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="#faf20d" 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=".ActivityB" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/strB" /> <Button android:id="@+id/buttonStartC" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView1" android:onClick="onClickStartC" android:text="Start Activity C" /> <TextView android:id="@+id/textStateActB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/buttonStartC" android:layout_below="@+id/buttonStartC" android:background="#fffcfc" android:text="Этот экземпляр АсtivityB запущен в первый раз" /> <TextView android:id="@+id/textActCountB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textStateActB" android:layout_below="@+id/textStateActB" android:layout_marginTop="14dp" android:background="#fffcfc" android:text="TextView" /> <EditText android:id="@+id/editTextB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textActCountB" android:layout_below="@+id/textActCountB" android:layout_marginTop="14dp" android:background="#fcf5ab" android:ems="10" /> <Button android:id="@+id/buttonCount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/textStateActB" android:layout_marginLeft="19dp" android:layout_toRightOf="@+id/buttonStartC" android:onClick="onClickCount" android:text="Count" /> <TextView android:id="@+id/textViewCount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/buttonCount" android:layout_alignBottom="@+id/buttonCount" android:layout_marginLeft="18dp" android:layout_toRightOf="@+id/buttonCount" android:text="Count=" /> </RelativeLayout>
И так продолжаем наши эксперименты и эволюцию приложения AP0003
Запустим приложение заново и сделаем все шаги точно так же включая последний, где мы повернули экран. И затем продолжим рассмотрение этого примера.
Теперь при повороте экрана увидим такую картинку
Теперь отображается layout_b.xml для горизонтальной ориентации экрана, как мы и запроектировали, но значения кнопки Count и поля TextView не сохраняются по умолчанию.
Посмотрим логи:
Собственно в логах все как в матчасти. Теперь нам надо самим позаботиться о сохранении значения счетчика и его восстановлении. Сделаем все как описано тут, но сперва, для пущего понимания, еще немного поиграемся с приложением.
Жмакнем на кнопку Count еще пару раз и увидим
Хотя на предыдущем скрине было видно, что Count=1 и мы нажали по кнопке Count ДВА раза, возможно было предположить, что Count будет равен 3, но это не так, поскольку состояние счетчика не сохранилось и он начал отсчет нажатий заново. Именно поэтому мы видим что Count=2.
Теперь логи
Теперь повернем экран
Посмотрим логи
И один раз нажмем на кнопку Count
Значение Count стало равно снова 1, хотя до этого было 2. Собственно это я уже объяснял почему такое происходит и на скрине это объяснено еще раз.
Теперь поправим сложившуюся ситуацию. Смотрим измененный код ActivityB.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.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class ActivityB extends Activity { final String TAG = "States"; TextView tvTextLife; List<ActivityManager.RunningTaskInfo> list; ActivityManager am; Integer TotalActCount; Boolean FirstStart; Boolean NextAct; TextView MyTextB, MyTextCount; Integer MyCount = 0; Button MyCountButton; EditText MyEditTextB; static final String SaveMyCount = "MY_CLICK_COUNT"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_b); // флаг что активность запущена впервые FirstStart = true; // кнопка запуска следующей Активности не нажималась NextAct = false; if (savedInstanceState != null) { // Restore value of members from saved state // восстанавливаем значение счетчика MyCount = savedInstanceState.getInt(SaveMyCount); // находим текстовое поле по его идентификатору MyTextCount = (TextView) findViewById(R.id.textViewCount); // присваиваем значение атрибуту Text для выбранного TextView MyTextCount.setText("Count = " + MyCount); // находим кнопку по ее идентификатору MyCountButton = (Button) findViewById(R.id.buttonCount); // присваиваем значение атрибуту Text для выбранной кнопки MyCountButton.setText("Count " + MyCount); // получаем ссылку на поле ввода текста MyTextB = (EditText) findViewById(R.id.editTextB); Log.d(TAG, "ActivityB: onCreate() NOT NULL: " + MyTextB.getText().toString()); } else { // Probably initialize members with default values for a new // instance Log.d(TAG, "ActivityB: onCreate() NULL"); } Log.d(TAG, "ActivityB: onCreate()"); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "ActivityB: onStart()"); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "ActivityB: 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.textActCountB); 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, "ActivityB: onPause()"); // флаг что активность уже была запущена FirstStart = false; // находим текстовое поле по его идентификатору tvTextLife = (TextView) findViewById(R.id.textStateActB); // присваиваем значение атрибуту Text для выбранного TextView tvTextLife.setText("Этот экземпляр ActivityB уже был запущен!"); } @Override protected void onStop() { super.onStop(); Log.d(TAG, "ActivityB: onStop()"); } @Override protected void onRestart() { super.onRestart(); Log.d(TAG, "ActivityB: onRestart()"); } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "ActivityB: onDestroy()"); } public void onClickStartC(View v) { Intent intent = new Intent(ActivityB.this, ActivityC.class); startActivity(intent); // кнопка запуска следующей Активности была нажата NextAct = true; } @Override protected void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); // сохраняем значение счетчика savedInstanceState.putInt(SaveMyCount, MyCount); // получаем ссылку на поле ввода текста MyTextB = (EditText) findViewById(R.id.editTextB); Log.d(TAG, "onSaveInstanceState B text: " + MyTextB.getText().toString()); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // получаем ссылку на поле ввода текста MyTextB = (EditText) findViewById(R.id.editTextB); Log.d(TAG, "onRestoreInstanceState B text: " + MyTextB.getText().toString()); } public void onClickCount(View v) { Log.d(TAG, "Click Count Button"); MyCount = MyCount + 1; // находим текстовое поле по его идентификатору MyTextCount = (TextView) findViewById(R.id.textViewCount); // присваиваем значение атрибуту Text для выбранного TextView MyTextCount.setText("Count = " + MyCount); // находим кнопку по ее идентификатору MyCountButton = (Button) findViewById(R.id.buttonCount); // присваиваем значение атрибуту Text для выбранной кнопки MyCountButton.setText("Count " + MyCount); // находим поле ввода текста по его идентификатору MyEditTextB = (EditText) findViewById(R.id.editTextB); // присваиваем значение атрибуту Text для выбранного поля ввода текста MyEditTextB.setText("Count = " + MyCount); } }
В строке 29 мы определили переменную для сохранения состояния счетчика в наборе Bundle. В строке 137 мы сохраняем в нее значение счетчика MyCount. В строках 39-59 восстанавливаем значение счетчика (строка 42) и значение атрибута Text на кнопе и в поле TextView. Ну и так же добавлены таги для отображения в логах необходимой для понимания и отладки информации.
Теперь запустим приложение и посмотрим как оно теперь работает. Запускаем, и с Активности А сразу запускаем Активность В, в которой клацаем по кнопке Count ТРИ раза.
Смотрим логи.
Теперь повернем экран
Как видим все в порядке, то есть все данные сохранились и восстановились. Посмотрим логи:
И так после наших трех кликов по кнопке Count мы повернули экран. Перед уничтожением Актвиности В ее состояние было сохранено методом onSaveInstanceState(), в котором мы так же сохранили и значение счетчика MyCount в объекте Bundle.
Далее уже начинается чуть интереснее. Хотя при создании мы видим, что в методе onCreate() объект Bundle не равен null, все же получить доступ к значению поля EditText не получается, потому там написано только NOT NULL, хотя код в строках 53 и 54 пытается вывести значение этого поля в логах … но там пусто. Значение в поле восстанавливается только в методе onRestoreInstanceState(), где уже можно прочитать значение атрибута Text в поле EditText. Строки кода 52-54 и 148-150 аналогичны в обоих методах onCreate() и onRestoreInstanceState(), но в первом мы не видим вывода значения атрибута TEXT в логах, а во втором он уже есть.
Теперь еще пару раз жмакнем по кнопке Count
И добавим в текстовое поле свой текст, чтобы все выглядело вот так:
И повернем экран опять
Посмотрим логи:
И так, добавленный нами текст сохранился, потому как сохранение и восстановление значения поля ввода текста мы доверили системе и сами его ни как не контролируем. И система нам честно сохраняет и восстанавливает его значение.
Значение в поле ввода текста меняет только наш метод onClickCount(). Конечно данное поведение тоже можно поменять но мы не будем. Так как это только способствует пониманию данной темы.
Теперь еще пару раз кликнем по кнопке Count
Как видим со счетчиком все в порядке, а так же текст в поле ввода текста был изменен нашим методом onClickCount().
На этом, пожалуй, тему сохранения состояния Активности мы закончим и вернемся к тебе задач и обратного стека.
Комментариев нет:
Отправить комментарий