Продолжаем курс по обучению основам разработки мобильных приложений в Android Studio на языке Kotlin.
Это урок 6, в котором мы познакомимся с событиями и состояниями жизненного цикла Активити, а также поговорим о том, как их нужно (и как не нужно) обрабатывать в процессе разработки приложения.
Предыдущий урок, на котором мы добавляли второй экран в андроид-приложение, здесь
Технологии и инструменты, используемые в уроке:
Как вы уже знаете из прошлых уроков, Активити — это, по сути, экран нашего приложения. Каждый пользователь андроид устройства запускает, сворачивает экраны приложения, запускает другие приложения, вертит устройство в руках. Что происходит при этом с активити? Как изменяются его состояния? И главный вопрос, как разработчик приложения должен это учитывать? Сейчас мы это выясним.
Создаем приложение в Android Studio
Давайте создадим приложение и на его примере будем изучать поведение активити.
Каждый раз при создании нового приложения по умолчанию создается класс главного экрана приложения — MainActivity.
Когда пользователь нажимает ярлык приложения, система запускает активити, которое указано как стартовое в манифесте приложения. По умолчанию это MainActivity. После запуска мы видим активити на экране. Мы можем свернуть его или открыть другое приложение, экран настроек и т.д. При этом наше активити уходит на второй план и перестает быть видимым. Однако оно не уничтожается, а продолжает работать в фоне, и мы можем в любой момент к нему вернуться. Наконец, мы можем совсем закрыть приложение, или его может закрыть система, если ей будет не хватать оперативной памяти. Тогда активити прекратит свою работу и будет уничтожено.
Кроме того, активити может пересоздаваться — уничтожаться и создаваться заново. Это происходит при событиях изменения конфигурации, таких как поворот устройства из портретной ориентации в альбомную (и обратно), смена размера экрана или языка интерфейса. О смене конфигурации мы более подробно поговорим на следующем уроке.
Схема Lifecycle
А сейчас посмотрите на схему состояний, через которое проходит активити в процессе работы, и событий, которые при этом происходят. Схема с официального сайта https://developer.android.com/images/topic/libraries/architecture/lifecycle-states.png
В прямоугольниках указаны состояния (states) активити. А события (events), которые происходят при смене состояний, отмечены стрелками. Важно понимать, что не события управляют состояниями активити, скорее наоборот. Состояния активити изменяются системой Андроид, а события происходят в процессе изменения.
Когда активити запускается системой, оно инициализируется (INITIALIZED) и происходит событие ON_CREATE. При этом активити переходит к состоянию “создано” (CREATED).В этот момент должен инициализироваться пользовательский интерфейс, поскольку активити готовится отобразиться пользователю. Далее происходит событие ON_START и активити переходит к состоянию “запущено” (STARTED). Следующее событие ON_RESUME. Активити переходит в состояние — RESUMED (возобновлено) — выходит на передний план, получает фокус и отображается пользователю. Если активити в процессе работы теряет фокус и частично перекрывается, например, диалоговым окном или другим активити, то переходит обратно в состояние STARTED. При этом происходит событие ON_PAUSE. В этом состоянии активити приостанавливается, но может быть все еще видимым на экране, например, в многооконном режиме. Если же активити полностью перекрыто, то система его останавливает и переводит в состояние CREATED. Выполняется событие ON_STOP. Активити пока не уничтожается системой и может в любой момент возобновить работу. Но поскольку оно не видно пользователю, то в этом состоянии целесообразно отключать некоторые функции, например, воспроизведение анимации. Если пользователь закрыл активити или система испытывает недостаток памяти, или изменилась конфигурация устройства (произошел поворот), активити уничтожается системой. При этом происходит событие ON_DESTROY. В этот момент необходимо освобождать ресурсы, используемые активити.
Функции обратного вызова жизненного цикла активити
На рассмотренной нами схеме указаны события жизненного цикла, всего их шесть: ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP и ON_DESTROY..
Для обработки событий класс Activity имеет колбеки — функции обратного вызова, соответствующие каждому событию жизненного цикла. Например, это уже известная вам функция onCreate(savedInstanceState: Bundle?) {…} в которой происходит инициализация пользовательского интерфейса и вызовы некоторых других функций.
Но кроме одноименных функций жизненного цикла, в активити есть несколько дополнительных, таких как onPostResume() и onRestart(). Мы их будем использовать в качестве вспомогательных для большей наглядности.
Во всех функциях обратного вызова нужно переопределять одноименные методы суперкласса во избежание ошибок.
Функции обратного вызова полезны для изучения жизненного цикла. До недавнего времени они массово использовались разработчиками для управления поведением приложения. Однако сейчас Гугл рекомендует другой, более правильный подход, который мы рассмотрим далее в этом уроке. А пока переопределим эти функции в активити.
onCreate()
Первая функция, которая переопределена по умолчанию в каждом активити, отвечает событию ON_CREATE. Выполняется перед стартом активити, когда активити переходит из состояния INITIALIZED к состоянию CREATED.
В теле функции onCreate должна выполняться базовая логика приложения, например связывание данных со списками, инициализация переменных и т.д.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
В качестве параметра функция onCreate получает объект типа Bundle, который представляет собой набор пар “ключ — значение” и может быть использован для сохранения предыдущего состояния активити. Мы это рассмотрим подробнее на следующем уроке. Если активити запускается впервые, объект имеет значение null.
Переопределим остальные функции для каждого события жизненного цикла.
Но для начала создадим функцию toastMeState, которая будет отображать тост с информацией о том, какое событие произошло и какое состояние активити получило.
private fun toastMeState(message: String) {
Toast.makeText(this, "${lifecycle.currentState}, $message", Toast.LENGTH_LONG).show()
}
Текущее состояние активити можно получить с помощью свойства объектаlifecycle.currentState, мы еще вернемся к нему и рассмотрим подробнее.
Длительность отображения тостов будет одинаковой и не сможет отражать реальную длительность событий и состояний активити. Тем не менее мы получим наглядную информацию об их очередности.
onStart()
override fun onStart() {
super.onStart()
toastMeState("ON_START")
}
Этот колбек вызывается, когда активити стартует и готовится выйти на передний план, чтобы стать видимым пользователю. Событие ON_START проходит очень быстро, и активити не задерживается долго в состоянии STARTED, а сразу переходит к состоянию RESUMED после вызова функции onResume().
Функцию toastMeState() мы будем вызывать в каждом колбеке, передавая туда имя функции обратного вызова.
onResume()
override fun onResume() {
super.onResume()
toastMeState("ON_RESUME")
}
Вызывается при событии ON_RESUME, когда активити становится видимым пользователю и переходит в состояние RESUMED. Здесь можно стартовать анимацию, получать доступ к камере и отображать превью и т.п. Имейте в виду, что состояние RESUMED не гарантирует на 100%, что ваша активность видна пользователю — системное окно, например клавиатура, может перекрывать экран. Используйте метод onWindowFocusChanged (boolean), чтобы точно знать, что ваша активность видна пользователю.
onPostResume()
override fun onPostResume() {
super.onPostResume()
toastMeState("onPostResume")
}
Вызывается после onResume() когда процесс возобновления активити завершился. Обычно эта функция не переопределяется в приложениях, а используется системой для запуска кода, зависящего от отображения активити пользователю. Мы будем использовать этот колбек для демонстрации текущего состояния активити — RESUMED.
onPause()
override fun onPause() {
super.onPause()
toastMeState("ON_PAUSE")
}
Этот колбек вызывается при событии ON_PAUSE когда активити уходит с переднего плана и переходит из состояния RESUMED в состояние STARTED. При этом активити может оставаться видимым, например, в многооконном режиме. При этом событии можно прекращать операции, которые должны выполняться только когда активити на переднем плане, например, отображение превью с камеры. Однако не стоит использовать событие ON_PAUSE для операций сохранения данных по причине его скоротечности.
onStop()
override fun onStop() {
super.onStop()
toastMeState("ON_STOP")
}
Событие ON_STOP происходит, когда активити становится невидимым пользователю и возвращается в состояние CREATED. Это может произойти когда его полностью перекрывает вновь открытое активити. При этом событии можно освобождать те ресурсы, которые не требуются, пока активити остается невидимым пользователю, а также выполнять ресурсоемкие операции сохранения данных в БД и т.п.
onRestart()
override fun onRestart() {
super.onRestart()
toastMeState("onRestart")
}
Этот колбек вызывается после события ON_STOP если активити собирается вернуться на передний план и будет отображено пользователю. После вызова этого колбэка выполняются события ON_START и ON_RESUME. Если возврата активити не происходит, то вместо вызова onRestart() может произойти следующее событие — ON_DESTROY.
onDestroy()
override fun onDestroy() {
super.onDestroy()
toastMeState("ON_DESTROY")
}
Вызывается непосредственно перед уничтожением активити системой в связи с нехваткой памяти, нажатием кнопки “Назад”, сменой конфигурации устройства (поворот, смена размера экрана, локали и т.п), или вызова функции finish(). Не используйте это событие для выполнения длительных операций сохранения данных. Здесь можно отписаться от подписок или освободить ресурсы, которые не были освобождены в более ранних событиях жизненного цикла.