Жизненный цикл 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. onPauseonStop. Экран жив, но невидим.
  • Возврат в приложение. onRestartonStartonResume.
  • Кнопка «Назад». Экран закрывается насовсем: onPauseonStoponDestroy, при этом 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

Проверьте себя
1. Поверх Activity открылся диалог из другого приложения, при этом часть экрана осталась видимой. Какие колбэки вызовутся?
AonPause, затем onStop
Bтолько onPause
ConPause, onStop, onDestroy
Dтолько onStop
2. Почему сохранять пользовательские данные в onDestroy — плохая идея?
AonDestroy выполняется в фоновом потоке, поэтому запись может не успеть
BonDestroy вызывается раньше onStop и данные ещё не готовы
ConDestroy может не вызваться вовсе: систему вправе убить процесс в фоне
Dв onDestroy запрещены операции ввода-вывода
3. Почему нельзя выполнять долгую операцию в onPause?
AonPause запускается до создания View, доступ к данным ещё невозможен
Bследующая Activity не начнёт создаваться, пока onPause не завершится, и переход подвиснет
Cсистема автоматически откатывает изменения, сделанные в onPause
DonPause вызывается в отдельном потоке без доступа к главному Looper