20 марта 2014 г.

Задачи и обратный стек (Tasks and back stack). Часть 3

И так продолжаем перевод этой статьи из альма-матер. Остановились мы здесь. В свою очередь это ссылается на другую статью. А мой перевод начала этой статьи здесь. Все в лучших традициях Java.

Сегодняшняя тема Saving activity state (сохранение состояния Активности).

Как уже обсуждалось выше, по умолчанию, система сохраняет состояние активности когда она остановлена. Таким образом, когда пользователь возвращается обратно, к предыдущей Активности, ее интерфейс отображается таким, как был оставлен пользователем. Однако вы можете, и вы должны, сохранять состояние вашей Активности на случай если она будет уничтожена, после чего естественно будет создана заново.

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

И дальше эта статья отсылает нас сюда. И самое интересная фразочка в этом самом сюда следующая:

Нет ни какой гарантии что onSaveInstanceState() будет вызвана перед тем как ваша Активность будет уничтожена, поскольку есть случаи в которых сохранение состояния не является необходимым (например, когда пользователь покидает вашу активность используя кнопку ОБРАТНО, то это означает, что пользователь сам прекратил работу с Активностью). Если система вызывает onSaveInstanceState(), то делает это до onStop() и возможно перед onPause().

Вот так вот! Улыбка

Рассмотрим это на примере. Я в очередной раз модифицировал свое мега приложение AP0003. Добавив в Разметки А и В поля ввода текста, а в Активности А и В метод onSaveInstanceState().

Текст 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="17dp"
        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="17dp"
        android:background="#fcf5ab"
        android:ems="10" >

        <requestFocus />
    </EditText>

</RelativeLayout>

А теперь исходинк 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.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;

 @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: oPause()");
  // флаг что активность уже была запущена
  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) {
  // получаем ссылку на поле ввода текста
  MyTextB = (EditText) findViewById(R.id.editTextB);
  Log.d(TAG, "onSaveInstanceState B text: " + MyTextB.getText().toString());
 }

}

Строки 110 и 111 выводят текст набранный в поле ввода в логах. Это сделано для наглядности работы метода onSaveInstanceState().

Теперь запустим прогу и поиграемся с ней чтобы понять как и когда срабатывает метод onSaveInstanceState().

TS0001

И логи

TS0002

Здесь все как обычно. Далее вводим текст в поле

TS0003

Затем нажимаем кнопку Home и смотрим логи

TS0004

Как видим метод onSaveInstanceState() был вызван перед методом onPause().

Теперь вернемся к приложению кликнув по его иконке в списке приложений

TS0005

Как видим введенный нами текст сохранился. Что собственно и описывалось выше в этой статье. Система, по умолчанию, сама сохраняет состояние Активностей в остановленном состоянии.

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

TS0006

Активность просто перешла из остановленного состояние в восстановленное и система сама восстановила состояние Активности.

Теперь нажмем кнопку Start Activity B

TS0007


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

TS0008

Видно, что перед тем как перейти в приостановленное состояние Активность А вызвала метод onSaveInstanceState().

Теперь введем текст в Активности В

TS0009

Нажмем кнопку Start Activity C и посмотрим логи

TS0010

Видно, что Активность В, так же вызвала метод onSaveInstanceState() перед методом onPause().

Теперь мы находясь в Активности C нажмем кнопку обратно, мы увидим так же что текст введённый нами в Активности В был восстановлен системой

TS0011

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

TS0012

Активность В просто перешла из остановленного состояния в восстановленное.

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

TS0005

Но если мы сейчас снова нажмем кнопку чтобы запустить Активность В, то там уже введенного нами текста не будет, поскольку после нажатия кнопку ОБРАТНО, тот экземпляр Активности В был уничтожен методом onDestroy().

TS0013

Теперь опять вернемся к переводу текста из альма-матер.

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

Однако, если система уничтожает Активность для высвобождения памяти, то экземпляр Активности уничтожается и поэтому система просто не может восстановить ее состояние каким оно было до этого. Вместо этого система создает новый экземпляр Активности. Пользователь же, не знает, что Активность была уничтожена и создана заново и может ожидать ее увидеть такой же, какой он ее оставил. В такой ситуации вы должны позаботиться чтобы важная информация из Активности была сохранена.

13 марта 2014 г.

Задачи и обратный стек (Tasks and back stack). Часть 2

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

В написании кода помогли следующие статьи (кои стоит прочитать):

http://startandroid.ru/ru/uroki/vse-uroki-spiskom/62-urok-25-task-chto-eto-takoe-i-kak-formiruetsja.html

http://startandroid.ru/ru/uroki/vse-uroki-spiskom/190-urok-116-povedenie-activity-v-task-intent-flagi-launchmode-affinity

http://stackoverflow.com/questions/9839592/getting-a-list-of-running-processes-and-killing-a-specific-process

http://stackoverflow.com/questions/7992563/how-to-find-back-stack-activities-in-an-android-application

