Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Жизненный цикл активности - это важная тема, о которой часто любят спрашивать на собеседованиях. Следует разобраться, как живёт приложение, чтобы не было мучительно больно. На самом деле об этом следовало поговорить в самом начале курса, но ничего страшного, познакомимся сейчас. Пример написан с использованием Kotlin, но код достаточно простой и вам не составит труда написать такой же код на Java.
Прежде чем изучать жизненный цикл, познакомимся с бревенчатым котом Logcat.
У английского слова log есть несколько значений. В айтишной среде слово используется в качестве регистрационного журнала - смотреть логи, т.е. смотреть записи событий на сервере, в системе и т.д.
Также log имеет другое обычное значение - бревно. Следовательно, logcat можно перевести как бревенчатый кот, чтобы это не значило. Слово "бревно" обыгрывается в другой библиотеке Timber, который является продвинутым вариантом Logcat. "Timber" переводится как лесозаготовки, брус - т.е. уже обработанное бревно.
Начнём с самого простого. Вы уже привыкли, что у активности есть метод onCreate(), в котором мы обычно пишем код для инициализации компонентов и что-то ещё. Это метод жизненного цикла активности и он явно присутствует в нашем коде.
Но существуют и другие методы жизненного цикла. Их немного и они довольно часто используются, поэтому со временем вы их запомните наизусть. Сейчас следует понять, что эти методы вызываются в любом случае, даже если вы их явно не указали в своём коде. А зачем же они тогда нужны, если мы их не видим? А нужны для тех случаев, когда нам нужно воспользоваться ими в своих целях.
Возьмём метод onStart(). Он вызывается после onCreate(). Как в этом убедиться? Здесь нам поможет специально обученный кот Logсat (логичный кот), вкладку которого можно увидеть в нижней части студии. А пока сразу после метода onCreate() начинаем печатать название метода onStart(). Четырёх символов вполне достаточно, чтобы увидеть нужный метод и нажать Enter и получить заготовку.
override fun onStart() {
super.onStart()
}
В заготовку вставляем код, который будет обрабатываться логичным котом.
override fun onStart() {
super.onStart()
Log.i("MainActivity", "onStart() called")
}
Подробнее о методах Log можно почитать в отдельно статье. Для большинства случае достаточно запомнить одну конструкцию с методом i (information), в котором указывают два строковых параметра. В первом параметре обычно указывают имя класса, а во втором - пояснение о событии, за которым мы наблюдаем. Если проект сложный и состоит из множества активностей, то первый параметр поможет быстро понять, откуда пришло сообщение.
Добавим аналогичный вызов в метод onCreate().
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.i("MainActivity", "onCreate() called")
}
Запустим приложение (желательно в эмуляторе), откроем вкладку Logсat и установим наблюдение. В окне Logcat будет слишком много сообщений, которые будут нам мешать. Когда смотришь на бесконечный поток сообщений с какими-то веб-адресами и прочими записями, создаётся впечатление, что студия сливает всю информацию о банковских счетах, семейном положении, недвижимости в ЦРУ, ФБР, ФСБ, КГБ, Моссад одновременно. Говорят, что у котов усы (вибриссы) служат антеннами для связи с космосом. Не удивлюсь, если и эти пушистые засранцы передают информацию о своём хозяине и поток информации также льётся такой же бесконечной лентой. Когда коты решат захватить мир, у человечества не будет шансов.
Когда чувство паранойи немного отпустит, продолжим работу. Чтобы сократить число мешающих сообщений, выберите на вкладке пункт Show only selected application. Также следите, чтобы на второй вкладке отображалось название пакета вашего приложения. Дальше можно сократить число сообщений, если выбрать в другой вкладке пункт Info вместо Verbose. И наконец, в строке поиска можно ввести строку I/Main (если недостаточно, до вводите больше символов, вплоть до I/MainActivity), чтобы отсечь вообще всё лишнее. Теперь ничего нам не будет мешать.
Мы видим, что сначала был вызван метод onCreate(), затем onStart().
I/MainActivity: onCreate() called
I/MainActivity: onStart() called
Перед записью идёт временная метка (дата, время). Можете иногда смотреть на эти показания, если вам интересно.
Не закрывая приложение, поверните устройство (в эмуляторе есть специальные значки для поворота) и наблюдайте за сообщениями в Logcat. Вы снова увидите пару строк, как при запуске приложения.
Наверное, вы уже подумали, что onStart() всегда вызывается после onCreate(). Рано успокаиваться. Не закрывая приложение, попробуйте вызвать другое приложение, которое бы закрыло ваше приложение. Это можно сделать следующим способом. Сверните приложение через среднюю кнопку навигации, запустите любое приложение, закройте запущенное приложение, через правую кнопку навигации откройте список недавно запущенных приложений и запустите своё приложение. Если посмотреть на сообщения, то увидите, что появится только строка "onStart() called", а строка "onCreate() called" не появится.
// запустили приложение
I/MainActivity: onCreate() called
I/MainActivity: onStart() called
// повернули устройство
I/MainActivity: onCreate() called
I/MainActivity: onStart() called
// запустили и закрыли другое приложение, заново восстановили своё приложение
I/MainActivity: onStart() called
Следует запомнить: за onCreate() всегда следует вызов onStart(), но перед onStart() не обязательно должен идти onCreate(), так как onStart() может вызываться и для возобновления работы приостановленного приложения (приложение останавливается методом onStop()). При вызове onStart() окно ещё не видно пользователю, но вскоре будет видно.
Когда это может пригодиться? Допустим, мы используем датчик освещения. Его можно зарегистрировать в onStart(), так как он всегда будет вызван после onCreate(). Если приложение временно приостановлено, мы снимаем регистрацию датчика для экономии ресурсов, а при возобновлении работы снова включаем датчик. Такой подход позволяет грамотно распределять ресурсы приложения.
Настало время познакомиться с другими методами жизненного цикла. Для удобства мы повторим прошлый манёвр и добавим вызов сообщений в Logcat.
package ru.alexanderklimov.hellokot
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.i("MainActivity", "onCreate() called")
}
override fun onStart() {
super.onStart()
Log.i("MainActivity", "onStart() called")
}
override fun onRestart() {
super.onRestart()
Log.i("MainActivity", "onRestart() called")
}
override fun onResume() {
super.onResume()
Log.i("MainActivity", "onResume() called")
}
override fun onPause() {
super.onPause()
Log.i("MainActivity", "onPause() called")
}
override fun onStop() {
super.onStop()
Log.i("MainActivity", "onStop() called")
}
override fun onDestroy() {
super.onDestroy()
Log.i("MainActivity", "onDestroy() called")
}
}
Мы получили заготовку и теперь можем наблюдать, как ведёт себя приложение в разных ситуациях - при вращении, закрытии, паузе и т.д. Далее следует изучить закономерности, в каком порядке методы жизненного цикла вызываются и использовать в своих целях. Описание методов и графики есть в отдельной статье.
Жизненный цикл есть не только у активности, но и у фрагментов. Частично эти циклы совпадают, но есть и различия. Используя методику, изложенную в стаье, вы можете самостоятельно проследить за жизненным циклом фрагмента и использовать в собственных целях.
Сообщения Logcat вы можете использовать в любых удобных местах, чтобы посмотреть, как отработал код, результат которого не виден на экране. Так вы всегда можете проверить, что всё идёт по плану.
Обсуждение статьи на форуме.