onPause — це метод життєвого циклу Android, який викликається, коли Activity втрачає фокус введення, але залишається частково видимим на екрані. Система викликає onPause перед тим, як нове Activity виходить на передній план, при відкритті діалогового вікна, при натисканні кнопки «Нещодавні програми» або при вхідному дзвінку. Цей метод — остання гарантована точка для збереження користувацьких даних, оскільки після onStop та onDestroy система може завершити процес без додаткових викликів. Всередині onPause розробник зберігає чернетки, призупиняє анімації, звільняє камеру та записує поточний стан UI у SharedPreferences. Докладніше про повний життєвий цикл Activity читайте у статті Activity Lifecycle.
Головне
onPause — четвертий метод життєвого циклу Activity, який викликається, коли екран втрачає фокус введення, але залишається частково видимим для користувача. Це «перехідний» стан між активною роботою додатку та його приховуванням. Система викликає onPause в наступних сценаріях: відкриття іншого Activity (новий екран перекриває поточний частково), поява діалогового вікна (Dialog, PopupWindow, Snackbar не викликають onPause, а DialogFragment — викликає), натискання кнопки «Нещодавні програми», вхідний дзвінок, натискання кнопки живлення для блокування екрану.
Основне завдання onPause — підготувати додаток до того, що він може бути прихований або знищений. Це остання точка в життєвому циклі, де розробник може бути впевнений, що його код виконається, перш ніж система продовжить перехід до іншого компоненту. Після onPause система викликає onStop (якщо Activity повністю приховується), після чого знищення процесу може відбутися в будь-який момент без додаткових повідомлень.
Згідно з документацією Android Developers (2025), onPause має бути максимально легким та швидким. Поки onPause не поверне управління, система не може запустити наступне Activity — це означає, що користувач бачить затримку переходу між екранами. Google рекомендує завершувати onPause за менш ніж 100 мілісекунд, а всі тривалі операції (збереження в базу даних, запис на диск) виконувати асинхронно через корутини або apply().
В Activity метод onPause викликається щоразу, коли екран перестає бути активним, але може продовжувати відображатися частково. Типовий приклад: користувач відкриває додаток «Карти», натискає «Поділитися місцезнаходженням», і поверх Карт відкривається системний діалог вибору додатку. Activity Карт отримує onPause, але залишається видимим під діалогом. Коли діалог закривається, Карти отримують onResume без виклику onStart (екран не був прихований повністю).
class NoteEditorActivity : AppCompatActivity() {
private var binding: ActivityNoteEditorBinding? = null
private val prefs by lazy {
getSharedPreferences("note_drafts", Context.MODE_PRIVATE)
}
override fun onPause() {
super.onPause()
// Зберігаємо чернетку нотатки — асинхронно
prefs.edit()
.putString("draft_title", binding?.titleInput?.text.toString())
.putString("draft_body", binding?.bodyInput?.text.toString())
.putLong("draft_timestamp", System.currentTimeMillis())
.apply()
// Призупиняємо відео
binding?.videoPlayer?.pause()
// Звільняємо ексклюзивні ресурси
releaseCamera()
releaseAudioFocus()
}
override fun onResume() {
super.onResume()
// Відновлюємо чернетку
binding?.titleInput?.setText(prefs.getString("draft_title", ""))
binding?.bodyInput?.setText(prefs.getString("draft_body", ""))
acquireCamera()
acquireAudioFocus()
}
}
Приклад NoteEditorActivity демонструє правильну роботу з onPause: збереження чернетки в SharedPreferences через apply(), призупинення відеофайлу, звільнення камери та аудіофокусу. Кожен виклик — легкий та швидкий, не блокуючий UI-потік на час, достатній для ANR. Зверніть увагу на порядок: super.onPause() викликається в першому рядку — це гарантує, що системна логіка виконається навіть при винятку в користувацькому коді.
onPause — остання точка, де розробник може гарантовано зберегти дані користувача перед тим, як додаток буде прихований або вбитий системою. Після onStop система може знищити процес при нестачі пам'яті без виклику onDestroy. Метод onSaveInstanceState() викликається після onPause, але його Bundle не призначений для довготривалого зберігання — він живе лише до наступного onCreate.
SharedPreferences з асинхронним apply() — оптимальний спосіб збереження невеликих обсягів даних в onPause. На відміну від commit(), який синхронно записує дані на диск та повертає boolean, apply() негайно зберігає дані в пам'яті та планує асинхронний запис на диск. Це займає менше 1 мілісекунди в UI-потоці проти 10–100 мілісекунд у commit().
override fun onPause() {
super.onPause()
// ❌ Погано: синхронний запис блокує потік
// prefs.edit().putInt("score", score).commit()
// ✅ Добре: асинхронний запис
prefs.edit().putInt("score", score).apply()
// Для складних об'єктів — кешування в ViewModel
viewModel.saveState()
}
Для структурованих даних (SQLite через Room) в onPause використовують корутини з lifecycleScope. ViewModelScope автоматично скасовує корутину при знищенні ViewModel, що запобігає запису в закриту базу даних. Запис через Room з корутинами займає 5–15 мілісекунд і не блокує UI-потік.
// У ViewModel:
fun saveDraft(title: String, body: String) {
viewModelScope.launch(Dispatchers.IO) {
noteDao.insert(NoteDraft(title = title, body = body))
}
}
// В Activity.onPause:
viewModel.saveDraft(
binding?.titleInput?.text.toString(),
binding?.bodyInput?.text.toString()
)
onPause у Fragment викликається, коли Fragment перестає бути активним, але може залишатися видимим. Це відбувається, коли: Fragment замінюється іншим Fragment через FragmentTransaction; Fragment перестає бути поточною сторінкою в ViewPager; Activity, що містить Fragment, отримує onPause. Взаємодія між onPause Activity та onPause Fragment строго ієрархічна: спочатку onPause отримує Activity, потім всі його Fragment.
class MapFragment : Fragment() {
private var mapController: MapController? = null
override fun onPause() {
super.onPause()
mapController?.stopFollowMode()
binding?.mapContainer?.alpha = 0.7f
}
override fun onResume() {
super.onResume()
binding?.mapContainer?.alpha = 1.0f
if (isVisible) {
mapController?.startFollowMode()
}
}
}
Специфіка роботи з картами в onPause: Google Maps та Yandex Maps споживають значні ресурси GPU при активному режимі стеження (follow mode). При втраті фокусу має сенс вимикати анімацію карти та знижувати частоту оновлення маркерів, а при поверненні фокусу — відновлювати повну функціональність. Це покращує продуктивність та знижує енергоспоживання при перемиканні між екранами.
Одна з найпоширеніших плутанин у початківців Android-розробників — нерозуміння різниці між onPause та onStop. Розглянемо кожен сценарій та визначимо правильний метод.
| Сценарій | onPause | onStop |
|---|---|---|
| Відкриття діалогового вікна | Викликається | Не викликається |
| Відкриття нового Activity (не прозорого) | Викликається | Викликається |
| Натискання кнопки «Додому» | Викликається | Викликається |
| Блокування екрану | Викликається | Викликається |
| Вхідний дзвінок | Викликається | Викликається |
| Прозоре Activity поверх поточного | Викликається | Не викликається |
| Split Screen (половина екрану) | Викликається | Не викликається |
| PiP (Picture-in-Picture) | Викликається | Не викликається |
Головне правило: onPause викликається при будь-якій втраті фокусу, onStop — тільки при повній втраті видимості. Якщо Activity залишається видимим (навіть частково), onStop не викликається. Це критично важливо для режимів Split Screen, PiP та прозорих Activity — тут onPause/onResume працюють, а onStart/onStop — ні.
onPause — найкритичніший за часом метод життєвого циклу, оскільки він блокує відображення наступного Activity. Система очікує завершення onPause поточного Activity, перш ніж показати нове. Якщо onPause виконується довше 100 мілісекунд, користувач помічає затримку переходу; якщо довше 5 секунд — система показує ANR.
Google Android Performance Guide (2025) дає наступні рекомендації для onPause: не виконуйте мережеві запити — вони мають бути скасовані або перенесені в WorkManager; не записуйте великі файли на диск — використовуйте BufferedWriter у фоновому потоці; не виконуйте складні SQL-запити — Room-операції мають бути асинхронними через корутини; уникайте створення нових об'єктів — збірка сміття в onPause погіршує затримку; використовуйте apply() замість commit() для SharedPreferences.
override fun onPause() {
super.onPause()
// ❌ Погано: HTTP-запит блокує UI
// val response = api.syncSave(data).execute()
// ❌ Погано: синхронний запис у файл
// FileOutputStream(file).write(data)
// ✅ Добре: асинхронне збереження
lifecycleScope.launch {
withContext(Dispatchers.IO) {
api.saveData(data)
fileDao.write(data)
}
}
// ✅ Добре: легкий запис у SharedPreferences
prefs.edit().putString("key", value).apply()
}
Профілювання onPause через Android Studio Profiler (CPU-графа) показує точний час виконання. Якщо onPause займає більше 100 мс, Profiler підсвічує метод жовтим, а більше 500 мс — червоним. У комерційних проектах IT Sectr ми використовуємо Macrobenchmark-тести, які автоматично перевіряють час переходу між Activity та сигналізують про регресію продуктивності в CI-пайплайні.
Досвідчені розробники теж допускають помилки в onPause. Розглянемо п'ять типових проблем та їх рішення.
Виклик Room DAO з синхронним запитом (.executeAsObservable() без корутин) в onPause блокує UI-потік на 10–50 мс. Якщо в цей момент відбувається GC або конкуренція за запис у БД, затримка може досягати 200–500 мс. Рішення: використовувати корутини з Dispatchers.IO або apply() для SharedPreferences.
onPause — не місце для реєстрації слухачів. Якщо зареєструвати BroadcastReceiver в onPause, він залишиться активним, коли Activity вже не видимий. Реєстрація повинна бути тільки в onStart/onResume, а в onPause/onStop — тільки відписка. Виняток — Intent-driven API, які вимагають реєстрації перед викликом.
Якщо в onPause виникає необроблений виняток, система не викликає onStop та onDestroy. Activity зависає в невизначеному стані, а onResume при поверненні може не відновити коректно звільнені ресурси. Рішення: обертати критичні операції в try/catch з логуванням через Log.e().
Не потрібно зберігати в onPause дані, які легко відновити. Наприклад, результати API-запитів кешують в Room або DataStore в момент отримання, а не в onPause. Зберігайте тільки те, що користувач ввів вручну і не зможе відновити автоматично — текст в полях, вибрані елементи, позицію скролу.
super.onPause() має викликатися, але, на відміну від onCreate, його відсутність не викликає негайного крашу. Система «прощає» пропуск super в onPause, але внутрішній кінцевий автомат переходить у некоректний стан. Наступний виклик onResume може не відновити фокус введення, і Activity залишиться «замороженим». Завжди викликайте super.onPause() якомога раніше.
Часті запитання
Виклик finish() в onPause завершить Activity негайно після повернення з методу. Це коректний сценарій, якщо при втраті фокусу потрібно закрити екран (наприклад, екран авторизації при згортанні додатку). Однак finish() запускає повний цикл завершення: onStop → onDestroy, що додає затримку до переходу. Використовуйте finish() в onPause тільки коли це дійсно необхідно.
onPause — для збереження даних, які повинні пережити завершення процесу (чернетки в SharedPreferences/Room). onSaveInstanceState — для збереження тимчасового стану UI, який потрібен тільки до наступного onCreate (позиція скролу, вибрана вкладка). Bundle onSaveInstanceState не зберігається при повному завершенні додатку — він існує тільки в пам'яті. onPause-дані зберігаються на диску і переживають перезавантаження.
Не рекомендується. Відкриття діалогу або спливаючого вікна в onPause призводить до WindowLeakException, якщо Activity вже завершено. Якщо потрібно показати сповіщення при втраті фокусу, використовуйте NotificationManager (системні сповіщення) — це безпечно та очікувано для користувача. Для відкладених дій використовуйте AlarmManager або WorkManager.
onPause гарантовано викликається перед тим, як Activity перестане бути активним. onStop може не викликатися, якщо система вбиває процес для звільнення пам'яті — в цьому випадку onDestroy також не викликається. onPause — єдиний метод після onResume, який викликається завжди, незалежно від причини втрати фокусу. Тому всі критичні дані зберігають саме в onPause.
Для тестування onPause використовують Robolectric або FragmentScenario від AndroidX Test. FragmentScenario.create() → moveToState(State.STARTED) → moveToState(State.RESUMED) → moveToState(State.STARTED) послідовно викликає onPause. Потім перевіряється, що дані збережено в SharedPreferences або що камеру звільнено через mock-об'єкт. Robolectric 4.12+ підтримує емуляцію onPause/onResume без фізичного пристрою.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також