и статейка о работе оператора if в java

http://developer.alexanderklimov.ru/android/java/if.php

Приложение все тоже. В нем есть три Активности – ActivityA, ActivityB и ActivityC. На каждой Активности есть кнопка вызывающая следующую активность. A->B->C->A и так далее. Сейчас добавил счетчик количества активностей в задаче а так же вывод информации о том была ли уже запущена Активность или нет.

Итак сперва запустим приложение, чтобы было понятно о чем речь, потом рассмотрим код.
Запустили и видим что Активность нам сообщает, (1) что этот экземпляр Активности запущен в первый раз. А так же видно количество Активностей в текущей задаче (2). У нас пока одна Активность, так как кнопку Start Activity B мы не нажимали.

T0001

Теперь посмотрим логи

T0002
Здесь все стандартно. Все как на классических диаграммах.

Далее жмем кнопку Home (3). Наше приложение уходит в фоновый режим. Смотрим логи

T0003

Приложение перешло в остановленное состояние.

Теперь заново откроем его тапнув по его иконке в списке приложений

T0004

Теперь Активность сообщает что она была уже запущена и просто восстановлена из памяти.

T0005

Из логов видно, что события onCreate для Активности не было, и она не создавалась заново, а была просто возобновлена ее работа методом onRestart.

Теперь нажмем кнопку Start Activity B

T0006

Мы видим что Активность В была запущена впервые и что в нашей задаче уже две активности А и В.

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

T0007

Мы видим что после нажатия на кнопку Start Activity B, сперва Активность А перешла в приостановленное состояние onPause и только потом создалась и отобразилась Активность В. И уже после этого Активность А перешла в остановленное состояние onStop. Это один из важных моментов для понимания.

Теперь нажмем кнопку Обратно (Back), чтобы вернуться к Активности А и посмотрим на нее и логи

T0008

Сейчас Активность А сообщает, что она уже была запущена прежде, а Активностей в нашей задаче снова одна, так как Активность В была уничтожена при нажатии в ней кнопки обратно, что подтверждают и логи

T0009

Теперь снова запустим Activity B, а из нее Activity C

T0010

Комментировать особо не буду, тут и так все понятно по аналогии с предыдущим. Просто приведу еще логи выполнения.

T0011

Сейчас у нас Активности А и В находятся в остановленном состоянии, но они существуют. Количество Активностей в нашей задаче равно 3 – А, В и С.

Теперь запустим из Активности С Активность А. Это будет НОВЫЙ экземпляр Актиновости А, другая Активность А, с которой мы начинали сейчас находится на дне стека.

T0012

Собственно скрин это и показывает. Теперь в нашей задаче 4 активности А, В, С и снова А. Теперь если нажать кнопку HOME. И снова вернуться к приложению, то мы увидим, что новая Активность А уже была запущена.

T0013

И логи

T0014

Видно что Активность А была приостановлена, затем остановлена и затем возобновила свое состояние.

Теперь если мы начнем последовательно нажимать кнопку ОБРАТНО, то очистим стек
Активностей и выйдем из приложения.

T0015

По существу этот материал как раз продемонстрировал на практике, то что было изложено в этой моей статье.

Теперь перейдем к коду этого приложения.

T0016

В приложении три Активности и три файла разметки. Я приведу код ActivityA.java и layout_a.xml, а так же код AndroidManifest.xml. Файлы остальных Активностей и разметок аналогичны.

И так 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.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class ActivityA extends Activity {

 final String TAG = "States";
 TextView tvTextLife;
 List<ActivityManager.RunningTaskInfo> list;
 ActivityManager am;
 Integer TotalActCount;
 Boolean FirstStart;
 Boolean NextAct;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.layout_a);
  Log.d(TAG, "ActivityA: onCreate()");
  // флаг что активность запущена впервые
  FirstStart = true;
  // кнопка запуска следующей Активности не нажималась
  NextAct = false;
 }

 @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;

 }

}

Обратите внимание на выделенные строки. Это я задал флаги состояния и их проверку, чтобы правильно скорректировать значение счетчика Активностей в задаче. Если этого не сделать, то поскольку, например, при нажатии кнопки ОБРАТНО восстанавливается предыдущая Активность, а та с которой был переход еще не уничтожена, то значение счетчика будет показывать на одну активность больше чем есть на самом деле. Тоже самое и с кнопкой Домой.

Теперь код 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" />

</RelativeLayout>

Выделенная строка 24 показывает вызов метода из ActivityA по нажатию кнопки.

И теперь код AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.ap0003"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.GET_TASKS"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.ap0003.ActivityA"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.ap0003.ActivityB"
            android:label="@string/app_name" >
        </activity>
        <activity
            android:name="com.example.ap0003.ActivityC"
            android:label="@string/app_name" >
        </activity>
    </application>

</manifest>

Строка 10 добавляет разрешение для получения информации о задачах приложением. Если эту сроку не добавить, то приложение будет завершаться с ошибкой, так как у него не будет полномочий на это.

