Жизненный цикл Activity: onCreate, onStart, onResume и обратно
Самый первый вопрос почти на любом Android-собеседовании — и самый недооценённый: за ним прячется вся модель того, кто в Android на самом деле управляет вашим приложением.
Жизненный цикл Activity — это набор колбэков, которыми система сообщает экрану о смене его состояния. Вызывает их не ваш код, а Android: приложение не владеет своим временем жизни, оно им пользуется по разрешению системы.
Вопрос 1: «Перечислите методы жизненного цикла Activity и скажите, когда какой вызывается»
Что на самом деле проверяет интервьюер
Не память на семь названий — их можно вызубрить за минуту. Проверяют, понимает ли кандидат главную идею платформы: процесс приложения не принадлежит разработчику. Система может остановить экран, когда пользователь принял звонок, и убить процесс целиком, когда не хватает памяти. Всё, что вы не сохранили, исчезнет. Кандидат, который это осознал, дальше сам объяснит и поворот экрана, и ViewModel, и утечки.
Развёрнутый ответ
Методы удобно запоминать не списком, а тремя вложенными парами. Каждая пара — это «вход в состояние» и «выход из него»:
| Пара | Что она ограничивает | Что делать внутри |
onCreate / onDestroy | всё время жизни экрана | создать View, получить ViewModel, разово настроить адаптеры |
onStart / onStop | экран виден пользователю | подписки на данные, регистрация BroadcastReceiver, старт локации |
onResume / onPause | экран в фокусе, принимает ввод | камера, сенсоры, анимации, воспроизведение видео |
Плюс onRestart — вызывается перед onStart, если экран возвращается из состояния «остановлен», а не создаётся заново.
class ProfileActivity : AppCompatActivity() {
private lateinit var binding: ActivityProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
// разовая настройка: адаптеры, слушатели кнопок, ViewModel
}
override fun onStart() {
super.onStart()
// экран стал видимым — самое место начать получать данные
}
override fun onResume() {
super.onResume()
// экран в фокусе — запускаем камеру, сенсоры, анимации
}
override fun onPause() {
// симметрично onResume; метод обязан быть БЫСТРЫМ
super.onPause()
}
override fun onStop() {
// экран скрыт: отписки, остановка тяжёлой работы
super.onStop()
}
override fun onDestroy() {
// может не вызваться вообще, если систему прижало по памяти
super.onDestroy()
}
}
Полезно уметь назвать конкретные сценарии — именно за них ставят плюс:
- Поверх открылся диалог или полупрозрачный экран. Activity теряет фокус, но остаётся видимой:
onPauseвызовется,onStop— нет. - Пользователь свернул приложение кнопкой Home.
onPause→onStop. Экран жив, но невидим. - Возврат в приложение.
onRestart→onStart→onResume. - Кнопка «Назад». Экран закрывается насовсем:
onPause→onStop→onDestroy, при этомisFinishing == true. - Не хватило памяти. Процесс убивают в фоне без
onDestroy. При возврате Activity создаётся заново с непустымsavedInstanceState.
Отдельно живёт onSaveInstanceState: система вызывает его, когда экран может быть уничтожен, чтобы вы положили в Bundle минимум для восстановления. До Android 9 он вызывался перед onStop, начиная с Android 9 — после. Полагаться на точный порядок относительно onStop не стоит.
Типичные ошибки кандидатов
- Считают
onDestroyгарантированным и «сохраняют данные там». При убийстве процесса он не вызовется — сохранять надо вonStopили сразу по изменению. - Грузят сеть и базу в
onCreateсинхронно. ПокаonCreateне закончится, экрана на дисплее нет — получаем белый кадр и повод для ANR. - Делают тяжёлую работу в
onPause. Следующая Activity не начнёт создаваться, покаonPauseтекущей не отработает: переход визуально «залипает». - Подписываются в
onCreate, а отписываются вonDestroy. Пока приложение в фоне, подписка продолжает жить и жечь батарею. Симметрия должна быть внутри одной пары: подписались вonStart— отписались вonStop. - Не знают про lifecycle-aware подход. Современный ответ: колбэки руками почти не нужны — есть
DefaultLifecycleObserver,lifecycleScopeиrepeatOnLifecycle(Lifecycle.State.STARTED), которые сами привязывают работу к нужному состоянию.
// вместо ручных onStart/onStop для подписки на поток данных
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state -> render(state) }
}
}
Вопрос 2: «В каком методе освобождать ресурсы?»
Что проверяет интервьюер
Умение рассуждать не заученным списком, а правилом. Правильный ответ — вопросом на вопрос: «Какие именно ресурсы?»
Ответ
Правило одно: освобождайте в парном методе того, где захватили, и выбирайте пару по тому, нужен ли ресурс невидимому экрану.
- Камера, микрофон, GPS высокой точности, акселерометр, воспроизведение видео — захват в
onResume, освобождение вonPause. Эти ресурсы эксклюзивны: если вы не отпустите камеру, её не получит приложение, которое пользователь открыл поверх вашего. - Подписки на данные, регистрация приёмников, соединения —
onStart/onStop. - Объекты, живущие ровно столько же, сколько экран —
onCreate/onDestroy.
Как ответить кратко (20–30 секунд)
«Жизненный цикл — три вложенные пары:
onCreate/onDestroy— всё время жизни,onStart/onStop— видимость,onResume/onPause— фокус. ПлюсonRestartпри возврате. Ресурс захватываю и освобождаю в одной паре, выбирая её по тому, нужен ли ресурс невидимому экрану: камера — в resume/pause, подписки — в start/stop. НаonDestroyне полагаюсь: при нехватке памяти процесс убивают без него, поэтому состояние сохраняю вonSaveInstanceStateиonStop. На практике вместо ручных колбэков использую lifecycle-aware API:lifecycleScopeиrepeatOnLifecycle.»