onDestroy: co to jest, zakończenie działania Activity w Android

Autor: IT Sectr Opublikowano: 2026-03-04 Czas czytania: 8 min

onDestroy — finalna metoda cyklu życia Activity i Fragment w Android, wywoływana przed całkowitym zniszczeniem komponentu. onDestroy sygnalizuje, że Activity lub Fragment kończy swoją pracę: wszystkie zasoby muszą zostać zwolnione, zagnieżdżone fragmenty — zniszczone, ViewModel — wyczyszczony. Według danych Google, onDestroy jest wywoływane w 100% przypadków zakończenia Activity, ale przy zabiciu procesu (process death) system może pominąć wywołanie onDestroy całkowicie. Dokumentacja Android na temat onDestroy podkreśla, że ta metoda nie gwarantuje wywołania przy awaryjnym zakończeniu.

Najważniejsze

  • onDestroy — ostatnie wywołanie przed zniszczeniem Activity lub Fragment, przeznaczone do finalnego czyszczenia zasobów.
  • Wywołanie onDestroy nie jest gwarantowane przy zabiciu procesu przez system (process death) — nie polegaj na nim przy zapisywaniu krytycznych danych.
  • W onDestroy należy anulować zadania w tle, zamknąć socket'y i bazy danych, wyczyścić ViewModelStore.
  • Różnica od onStop: onStop — utrata widoczności (Activity żyje w pamięci), onDestroy — całkowite zniszczenie.
  • isFinishing() w onDestroy pokazuje, czy Activity kończy się na polecenie użytkownika (finish()) czy decyzją systemu.

onDestroy: co to jest w Android?

onDestroy — metoda-wywołanie zwrotne, którą Android wywołuje przed ostatecznym zniszczeniem Activity lub Fragment. To ostatnia szansa dla programisty na zwolnienie zasobów, anulowanie operacji w tle i zakończenie pracy z danymi. Po wykonaniu onDestroy instancja Activity/Fragment jest oznaczana do garbage collectora (GC) i nie może być już użyta.

Przyczyny wywołania onDestroy:

  • Jawne wywołanie finish() — użytkownik nacisnął „Wstecz” lub programista wywołał finishActivity().
  • Obrót ekranu — Activity jest niszczone i tworzone od nowa z nową konfiguracją.
  • Zmiana konfiguracji — klawiatura, zmiana języka, zmiana rozmiaru ekranu (multi-window).
  • Decyzja systemu — Android zabija Activity w celu zwolnienia zasobów (ale onDestroy może nie być wywołane).

Według statystyk Google Android Vitals (2025), około 12% wszystkich przypadków niszczenia Activity wynika z obrotu ekranu, 65% — z finish() i 23% — ze zmiany konfiguracji. Odsetek zabić procesów z pominięciem onDestroy wynosi około 5–8% w zależności od urządzeń z małą ilością RAM (poniżej 4 GB).

Kiedy onDestroy jest wywoływane — a kiedy nie

onDestroy jest wywoływane w większości standardowych scenariuszy, ale są ważne wyjątki, które programista musi uwzględnić. Zrozumienie gwarancji wywołania onDestroy jest krytyczne dla architektury aplikacji, zwłaszcza dla zapisywania danych i anulowania zadań WorkManager.

Kiedy onDestroy jest wywoływane:

  • Użytkownik naciska przycisk „Wstecz” — Activity.finish() → onPause → onStop → onDestroy.
  • Obrót ekranu — Activity jest niszczone (onPause → onStop → onDestroy), następnie tworzone od nowa.
  • Zmiana konfiguracji — ustawienie systemowe wymagające odtworzenia Activity.
  • Wywołanie finishAffinity() — zakończenie wszystkich Activity w stosie.
  • Usunięcie Fragment z FragmentManager — Fragment otrzymuje onPause → onStop → onDestroyView → onDestroy → onDetach.