На этом пока закончим эту часть.

12 марта 2014 г.

Задачи и обратный стек (Tasks and back stack). Часть 1

Это следующий, более глубокий этап понимания жизненных циклов Активностей.

На эту тему уже был перевод статьи с альма-матер

http://habrahabr.ru/post/186434/

но он очень вольный (хотя у меня такие же) и кроме того там есть парочка серьезных ошибок, но при обращении к оригинальной статье все становится на свои места. Так или иначе перевод полезный и его стоит почитать для общего понимания темы. Я тут буду излагать свой. Местами они будут повторяться, но я надеюсь что сделаю более полный перевод. Кроме того я приведу приложения демонстрирующие все эти концепции, так чтобы можно было на практике увидеть, что к чему.

Кроме того в создании этой заметки помог так же материал вот от сюда

http://startandroid.ru/ru/uroki/vse-uroki-spiskom/190-urok-116-povedenie-activity-v-task-intent-flagi-launchmode-affinity

http://startandroid.ru/ru/uroki/vse-uroki-spiskom/62-urok-25-task-chto-eto-takoe-i-kak-formiruetsja.html

http://habrahabr.ru/post/201214/

http://habrahabr.ru/post/201886/

И так! Поехали!

Приложение обычно состоит из нескольких Активностей. Каждая Активность выполняет обычно одну определенную задачу, для выполнения другой задачи пользователь может переключится на другую Активность. Например, приложение электронной почты может иметь одну активность для отображения списка всех писем, а другую для отображения текста конкретного письма.

Активность одного приложения может запустить Активность из другого приложения. Например, ваше приложение хочет отправить сообщение по электронной почте, вы можете это сделать с помощью намерения (intent) с действием SEND, в которое как данные включаете адрес назначения и текст письма, а Активность из другого приложения которая заявила что может поддерживать подобные намерения intent с действием SEND, может принять ваши данные и открыть свою Активность для отправки сообщения. Когда сообщение будет отослано, ваша Активность снова появится на экране и будет казаться, как будто Активность отсылки электронной почты является частью вашего приложения. Даже если Активности принадлежат разным приложениям, Андроид поддерживает видимость для пользователя как будто это одно приложение, храня обе Активности в одной задаче.

Задача – это набор Активностей находящихся в одном стеке, которые пользователь запускает для выполнения какой либо своей задачи. Активности находятся в стеке (обратном стеке) в том порядке в каком их вызывал пользователь.

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

Когда текущая Активность запускает другую, новая Активность помещается на вершину стека, а предыдущая занимает место под ней и переходит в остановленное состояние. Когда активность останавливается, система сохраняет состояние ее интерфейса. Когда пользователь нажимает кнопку ОБРАТНО, текущая Активность изымается из вершины стека и уничтожается методом onDestroy, и фокус получает Активность, которая находилась под ней. Она помещается на вершину стека и ее состояние восстанавливается методом onResume. Активности в стеке ни когда не реорганизуются, то есть не меняют своего положения в стеке. Они только помещаются на вершину стека, когда пользователь запускает Активность или изымаются с вершины стека, когда пользователь нажимает кнопку ОБРАТНО на текущей Активности. Стек организован по принципу «last in, first out» — т.е. последний вошел, первый вышел. На диаграмме ниже это хорошо показано.

diagram_backstack

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

Кроме того, как раз это поведение детально и было рассмотрено в моей предыдущей статье.

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

Задача продолжает существовать даже когда находится в фоновом режиме. Задача переходит в фоновый режим, когда пользователь запускает новую задачу или когда переходит на домашний экран нажав кнопку HOME. Все Активности в фоновой задаче находятся в остановленном состоянии, обратный стек задачи сохраняет свое состояние, задача просто потеряла фокус, поскольку фокус получила другая задача. Это показано на диаграмме ниже:

diagram_multitasking

Тут две задачи. Задача B взаимодействует с пользователем и находится в фокусе (на переднем плане). В то время как задача A находится в фоне ожидая возобновления работы. Каждая задача имеет свой обратный стек Активностей. Пользователь может переключаться между задачами. Тогда одна из них теряет фокус и переходит на задний план, а другая получает фокус и переходит на передний план. Это пример многозадачности Андроид.

 

Но следует иметь в виду, что хотя в фоновом режиме и может находится множество задач одновременно, система, для высвобождения ресурсов, может убить эти задачи. Это приведет к тому, что Активности потеряют свое состояние, в котором их оставил пользователь. Чтобы избежать этого, следует сохранять состояние Активностей.

