onPause: какво е това, запазване на състоянието на Activity в Android

Автор: IT Sectr Публикувано: 2026-03-04 Време за четене: 10 мин

onPause — метод от жизнения цикъл на Android, който се извиква, когато Activity губи фокуса за въвеждане, но остава частично видимо на екрана. Системата извиква onPause преди ново Activity да излезе на преден план, при отваряне на диалогов прозорец, при натискане на бутона „Последни приложения” или при входящо повикване. Този метод — последната гарантирана точка за запазване на потребителски данни, тъй като след onStop и onDestroy системата може да прекрати процеса без допълнителни извиквания. Вътре в onPause разработчикът запазва чернови, спира анимации, освобождава камерата и записва текущото състояние на UI в SharedPreferences. Повече за пълния жизнен цикъл на Activity прочетете в статията Activity Lifecycle.

Основни точки

  • onPause — Activity губи фокус, но остава видимо; последна гарантирана точка за запазване на данни
  • Запазване на състояние — в onPause се запазват критични потребителски данни: чернови, текст във форми, напредък
  • Освобождаване на ресурси — камера, микрофон, видеоплейър се освобождават в onPause за предаване на друго приложение
  • Времево ограничение — onPause трябва да завърши за 100 ms; превишаването причинява ANR и забавя прехода
  • SharedPreferences.apply() — асинхронен запис в onPause; commit() блокира нишката и може да причини ANR
  • onPause vs onStop — onPause при частична видимост (диалог), onStop при пълно скриване (друго Activity)
  • onSaveInstanceState — извиква се след onPause за запазване на временно състояние в Bundle

Какво е onPause в Android

onPause — четвъртият метод от жизнения цикъл на Activity, който се извиква, когато екранът губи фокуса за въвеждане, но остава частично видим за потребителя. Това е „преходно” състояние между активната работа на приложението и скриването му. Системата извиква onPause в следните сценарии: отваряне на друго Activity (нов екран покрива текущия), появяване на диалогов прозорец (Dialog, PopupWindow, Snackbar не извикват onPause, но DialogFragment извиква), натискане на бутона „Последни приложения”, входящо повикване, натискане на бутона „Захранване” за заключване на екрана.

Основната задача на onPause — да подготви приложението за възможността да бъде скрито или унищожено. Това е последната точка в жизнения цикъл, където разработчикът може да бъде сигурен, че кодът му ще се изпълни, преди системата да продължи прехода към друг компонент. След onPause системата извиква onStop (ако Activity се скрива напълно), след което унищожаването на процеса може да настъпи по всяко време без допълнителни уведомления.

Според документацията на Android Developers (2025), onPause трябва да бъде максимално лек и бърз. Докато onPause не върне контрола, системата не може да стартира следващото Activity — това означава, че потребителят вижда забавяне на прехода между екраните. Google препоръчва onPause да завършва за по-малко от 100 милисекунди, а всички продължителни операции (запазване в база данни, запис на диск) да се изпълняват асинхронно чрез корутини или apply().

onPause в Activity

В Activity методът onPause се извиква всеки път, когато екранът престане да бъде активен, но може да продължи да се показва частично. Типичен пример: потребителят отваря приложението „Карти”, натиска „Споделяне на местоположение” и над Картите се отваря системен диалог за избор на приложение. Activity на Картите получава onPause, но остава видимо под диалога. Когато диалогът се затвори, Картите получават onResume без извикване на onStart (екранът не беше напълно скрит).

kotlin
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

onPause — последната точка, където разработчикът може гарантирано да запази потребителски данни, преди приложението да бъде скрито или убито от системата. След onStop системата може да унищожи процеса при недостиг на памет без да извика onDestroy. Методът onSaveInstanceState() се извиква след onPause, но неговият Bundle не е предназначен за дългосрочно съхранение — живее само до следващото onCreate.

SharedPreferences с apply()

SharedPreferences с асинхронен apply() — оптималният начин за запазване на малки обеми данни в onPause. За разлика от commit(), който синхронно записва данни на диск и връща boolean, apply() незабавно запазва данни в паметта и планира асинхронен запис на диск. Това отнема по-малко от 1 милисекунда в UI нишката срещу 10–100 милисекунди при commit().

kotlin
override fun onPause() {
    super.onPause()

    // ❌ Лошо: синхронният запис блокира нишката
    // prefs.edit().putInt("score", score).commit()

    // ✅ Добре: асинхронен запис
    prefs.edit().putInt("score", score).apply()

    // За сложни обекти — кеширане в ViewModel
    viewModel.saveState()
}

Room и корутини

За структурирани данни (SQLite чрез Room) в onPause се използват корутини с lifecycleScope. ViewModelScope автоматично отменя корутината при унищожаване на ViewModel, което предотвратява запис в затворена база данни. Записът чрез Room с корутини отнема 5–15 милисекунди и не блокира UI нишката.

kotlin
// Във 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

onPause във Fragment се извиква, когато Fragment престане да бъде активен, но може да остане видим. Това се случва, когато: Fragment бъде заменен от друг Fragment чрез FragmentTransaction; Fragment престане да бъде текущата страница във ViewPager; Activity, съдържащо Fragment, получи onPause. Взаимодействието между onPause на Activity и onPause на Fragment е строго йерархично: първо onPause получава Activity, след това всички негови Fragmentи.

kotlin
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). При загуба на фокус има смисъл да изключите анимацията на картата и да намалите честотата на обновяване на маркерите, а при връщане на фокуса — да възстановите пълната функционалност. Това подобрява производителността и намалява консумацията на енергия при превключване между екрани.

onPause vs onStop: разлика и сценарии