Kiedy onDestroy NIE jest wywoływane:

  • Zabicie procesu przez system (process death) — Android zabija cały proces aplikacji przy braku pamięci. Activity nie otrzymuje onDestroy, ponieważ proces jest kończony na poziomie jądra Linux.
  • Awaryjne zakończenie — nieprzechwycony wyjątek w głównym wątku zabija aplikację bez wywołania onDestroy.
  • Force Stop — użytkownik wymusza zatrzymanie aplikacji w ustawieniach.

Z powodu braku gwarancji wywołania onDestroy Google zaleca: nigdy nie polegaj na onDestroy przy zapisywaniu krytycznych danych. Używaj onSaveInstanceState(), WorkManager lub Room z automatycznym zapisywaniem. onDestroy — dla zwalniania zasobów, ale nie dla trwałości danych.

onDestroy w Activity i Fragment: podobieństwa i różnice

onDestroy istnieje zarówno dla Activity, jak i dla Fragment, ale z różnymi kontraktami. U Fragment cykl życia jest bardziej szczegółowy: oprócz onDestroy istnieje onDestroyView (zniszczenie hierarchii View) i onDetach (odłączenie od Activity).

KomponentMetody niszczeniaKolejnośćViewModel przetrwa
ActivityonDestroyonPause → onStop → onDestroyNie (tylko jeśli ViewModelStore nie został zachowany)
FragmentonDestroyView, onDestroy, onDetachonPause → onStop → onDestroyView → onDestroy → onDetachTak, jeśli Fragment nie został usunięty

Kluczowa różnica: u Fragment View jest odtwarzane częściej niż sam Fragment. Przy obrocie ekranu Fragment przechodzi przez onDestroyView (zniszczenie View), ale sam Fragment i jego ViewModel pozostają żywe. onDestroyView — odpowiednie miejsce do czyszczenia referencji do View, aby uniknąć wycieków pamięci. onDestroy Fragment — odpowiednik onDestroy Activity, wywoływany przy całkowitym usunięciu Fragment.

Zagnieżdżone fragmenty (child fragments) są niszczone przed onDestroy rodzica Fragment. W Activity fragmenty potomne otrzymują onDestroy przy wywołaniu onDestroy rodzica Activity. Kolejność jest gwarantowana: fragmenty kończą się wcześniej niż zawierające je Activity.

Co robić w onDestroy: lista kontrolna czyszczenia

onDestroy jest przeznaczone do zwalniania wszystkich zasobów, które nie powinny żyć dłużej niż Activity lub Fragment. W przeciwieństwie do onStop, który zwalnia zasoby do czasu powrotu, onDestroy wykonuje końcowe czyszczenie.

Lista kontrolna obowiązkowych działań w onDestroy:

  • Anulowanie coroutine i Flow — anuluj job'y, które nie są powiązane z viewModelScope. viewModelScope jest anulowany automatycznie, ale lifecycleScope jest powiązany z cyklem życia Activity.
  • Zamykanie socket'ów i kanałów — WebSocket (OkHttp), BluetoothSocket, ServerSocket. Trzymanie ich otwartych po zniszczeniu to wyciek zasobów systemowych.
  • Zamykanie plików i strumieni — FileInputStream, FileOutputStream, Cursor. Cursor może spowodować ANR na ContentProvider, jeśli nie zostanie zamknięty.
  • Wypisanie się z ContentObserver — jeśli Activity śledzi zmiany treści (kontakty, mediateka).
  • Wypisanie się z BroadcastReceiver — dynamicznie zarejestrowane receiver'y muszą być anulowane.
  • Zamykanie bazy danych — Room zamyka połączenie automatycznie przy zniszczeniu Application, ale direct SQLiteDatabase wymaga ręcznego close().

Czego NIE robić w onDestroy: Nie zapisuj danych w onDestroy — używaj onPause lub onSaveInstanceState. Nie uruchamiaj nowych Service lub WorkManager — Activity zostanie zniszczone i nie będziesz mógł śledzić wyniku. Nie próbuj aktualizować UI — hierarchia View jest już zniszczona lub w trakcie niszczenia; wywołanie findViewById() zwróci null.