diagram_multiple_instancesПоскольку положение Активностей в обратном стеке ни когда не реорганизуется, то, если ваше приложение позволяет запускать какую либо Активность несколько раз, то будет создаваться новый экземпляр этой Активности и помещаться на вершину стека. То есть предыдущий экземпляр этой же самой Активности не будет помещаться на вершину стека, а будет создаваться новый. Таким образом экземпляры одной и той же Активности могут быть помещены в стек несколько раз. Причем экземпляры одной активности могут находится в стеках разных задач. Однако такое поведение Активности по умолчанию можно изменить. Например вы можете сделать так, чтобы Активность могла существовать только в единственном экземпляре. Как это сделать можете прочитать в управлении задачами.

Итак, подведем итог поведения Активности по умолчанию:

  • Когда Активность А запускает Активность B, Активность А останавливается, но система сохраняет ее состояние (например,положение прокрутки, текст введенный в поля формы и т.д.). Если пользователь нажимает кнопку обратно находясь в Активности B, Активность А восстанавливает свое состояние.
  • Когда пользователь покидает задачу нажав кнопку Home, текущая Активность останавливается и задача переходит в фоновый режим. Система сохраняет состояние каждой Активности в задаче. Если пользователь позже возвращается к задаче, то задача переходит на передний план и восстанавливает Активность находящуюся на вершине стека (отображает ее).
  • Если пользователь нажимает кнопку ОБРАТНО, то Активность находящаяся на вершине стека изымается из стека и уничтожается, а Активность находящаяся под ней в стеке получает фокус и восстанавливается из сохраненного состояния. После уничтожения Активности, система не сохраняет ее состояние.
  • Активность может иметь множество запущенных экземпляров. Причем в разных задачах.

Пример поведения Активностей описанных в этой части, можно посмотреть в моей предыдущей статье.

На этом пока теорию закончим. В следующей статье будет еще немножко практики на эту же тему, а потом двинемся дальше по теме задач и обратных стеков.

Жизненный цикл Activity в Android (часть 4)

Чтобы еще лучше понять жизненный цикл Активности, я набросал программку, которая состоит из трех активностей: A, B и C. Активность A вызывает Активность B, Активность B вызывает C, а C вызывает снова A. Кратно можно показать так A->B->C->A.

Рассматривая этот пример мы уже в плотную подходим к теме задач и стеков активностей или если правильней назвать обратных стеков (Tasks and Back Stack).

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

Кроме того для большей наглядности и анализа использованы возможности отладки приложений Eclipse и Android SDK.

В написании проги помогли и эти статьи
http://startandroid.ru/ru/uroki/vse-uroki-spiskom/19-urok-12-logi-i-vsplyvajuschie-soobschenija.html
http://developer.alexanderklimov.ru/android/activity.php

А изначальную идею к написанию дала эта статья
http://habrahabr.ru/post/201214/

но там нет представленного кода. Хотя он конечно простой, но когда только начинаешь все изучать, все не просто.

Приложение назвал AP0003. Очень говорящее название. Раскрасил его тоже дико, как светофор. A – красный, B – желтый и C – зеленый. На зато сразу видно какая активность на экране. На каждой Активности есть кнопка, которая вызывает следующую Активность.
Вот так выглядит приложение в работающем виде:

AL0001

AL0002

AL0003

Теперь приведу код активностей и разметок

Код ActivityA.java

package com.example.ap0003;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.util.Log;

public class ActivityA extends Activity {

 final String TAG = "States";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.layout_a);
  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()");
 }

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

 @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);

 }

}

Две другие Активности подобны этой только есть изменения в соотвтествующих строках выведения сообщений отладки.

В строке 11 задается значение ярлыка отладки, чтобы сообщения с ним было легче находить в журналах отладки. В строке 17 выводится этот ярлык а так же дополнительное сообщение отладки.

Код 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" />

</RelativeLayout>

В строке 24 определен метод вызова нажатия кнопки запускающей следующую Активность.

Теперь об отладке. Просто приведу скрин и небольшое объяснение.

AL0004

Теперь запускаем приложение и смотрим что показывает отладка по фильтру State.

AL0005

Теперь сделаем интересную и важную вещь. В окне AVD нажмем комбинацию Ctrl+F11 – смена ориентации экрана.

AL0006

и что же мы видим в логах

AL0007



Вот что надо знать и учитывать, что при смене ориентации экрана текущая активность уничтожается и запускается заново. Это раз.

вернем ориентацию обратно и запустим активность B

AL0008

Как видим Активность А перешла в остановленное состояние но не уничтожена, то есть остается в памяти, а верней в стеке активностей.

Теперь запустим Активность C

AL0009

Теперь и Активность B у нас остановлена, но не уничтожена и находится в стеке, на вершине которого сейчас Активность C.

Теперь нажмем кнопку на Активности С и запустим Активность А

AL0010

Активность С была остановлена, и была запущена НОВАЯ Активность А. То есть запущен не старый экземпляр окна который мы видели в начале, а это совершенно новый экземпляр Активности А, так как вызван был метод onCreate, а не onRestart. Из этого вывод, что в стандартном варианте запуска одна Активность может быть запущена несколько раз и находится в стеке сколько угодно раз (но это поведение можно поменять).

