Явления (Activity)

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

Приложение обычно состоит из нескольких явлений, которые слабо связаны друг с другом. Как правило одно явление является “главным”, оно открывается при первоначальном запуске приложения. Каждое явление может запускать другие явления для выполнения различных задач. При запуске нового явления, предыдущее всегда останавливается и сохраняется в обратном стеке (“back stack”), а новое получает фокус пользователя. Стек придерживается правила “последним пришел, первым ушел”, поэтому при нажатии кнопки “назад” явления открываются в том порядке, в котором происходила навигация пользователя. (Подробнее читайте в разделе Задачи и Обратный Стек.)

Если явление остановлено вследствие запуска другого, оно сообщает об изменении состояния с помощью методов обратного вызова жизненного цикла. Это методы, которые могут вызываться при создании, остановке, возобновлении или уничтожении явления. Каждый метод предоставляет вам возможность выполнить какие-либо операции, соответствующие изменению состояния явления. К примеру, при остановке, явление должно освободить память, удалив из него большие объекты, а также дескрипторы файлов или базы данных. При возобновлении, вы можете восстанавливать ресурсы и начать работу с места остановки. Все эти переходы между состояниями являются частью жизненного цикла явлений.

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

Создание явления

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

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

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

Создание пользовательского интерфейса

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

Android предоставляет несколько готовых к использованию компонентов, которые вы можете использовать для разработки и организации разметки. “Виджеты” это визуальные интерактивные компоненты, располагающиеся на экране, вроде кнопки, текстового поля, флажка или простой картинки. “Layouts” это объекты типа ViewGroup, которые предоставляют уникальные модели разметки для элементов, помещенных в них, это такие компоненты, как LinearLayout, GridLayout или RelativeLayout. Вы также можете создавать подклассы View или ViewGroup для создания ваших собственных виджетов или компонентов для группировки.

Наиболее распространенным способом описания разметки является XML файл ресурса. Благодаря ему вы можете разделить создание пользовательского интерфейса и программного кода. Вы можете установить разметку пользовательского интерфейса явления с помощью метода setContentView(), передав в качестве аргумента идентификатор ресурса разметки. Однако, вы можете также создавать новые компоненты интерфейса прямо в коде, используя объекты типа View, ViewGroup и их наследников. Метод setContentView() принимает в качестве параметра также объекты класса View.

Подробную информацию смотрите в разделе Пользовательский интерфейс

Объявляем явление в файле манифеста

Для того, чтобы явление стало доступно, вы должны объявить его в файле манифеста. Для этого добавьте в элемент <application> дочерний элемент <activity>. Например:

Существует несколько атрибутов, с помощью которых можно описать свойства явления, вроде заголовка, иконки или темы. Единственным обязательный атрибут android:name содержит имя явления. После публикации приложения, вы не должны менять это имя, поскольку оно влияет на некоторые функции, вроде создания ярлыков (подробнее смотрите в записи Вещи, которые нельзя менять).

Использование фильтров намерения

Элемент <activity> может содержать также фильтры намерений – тег <intent-filter>. Они используются для указания компонентов приложения, которым разрешено запускать данное явление.

При создании приложения с использованием Android SDK, главному явлению автоматически добавляются фильтры с действием “main” и категорией “launcher”. Фильтр выглядит примерно так:

Элемент <action> указывает, что данное явление является главной точкой входа в приложение. Элемент <category> указывает, что явление должно быть добавлено в список программ.

Если компоненты приложения не должны запускать из других приложений, то вам не нужны какие-либо другие фильтры намерений. Только одно явление должно содержать действие “main” и категорию “launcher”, как показано в предыдущем примере. Явления, недоступные для запуска из других приложений вообще не должны содержать фильтров, их вы сможете запускать с помощью явных намерений из вашего приложения.

Однако, если вы хотите, чтобы явления могли обрабатывать неявные намерения от других приложений (и от вашего в том числе), дополнительные фильтры намерений понадобятся. Для каждого типа намерения необходимо добавить элемент <intent-filter> с дочерними элементами <action> и <category>(необязательный).

Подробнее об этом в разделе Намерения и фильтры намерений.

Запуск явления

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

Часто при разработке приложения необходимо просто запустить нужное явление. Это можно сделать с помощью явного намерения, передав в него имя класса явления, которое нужно запустить. Например, если нужно запустить явление с именем SignInActivity, код может выглядеть так:

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

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

Запуск явления для получения результата

Иногда необходимо получить результат выполнения явления. В таких случаях явление необходимо запускать с помощью метода startActivityForResult(). Для получения результата используется метод обратного вызова onActivityResult(). Когда запущенное явление завершается, оно передает результат в виде объекта типа Intent в данный метод.

Например, для выбора контакта и возвращения результата обратно в ваше явление, можно использовать следующие код:

Данный пример показывает базовую логику обработки результата в методе onActivityResult. Первое условие проверяет, что явление завершилось успешно (т.е resultCode равен RESULT_OK) и что это результат выполнения операции, которую мы запрашивали (в данном случае код запроса requestCode совпадает со вторым параметром, который мы передали в метод startActivityForResult()). После этого мы обработали результат, переданный в объекте Intent (параметр data).

Завершение явления

Завершить явление можете с помощью вызова метода finish(). Вы можете также завершать явления, запущенные ранее с помощью метода finishActivity().

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

Управление жизненным циклом явлений

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

Явление может находиться в трех состояниях:

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

Реализация методов обратного вызова

При переключении состояний явления, система вызывает методы обратного вызова. Следующий код включает в себя все основные методы:

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

Реализуя эти методы вы можете контролировать время жизни явления:

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