onDestroy i ViewModel: współpraca

ViewModel jest zaprojektowany tak, aby przetrwać onDestroy Activity przy obrocie ekranu, ale być zniszczony razem z Activity przy finish(). To asymetryczne zachowanie — główna przyczyna nieporozumień u programistów.

Przy obrocie ekranu:

  • Activity: onPause → onStop → onDestroy (Activity zniszczone).
  • ViewModel: NIE zniszczony — ViewModelStore jest zachowywany i przekazywany nowemu Activity.
  • Nowe Activity: onCreate → onStart → onResume, otrzymuje ten sam ViewModel.

Przy finish() (użytkownik nacisnął „Wstecz”):

  • Activity: onPause → onStop → onDestroy.
  • ViewModel: onCleared() — wywoływane po onDestroy Activity.
  • Wszystkie coroutine viewModelScope są anulowane automatycznie.

W związku z tym anulowanie viewModelScope w onDestroy nie jest potrzebne — ViewModel zrobi to samodzielnie. Jeśli używasz lifecycleScope (powiązanego z Activity, a nie z ViewModel), anuluj go w onDestroy przez lifecycleScope.cancel() lub zarządzaj Job ręcznie.

Przykłady kodu z onDestroy w Kotlin

Przykład 1: onDestroy Activity z anulowaniem coroutine lifecycleScope

Demonstruje poprawne zarządzanie lifecycleScope w Activity: coroutine jest uruchamiana do śledzenia stanu sieci i anulowana w onDestroy.

kotlin
class NetworkMonitorActivity : AppCompatActivity() {
    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            Log.d("NetworkMonitor", "Sieć dostępna")
        }
        override fun onLost(network: Network) {
            Log.d("NetworkMonitor", "Sieć utracona")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_network)
        val connectivityManager = getSystemService(ConnectivityManager::class.java)
        connectivityManager.registerDefaultNetworkCallback(networkCallback)
        lifecycleScope.launch {
            Log.d("NetworkMonitor", "Monitorowanie sieci uruchomione")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        val connectivityManager = getSystemService(ConnectivityManager::class.java)
        connectivityManager.unregisterNetworkCallback(networkCallback)
        Log.d("NetworkMonitor", "onDestroy: callback anulowany")
    }
}

W onDestroy anulowana jest rejestracja callbacku sieciowego. lifecycleScope jest anulowany automatycznie przy zniszczeniu cyklu życia — oddzielne anulowanie coroutine nie jest wymagane. Callback sieciowy musi zostać wypisany, w przeciwnym razie będzie wisiał w systemie nawet po zniszczeniu Activity.

Przykład 2: onDestroy Fragment z czyszczeniem referencji View

Fragment poprawnie czyści referencje do View w onDestroyView, zapobiegając wyciekom pamięci spowodowanym przez domknięcia.

kotlin
class ProfileFragment : Fragment() {
    private var avatarView: ImageView? = null
    private var progressBar: ProgressBar? = null
    private val imageLoader = ImageLoader()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        avatarView = view.findViewById(R.id.avatar)
        progressBar = view.findViewById(R.id.progress)
        loadProfile()
    }

    private fun loadProfile() {
        viewLifecycleOwner.lifecycleScope.launch {
            try {
                progressBar?.visibility = View.VISIBLE
                val bitmap = imageLoader.load("https://example.com/avatar.png")
                avatarView?.setImageBitmap(bitmap)
            } finally {
                progressBar?.visibility = View.GONE
            }
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        avatarView = null
        progressBar = null
        imageLoader.cancel()
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("ProfileFragment", "onDestroy: Fragment całkowicie zniszczony")
    }
}

W onDestroyView referencje do View są zerowane — zapobiega to wyciekowi pamięci, jeśli domknięcie w imageLoader przechowuje referencję do avatarView. Sam Fragment i jego ViewModel pozostają żywe do onDestroy. imageLoader.cancel() anuluje ładowanie, jeśli Fragment opuszcza ekran.