Теперь нажмем кнопку обратно (возврат)

AL0011

Сейчас мы видим что Активность С была вызвана заново, то есть не создана заново, а восстановлена из состояния Stop методом onRestart(). А Активность А была уничтожена, но уничтожена самая последняя Активность А, которую мы запускали. Если нажать последовательно еще два раза кнопку back (возврат).

То тогда сперва будет восстановлена и показана Активность B, затем она будет уничтожена и будет показана (восстановлена) наша первая Активность А методом onRestart.

AL0012

И так этот пример уже совсем в плотную пододвинул нас к следующей важной теме Tasks and Back Stack.

Что еще хотелось бы сказать, в данной теме, так то, что если сейчас принудительно завершить приложение AP003, нажав кнопку Home и перейдя в Настройки-> Приложения –> Управление приложениями, затем выбрать приложение AP003 и нажать кнопочку Принудительная остановка (Force Stop)

AL0013

То приложение будет принудительно остановлено, но метод onDestroy вызван не будет! Это надо учитывать при разработке приложений. Так же метод onDestroy не вызывается, если приложение уходит в фон и затем завершается системой для высвобождения необходимых ресурсов.

AL0014

Так же по этой теме можно почитать еще тут:

http://startandroid.ru/ru/uroki/vse-uroki-spiskom/58-urok-21-sozdanie-i-vyzov-activity.html

http://startandroid.ru/ru/uroki/vse-uroki-spiskom/60-urok-23-activity-lifecycle-v-kakih-sostojanijah-mozhet-byt-activity.html

http://startandroid.ru/ru/uroki/vse-uroki-spiskom/61-urok-24-activity-lifecycle-primer-smeny-sostojanij-s-dvumja-activity.html

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

На этом пока закончим с жизненными циклами Активностей.

6 марта 2014 г.

Жизненный цикл Activity в Android (часть 3)

Опять же по следам этой статьи (не всей):
http://developer.alexanderklimov.ru/android/theory/activity_methods.php
Автор забыл в начале статьи приложить блок схему, что мы исправим для пущего понимания
Act0011
При переходе активности от одного состояния к другому, она получает уведомления через защищенные методы:
  • protected void onCreate();
  • protected void onStart();
  • protected void onRestart();
  • protected void onResume();
  • protected void onPause();
  • protected void onStop();
  • protected void onDestroy()
Семь перечисленных методов определяют весь жизненный цикл активности. Есть три вложенных цикла, которые вы можете отслеживать в классе активности:
  • полное время жизни (Full Lifetime) — время с момента первого вызова метода onCreate() до вызова onDestroy(). Активность делает всю начальную установку своего глобального состояния в методе onCreate() и освобождает все остающиеся ресурсы в onDestroy(). Например, если активность порождает дополнительный поток, выполняющийся в фоновом режиме, можно создать этот поток в методе onCreate() и затем остановить поток в методе onDestroy();
  • видимое время жизни (Visible Lifetime) — время между вызовом метода onStart() и вызовом onStop(). В это время пользователь может видеть окно активности на экране, хотя окно может не быть на переднем плане и может не взаимодействовать с пользователем. Между этими двумя методами вы можете поддерживать в коде ресурсы, которые необходимы, чтобы отображать активность пользователю;
  • активное время жизни (Active Lifetime) — время между вызовами onResume() и onPause(). В это время окно активности находится на переднем плане и взаимодействует с пользователем. Активность в процессе работы приложения может часто переходить между состояниями active и paused, поэтому код в этих двух методах должен быть или небольшим по объему (чтобы не замедлять работу приложения во время выполнения), или порождать дополнительные потоки, если требуется выполнение задач, занимающих длительное время.Можно написать код с заглушками для методов внутри Активности, которые обрабатывают изменения состояний. Комментарии к каждой такой заглушке описывают действия, которые нужно учитывать при обработке этих событий.
import android.app.Activity;
import android.os.Bundle;