На рисунке ниже показаны пути между состояниями. Прямоугольники это методы обратного вызова.

Те же методы обратного вызова с более подробным описанием приведены в таблице ниже. В том числе указано, может ли система уничтожить явление после завершения работы каждого из методов.

Метод Описание Уничтожение после выполнения? Следующий вызываемый метод
onCreate() Вызывается при первоначальном создании явления. В это методе вы должны делать первоначальные установки – создавать компоненты интерфейса, связывать данные для отображения и другое. Этот метод принимает в качестве параметра объект типа Bundle, в котором хранится информация о предыдущем состоянии явления. Нет onStart()
onRestart() Вызывается при повторном запуске остановленного явления. Нет onStart()
onStart() Вызывается перед тем, как явление станет видимым для пользователя. После этого метода вызывается onResume() если явление переходит на передний план или onStop(), если будет скрыто. Нет onResume() или onStop()
onResume() Вызывается перед началом взаимодействия с пользователем. В этот момент данное явление находится в вершине обратного стека и может принимать ввод пользователя. Нет onPause()
onPause() Вызывается, когда система собирается запустить другое явление. В этом методе, как правило, применяются несохраненные данные, останавливается анимация и другие процессы, которые потребляют процессорное время. Но все дествия не должны занимать много времени, поскольку следующее явление может быть и не запущено и фокус вернется к текущему. После него вызывается метод onResume(), если следующее явление не было запущено или onStop(), если было. Да onResume() или onStop()
onStop() Метод вызывается, когда явление становится невидимо для пользователя. Это может происходить при уничтожении явления или из-за перекрытия его другим явлением. После него вызывается onRestart(), если явление вновь открывается или onDestroy() при его уничтожении. Да onRestart() или onDestroy()
onDestroy() Вызывается перед уничтожением явления. Это последний вызов, который получает явление и он может происходить из-за вызова метода finish() или из-за уничтожения системой для освобождения пространства. Чтобы узнать точную причину, используйте метод isFinishing(). Да Ничего не вызывается

Примечание: несмотря на то, что в столбце “уничтожение после выполнения” у некоторых методов написано “нет”, в критических случаях явление все-таки может быть уничтожено. Подробнее об этом читайте в разделе Процессы и потоки.

Сохраняем состояние явления

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

Система вызывает данный метод перед уничтожением явления и передает в него объект класса Bundle, в котором вы можете сохранять информацию в виде пар ключ-значение, используя методы putString() и putInt(). Если система уничтожит процесс приложения, а пользователь вновь откроет данное явление, система воссоздаст явление и передаст объект типа Bundle в оба метода onCreate() и onRestoreInstanceState(). В любом из эти методов вы можете извлечь данные из объекта Bundle и восстановить состояние явления. Если информации для восстановления нет, объект Bundle будет равен null (такое случается, когда явление создается впервые).

Рисунок 2. два пути возвращения фокуса явлению: или явление было уничтожено и его состояние требуется восстановить, или явление было остановлено и его состояние остается неизменным.

Примечание: нет гарантии, что метод onSaveInstanceState() будет вызван перед уничтожением явления, поскольку бывают случаи, в которых нет необходимости сохранять состояние явления(например, если пользователь покидает явление, нажав кнопку “назад”, то есть явно закончил с ним работать). Если система вызывает метод onSaveInstanceState(), она делает этого перед onStop() и, возможно, до onPause().

Однако, даже если вы не создали метод onSaveInstanceState(), состояние некоторых элементов будет восстановлено, так как класс Activity реализует данный метод по умолчанию. В частности, его реализация включает в себя вызовы соответствующих методов onSaveInstanceState() для каждого компонента разметки типа View, которые предоставляют о себе информацию, которая должна быть сохранена. Почти все виджеты Android реализуют этот метод, чтобы при возобновлении явления восстановить все видимые изменения. Например, виджет текстового поля сохраняет любой введенный текст. Все, что от вас требуется, это указать уникальные идентификаторы (с помощью атрибута android:id) для всех компонентов, состояние которых вы хотите сохранять. Если у виджета нет идентификатора, система не сможет сохранить его состояние.

Вы можете также явно запретить сохранять состояние визуальных элементов, установив для него значение атрибута android:saveEnabled в значение "false" или с помощью метода setSaveEnabled().

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

Если вы переопределяете метод onSaveInstanceState(), обязательно вызывайте в нем метод базового класса, чтобы сохранить информацию о состоянии визуальных элементов! Тоже самое касается метода onRestoreInstanceState().

Примечание: поскольку нет гарантии, что метод onSaveInstanceState() будет вызван, используйте его только для сохранения информации о переходных состояниях(состоянии интерфейса) – никогда не используйте этот метод для сохранения данных в постоянное хранилище, для этого лучше использовать метод onPause().

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

Обработка изменений конфигурации

Конфигурация устройства может меняться во время выполнения приложения (положение экрана, раскладка клавиатуры и язык). При изменении конфигурации Android пересоздает явление (вызывает метод onDestroy(), а затем сразу метод onCreate()). Такое поведение помогает приложению адаптироваться под новую конфигурацию, автоматически загрузив соответствующие конфигурации ресурсы.

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

Поэтому используйте методы onSaveInstanceState() и onRestoreInstanceState() (или onCreate()), как было описано выше.

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

Координация между явлениями

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

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

  1. Выполняется метод onPause() явления А.
  2. Выполняются в указанном порядке методы onCreate(), onStart() и onResume() явления В. И теперь явление В владеет фокусом пользователя.
  3. Если явление А больше невидимо, вызывается его метод onStop().

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

Добавить комментарий