Przykład 3: Sprawdzanie isFinishing w onDestroy

Użycie isFinishing() pozwala odróżnić, czy Activity kończy się na polecenie użytkownika czy w celu odtworzenia.

kotlin
class AnalyticsActivity : AppCompatActivity() {
    private val analytics = Analytics()

    override fun onDestroy() {
        if (isFinishing) {
            Log.d("AnalyticsActivity", "Activity kończy się finish() — wysyłamy analitykę")
            analytics.sendSessionEnd()
        } else {
            Log.d("AnalyticsActivity", "Activity jest odtwarzane (obrót/konfiguracja) — nie wysyłamy analityki")
        }
        super.onDestroy()
    }
}

Sprawdzanie isFinishing() — ważny wzorzec dla analityki, logowania i czyszczenia danych sesyjnych. Przy obrocie nie trzeba wysyłać zdarzeń zakończenia sesji — użytkownik wciąż pracuje z aplikacją. Według Google Analytics, nieprawidłowe sprawdzanie isFinishing() jest przyczyną 40% fałszywych zdarzeń sesyjnych.

Często zadawane pytania

Czy onDestroy może nie zostać wywołane?

Tak, może — przy zabiciu procesu przez system (process death), Force Stop przez użytkownika lub awaryjnym zakończeniu. Według danych Google, około 5–8% zakończeń Activity następuje bez wywołania onDestroy. Programista nie powinien polegać na onDestroy przy zapisywaniu krytycznych danych — używaj onPause lub onSaveInstanceState.

Czym różni się onDestroy od finish()?

finish() — wywołanie inicjujące zniszczenie Activity. onDestroy — callback, który jest wywoływany w trakcie wykonywania finish(). finish() jest wymagane do wywołania onDestroy przy standardowym zakończeniu. finish() może być wywołane przez system lub programistę, onDestroy — tylko systemowy callback.

Czy trzeba wywoływać super.onDestroy() w Fragment?

Tak, koniecznie zarówno w Activity, jak i w Fragment. super.onDestroy() zapewnia poprawne czyszczenie ChildFragmentManager, LoaderManager i innych komponentów systemowych. Pominięcie super.onDestroy() prowadzi do wycieków pamięci i błędów przy przywracaniu fragmentów.

Kiedy wywoływane jest onCleared() w ViewModel względem onDestroy?

onCleared() jest wywoływane po onDestroy Activity lub Fragment, gdy ViewModel nie jest już potrzebny. Przy obrocie ekranu onCleared() nie jest wywoływane — ViewModel przetrwa onDestroy. Kolejność: onDestroy Activity/Fragment → (ViewModelStore jest czyszczony) → onCleared().

Czy można uruchomić Service z onDestroy?

Technicznie tak, ale nie jest zalecane. Activity jest natychmiast niszczone po onDestroy, a uruchomiony Service pozostaje bez kontroli. Do zadań w tle używaj WorkManager z opóźnieniem: WorkManager gwarantuje wykonanie nawet po zakończeniu Activity i przetrwa process death.

Podsumowanie

  • onDestroy — końcowy callback cyklu życia Activity i Fragment, wywoływany przed całkowitym zniszczeniem komponentu.
  • Wywołanie onDestroy nie jest gwarantowane przy process death — około 5–8% zakończeń następuje bez niego.
  • W onDestroy należy zwolnić: callbacki sieciowe, socket'y, strumienie plików, BroadcastReceiver, ContentObserver.
  • ViewModel.onCleared() jest wywoływane po onDestroy Activity — viewModelScope jest anulowany automatycznie.
  • onDestroyView w Fragment (oddzielnie od onDestroy) — odpowiednie miejsce do zerowania referencji do View.
  • Sprawdzanie isFinishing() w onDestroy pozwala odróżnić zakończenie finish() od odtworzenia przy zmianach konfiguracji.
  • Nie polegaj na onDestroy przy zapisywaniu danych — używaj onPause lub onSaveInstanceState.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również