public class MyActivity extends Activity {
    // Вызывается при входе в "полноценное" состояние.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Инициализируйте Активность.
    }
    
    // Вызывается, когда метод onCreate завершил свою работу, 
    // и используется для восстановления состояния пользовательского 
    // интерфейса
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        // Восстановите состояние UI из переменной savedInstanceState.
        // Этот объект типа Bundle также был передан в метод onCreate.
    }
    
    // Вызывается перед тем, как Активность становится "видимой".
    @Override
    public void onRestart(){
        super.onRestart();
        // Загрузите изменения, учитывая то, что Активность
        // уже стала "видимой" в рамках данного процесса.
    }
    
    // Вызывается в начале "видимого" состояния.
    @Override
    public void onStart(){
        super.onStart();
        // Примените к UI все необходимые изменения, так как
        // Активность теперь видна на экране.
    }
    
    // Вызывается в начале "активного" состояния.
    @Override
    public void onResume(){
        super.onResume();
        // Возобновите все приостановленные обновления UI,
        // потоки или процессы, которые были "заморожены",
        // когда данный объект был неактивным.
    }
    
    // Вызывается для того, чтобы сохранить пользовательский интерфейс
    // перед выходом из "активного" состояния.
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        // Сохраните состояние UI в переменную savedInstanceState.
        // Она будет передана в метод onCreate при закрытии и
        // повторном запуске процесса.
        super.onSaveInstanceState(savedInstanceState);
    }
    
    // Вызывается перед выходом из "активного" состояния
    @Override
    public void onPause(){
        // "Замораживает" пользовательский интерфейс, потоки 
        // или трудоемкие процессы, которые могут не обновляться, 
        // пока Активность не находится на переднем плане.
        super.onPause();
    }
    
    // Вызывается перед тем, как Активность перестает быть "видимой".
    @Override
    public void onStop(){
        // "Замораживает" пользовательский интерфейс, потоки 
        // или операции, которые могут подождать, пока Активность
        // не отображается на экране. Сохраняйте все введенные
        // данные и изменения в UI так, как будто после вызова
        // этого метода процесс должен быть закрыт.
        super.onStop();
    }
    
    // Вызывается перед выходом из "полноценного" состояния.
    @Override
    public void onDestroy(){
        // Очистите все ресурсы. Это касается завершения работы
        // потоков, закрытия соединений с базой данных и т. д.
        super.onDestroy();
    }
}

Как видно из кода, переопределяя эти обработчики, вы всегда должны вызывать одноименные методы родительского класса.

Из перечисленных методов в вашем классе обязательно должен быть метод onCreate(), которая задает начальную установку параметров при инициализации активности. Вторым по популярности является метод onPause(), используемый для сохранения пользовательских настроек активности и подготовиться к прекращению взаимодействия с пользователем.

При реализации любого из этих методов необходимо всегда сначала вызывать версию этого метода из суперкласса. Например:
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
        ...
}

protected void onPause()
{
    super.onPause();
    ...
}

Жизненный цикл Activity в Android (часть 2)

По существу вся эта часть будет построена на статье

http://developer.alexanderklimov.ru/android/theory/lifecycle.php

Так как статьи в инете имеют свойство исчезать, то большую часть ее я воспроизведу здесь, поскольку гуглу исчезнуть труднее, чем другим ресурсам в сети.
Из прошлой статьи мы уже знаем основные методы жизненного цикла Активности, но перечислим их еще раз (повторенье мать ученья):
  • protected void onCreate();
  • protected void onStart();
  • protected void onRestart();
  • protected void onResume();
  • protected void onPause();
  • protected void onStop();
  • protected void onDestroy();
Рассмотрим жизненный цикл Активности более подробно с помощью этой диаграммы и описаний ниже:

activity_lifecycle

onCreate()


Вызывается при создании активности. Система может запускать и останавливать текущие окна в зависимости от происходящих событий. Android вызывает метод onCreate() после запуска или перезапуска Activity. Внутри этого метода настраивают статический интерфейс активности, инициализируют статические данные активности, связывают данные со списками, связывают с необходимыми данными и ресурсами и т.д. Задают внешний вид через метод setContentView().
В этом методе загружайте пользовательский интерфейс, размещайте ссылки на свойства класса, связывайте данные с элементами управления, создавайте Сервисы и потоки. Метод onCreate() принимает объект Bundle, содержащий состояние пользовательского интерфейса, сохраненное в последнем вызове обработчика onSaveInstanceState. Для восстановления графического интерфейса в его предыдущем состоянии нужно задействовать эту переменную: внутри onCreate() или переопределяя метод onRestoreInstanceState().
Операции по инициализации, занимающие много времени, следует выполнять в фоновом процессе, а не с помощью метода onCreate(). В противном случае можно получить диалоговое окно ANR (Application Not Responding, приложение не отвечает).
В методе можно сделать проверку, запущено ли приложение впервые или восстановлено из памяти. Если значение переменной savedInstanceState будет null, приложение запускается первый раз:
// Приложение запущено впервые или восстановлено из памяти?
if ( savedInstanceState == null )   // приложение запущено впервые
{
   currentBillTotal = 0.0;    // инициализация суммы счета нулем
   // другой код
} 
else // приложение восстановлено из памяти
{
     // инициализация суммы счета сохраненной в памяти суммой
     currentBillTotal = savedInstanceState.getDouble(BILL_TOTAL);
}

А значение переменной currentBillTotal можно сохранить в методе onSaveInstanceState():
@Override
protected void onSaveInstanceState(Bundle outState) {
 super.onSaveInstanceState(outState);

 outState.putDouble(BILL_TOTAL, currentBillTotal);
} // end method onSaveInstanceState

onStart()


За onCreate() всегда следует вызов onStart(), но перед onStart() не обязательно должен идти onCreate(), так как onStart() может вызываться и для возобновления работы приостановленного приложения (приложение останавливается методом onStop()). При вызове onStart() окно еще не видно пользователю, но вскоре будет видно. Вызывается непосредственно перед тем, как активность становится видимой пользователю. Сопровождается вызовом метода onResume(), если активность получает передний план, или вызовом метода onStop(), если становится скрытой.