Едно от най-честите обърквания сред начинаещите Android разработчици — неразбирането на разликата между onPause и onStop. Нека разгледаме всеки сценарий и да определим правилния метод.

СценарийonPauseonStop
Отваряне на диалогов прозорецИзвиква сеНе се извиква
Отваряне на ново Activity (не прозрачно)Извиква сеИзвиква се
Натискане на бутона „Начало”Извиква сеИзвиква се
Заключване на екранаИзвиква сеИзвиква се
Входящо повикванеИзвиква сеИзвиква се
Прозрачно Activity над текущотоИзвиква сеНе се извиква
Split Screen (половин екран)Извиква сеНе се извиква
PiP (Picture-in-Picture)Извиква сеНе се извиква

Основно правило: onPause се извиква при всяка загуба на фокус, onStop — само при пълна загуба на видимост. Ако Activity остане видимо (дори частично), onStop не се извиква. Това е критично за режимите Split Screen, PiP и прозрачни Activity — тук onPause/onResume работят, а onStart/onStop — не.

Време и производителност на onPause

onPause — най-критичният по време метод от жизнения цикъл, тъй като блокира изобразяването на следващото Activity. Системата изчаква завършването на onPause на текущото Activity, преди да покаже ново. Ако onPause отнеме повече от 100 милисекунди, потребителят забелязва забавяне на прехода; ако повече от 5 секунди — системата показва ANR.

Препоръки за производителност

Ръководството за производителност на Android на Google (2025) дава следните препоръки за onPause: не изпълнявайте мрежови заявки — те трябва да бъдат отменени или преместени в WorkManager; не записвайте големи файлове на диск — използвайте BufferedWriter във фонова нишка; не изпълнявайте сложни SQL заявки — Room операциите трябва да бъдат асинхронни чрез корутини; избягвайте създаването на нови обекти — събиране на отпадъци в onPause влошава забавянето; използвайте apply() вместо commit() за SharedPreferences.

kotlin
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 ms, Profiler отбелязва метода в жълто, а повече от 500 ms — в червено. В комерсиалните проекти на IT Sectr използваме Macrobenchmark тестове, които автоматично проверяват времето за преход между Activity и сигнализират за регресия на производителността в CI пайплайна.

Чести грешки в onPause

Дори опитни разработчици правят грешки в onPause. Нека разгледаме пет типични проблема и техните решения.

Синхронен запис в базата данни

Извикването на Room DAO със синхронна заявка (.executeAsObservable() без корутини) в onPause блокира UI нишката за 10–50 ms. Ако в този момент настъпи GC или конкуренция за запис в базата данни, забавянето може да достигне 200–500 ms. Решение: използвайте корутини с 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()

super.onPause() трябва да се извика, но за разлика от onCreate, липсата му не причинява незабавен срив. Системата „прощава” пропускането на super в onPause, но вътрешната машина на състоянията преминава в неправилно състояние. Следващото извикване на onResume може да не възстанови фокуса за въвеждане и Activity остава „замръзнало”. Винаги извиквайте super.onPause() възможно най-рано.

Често задавани въпроси

Какво ще стане, ако извикате finish() в onPause?

Извикването на finish() в onPause ще прекрати Activity незабавно след връщане от метода. Това е правилен сценарий, ако при загуба на фокус трябва да се затвори екранът (например екран за авторизация при минимизиране на приложението). Въпреки това, finish() стартира пълния цикъл на прекратяване: onStop → onDestroy, което добавя забавяне към прехода. Използвайте finish() в onPause само когато е наистина необходимо.

С какво onPause се различава от onSaveInstanceState?

onPause — за запазване на данни, които трябва да преживеят прекратяването на процеса (чернови в SharedPreferences/Room). onSaveInstanceState — за запазване на временно състояние на UI, което е необходимо само до следващото onCreate (позиция на скрол, избран раздел). Bundle на onSaveInstanceState не се запазва при пълно прекратяване на приложението — съществува само в паметта. Данните на onPause се запазват на диск и преживяват рестартиране.

Може ли да се отвори диалогов прозорец в onPause?

Не се препоръчва. Отварянето на диалог или изскачащ прозорец в onPause води до WindowLeakException, ако Activity вече е прекратено. Ако трябва да покажете известие при загуба на фокус, използвайте NotificationManager (системни известия) — това е безопасно и очаквано от потребителя. За отложени действия използвайте AlarmManager или WorkManager.

Защо onPause е гарантирано място за запазване, а onStop — не?

onPause се извиква гарантирано, преди Activity да престане да бъде активно. onStop може да не бъде извикан, ако системата убие процеса за освобождаване на памет — в този случай onDestroy също не се извиква. onPause е единственият метод след onResume, който се извиква винаги, независимо от причината за загуба на фокус. Ето защо всички критични данни се запазват точно в onPause.

Как да тестваме 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 без физическо устройство.

Обобщение

  • onPause — Activity губи фокус за въвеждане, но остава частично видимо; последна гарантирана точка за запазване на данни
  • Запазване — SharedPreferences.apply() или Room чрез корутини; commit() и синхронните операции са забранени
  • Освобождаване на ресурси — камера, аудио фокус, видеоплейър се освобождават в onPause за друго приложение
  • Ограничение 100 ms — onPause блокира изобразяването на следващото Activity; превишаването причинява ANR
  • onPause vs onStop — onPause при загуба на фокус (видимостта се запазва), onStop при пълно скриване
  • Fragment.onPause — йерархично извикване след Activity.onPause; специфика за карти и ViewPager
  • Типични грешки — синхронен запис, регистриране на слушатели, игнориране на try/catch, излишно запазване
  • super.onPause() — извиквайте възможно най-рано; пропускането не причинява срив, но разваля машината на състоянията

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също