onResume()


Метод onResume() вызывается после onStart(), даже когда окно работает в приоритетном режиме и пользователь может его наблюдать. В этот момент пользователь взаимодеиствует с созданным вами окном. Приложение получает монопольные ресурсы. Запускает воспроизведение анимации, аудио и видео. Также может вызываться после onPause().

Имейте в виду, что система вызывает этот метод каждый раз, когда ваша активность идёт на переднем плане, в том числе, при первом создании. Таким образом, вы должны реализовать onResume() для инициализации компонентов, которые вы освободили в OnPause() и выполнять любые другие инициализации, которые должны происходить, когда активность вновь активна.

Пытайтесь размещать относительно быстрый и легковесный код, чтобы ваше приложение оставалось отзывчивым при скрытии с экрана или выходе на передний план.

Метод onResume может быть довольно легковесным. Вам не нужно перезагружать состояние пользовательского интерфейса внутри него, так как эти функции возложены на обработчики onCreate и onRestoreInstanceState.

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

Например, после метода onPause(), в котором мы приостановили работу камеры (см. ниже) снова запускаем камеру:
@Override
public void onResume() {
    super.onResume();

    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
        initializeCamera(); // Local method to handle camera init
    }
}

onPause()


Когда пользователь решает перейти к работе с новым окном, система вызовет для прерываемого окна метод onPause(). По сути происходит свертывание активности. Сохраняет незафиксированные данные. Деактивирует и выпускает монопольные ресурсы. Останавливает воспроизведение видео, аудио и анимацию. От onPause() можно перейти к вызову либо onResume(), либо onStop().

В этом методе необходимо остановить анимацию и другие действия, которые загружают процессор. Зафиксировать не сохранённые данные, например, черновик письма. Освободить системные ресурсы, например, обработку данных от GPS.

Пытайтесь размещать относительно быстрый и легковесный код, чтобы ваше приложение оставалось отзывчивым при скрытии с экрана или выходе на передний план.

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

Например при работе с камерой это можно сделать так:
@Override
public void onPause() {
    super.onPause();

    // Release the Camera because we don't need it when paused
    // and other activities might need to use it.
    if (mCamera != null) {
        mCamera.release()
        mCamera = null;
    }
}

В тоже время вы не должны использовать OnPause() для хранения пользовательских изменений (таких, как персональные данные, введенные в форму) для постоянного хранения. Искключение допускается, когда вы уверены, что пользователи ожидают изменения, которые будут автоматически сохранены (например, при составлении электронной почты). Тем не менее, вы должны избегать выполнения интенсивной работы в OnPause(), таких как запись в базе данных, так как это может замедлить переход к следующей активности (вместо него вы должны выполнять тяжелую нагрузку во время операции отключения OnStop()).

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


onStop()


Метод onStop() вызывается, когда окно становится невидимым для пользователя. Это может произойти при ее уничтожении, или если была запущена другая активность (существующая или новая), перекрывшая окно текущей активности. Следующим состоянием является вызов метода onRestart(), если активность возвращается, чтобы взаимодействовать с пользователем, или метода onDestroy(), если эта активность уничтожается.

Когда ваша Активность останавливается, объекты Активности хранятся в памяти и восстанавливаются, когда активность возобновляется. Вам не нужно повторно инициализировать компоненты, которые были созданы ранее. Кроме того, система отслеживает текущее состояние для каждого представления, поэтому, если пользователь введёт текст в текстовое поле EditText, то его содержимое сохраняется и вам не нужно сохранять и восстанавливать его.

Примечание: Даже если система закрыла вашу активность, когда она была остановлена, она по-прежнему сохраняет состояние объектов, таких как текст в EditText в специальном объекте Bundle (в виде ключ-значение) и восстанавливает их, если пользователь переходит обратно к тому же экземпляру активности.

В этом методе можно сделать сложные операции по сохранению данных: для приостановки сложной анимации, потоков, отслеживания показаний датчиков, запросов к GPS, таймеров, Сервисов или других процессов, которые нужны исключительно для обновления пользовательского интерфейса. Нет смысла потреблять ресурсы (такты центрального процессора или сетевой трафик) для обновления интерфейса, в то время как он не виден на экране. Примените методы onStart() или onRestart() для возобновления или повторного запуска этих процессов, когда Активность опять станет видимой.

onRestart()


Если окно возвращается в приоритетный режим после вызова onStop(), то в этом случае вызывается метод onRestart(). Т.е. вызывается после того, как активность была остановлена и снова была запущена пользователем. Всегда сопровождается вызовом метода onStart().

onRestart предшествует вызовам метода onStart (кроме самого первого). Используйте его для специальных действий, которые должны выполняться только при повторном запуске Активности в рамках «полноценного» состояния.

onDestroy()


Метод вызывается по окончании работы активности, при вызове метода finish() или в случае, когда система уничтожает этот экземпляр активности для освобождения ресурсов. Эти два сценария уничтожения можно определить вызовом метода isFinishing(). Вызывается перед уничтожением активности. Это последний запрос, который получает активность от системы. Если определенное окно находится в верхней позиции в стеке, но невидимо пользователю и система решает завершить это окно, вызывается метод onDestroy(). В этом случае метод удаляет все статические данные активности. Отдаёт все используемые ресурсы.

Так как все необходимые операции по освобождению ресурсов вы сделали в методе onStop(), то в этом методе вы можете подстраховаться и проверить ещё раз все неосвобождённые ресурсы.

На практике вам чаще всего придется сталкиваться с методами onCreate(), onResume() и onPause(). Метод onCreate() будет вызываться при создании пользовательского интерфейса для работы с окном. Данный метод позволит вам связывать данные с виджетами и подключать обработчики событий к компонентам пользовательского интерфейса. При помощи onStop() вы сможете сохранить важную информацию в базе данных вашего приложения. Это последний безопасный метод, который будет вызываться перед тем, как система завершит работу приложения. Метод onDestroy() не обязательно будет вызываться, поэтому не полагайтесь на этот метод при реализации критическом логики.

Еще одна диаграмма для наглядности процесса сохранения и восстановления состояния Активности

restore_instance

И так после внимательного прочтения, всего выше изложенного понимание того что происходит прибавилось, но возникло ощущение от машинного перевода всего этого дела, и то что текст был даже не до конца отредактирован, хотя на безрыбье и рак рыба и кроме того всегда есть изначальная документация по тому вопросу http://developer.android.com/guide/components/activities.html

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

Теперь попрактикуемся при помощи исходников представленных автором статьи. Создаем новый проект и кодим.

Код нашей Активности:

package com.example.ap0002;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class ActivityMain extends Activity {

 TextView tvTextLife;

 // Вызывается при входе в "полноценное" состояние.
 @Override
 protected void onCreate(Bundle savedInstanceState) {
     // Инициализируйте Активность
  super.onCreate(savedInstanceState);
  setContentView(R.layout.layout_main);

  Toast.makeText(this, "onCreate()", Toast.LENGTH_LONG).show();

  tvTextLife = (TextView) findViewById(R.id.textlife);
 }

 @Override
 protected void onDestroy() {
  // TODO Auto-generated method stub
  super.onDestroy();
  Toast.makeText(this, "onDestroy()", Toast.LENGTH_LONG).show();
 }

 @Override
 protected void onPause() {
  // TODO Auto-generated method stub
  super.onPause();
  Toast.makeText(this, "onPause()", Toast.LENGTH_LONG).show();
 }

 @Override
 protected void onRestart() {
  // TODO Auto-generated method stub
  super.onRestart();
  Toast.makeText(this, "onRestart()", Toast.LENGTH_LONG).show();
 }

 @Override
 protected void onResume() {
  // TODO Auto-generated method stub
  super.onResume();
  Toast.makeText(this, "onResume()", Toast.LENGTH_LONG).show();
 }

 @Override
 protected void onStart() {
  // TODO Auto-generated method stub
  super.onStart();
  Toast.makeText(this, "onStart()", Toast.LENGTH_LONG).show();
 }

 @Override
 protected void onStop() {
  // TODO Auto-generated method stub
  super.onStop();
  Toast.makeText(this, "onStop()", Toast.LENGTH_LONG).show();
 }

 public void onClick(View v) {
  switch (v.getId()) {
  case R.id.buttonafterstart:
   tvTextLife.setText("Приложение уже было запущено!");
   break;
  case R.id.buttonexit:
   finish();
   break;

  default:
   break;
  }
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }
}

И код разметки

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textlife"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Приложение запущено впервые"
        android:textSize="24px" />

    <Button
        android:id="@+id/buttonafterstart"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Коснись меня!" />

    <Button
        android:id="@+id/buttonexit"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Выход" />

</LinearLayout>

На всякий случай скрин как это выглядит в дизайнере

Act0012

И как работает при запуске

Act0013

Запускайте проект и следите за сообщениями. Они будут всплывать в нужной последовательности, давая вам представление о жизненном цикле приложения. Обратите внимание на следующий момент. Когда ваше приложение запущено, то нажмите на первую кнопку, чтобы изменить текст в TextView. Затем нажмите кнопку Home (не Back!), чтобы попасть на Рабочий стол. После чего снова запустите ваше приложение. Вы увидите, что приложение не вызывает метод onCreate(), и текст в TextView будет свидетельствовать, что приложение не было закрыто, а только свернуто. Это очень важный момент, который нужно понять. Понимание этих вещей поможет вам правильно выстраивать логику приложения.

Кодинг просветляет! Впрочем как и теория. Но практика закрепляет теорию.