onDestroy — Android에서 Activity와 Fragment의 생명주기 최종 메서드로, 컴포넌트가 완전히 소멸되기 전에 호출됩니다. onDestroy는 Activity 또는 Fragment가 작업을 종료하고 있음을 알립니다: 모든 리소스를 해제하고, 중첩된 프래그먼트를 소멸시키고, ViewModel을 정리해야 합니다. Google에 따르면, onDestroy는 Activity 종료의 100% 경우에 호출되지만, 프로세스 사망(process death) 시 시스템이 onDestroy 호출을 완전히 건너뛸 수 있습니다. onDestroy에 대한 Android 문서는 이 메서드가 비정상 종료 시 호출을 보장하지 않음을 강조합니다.
핵심 사항
onDestroy — Android가 Activity 또는 Fragment를 완전히 소멸시키기 전에 호출하는 콜백 메서드입니다. 이는 개발자가 리소스를 해제하고, 백그라운드 작업을 취소하고, 데이터 작업을 완료할 수 있는 마지막 기회입니다. onDestroy 실행 후, Activity/Fragment 인스턴스는 가비지 컬렉션(GC) 대상으로 표시되며 더 이상 사용할 수 없습니다.
onDestroy 호출 이유:
Google Android Vitals 통계(2025)에 따르면, 모든 Activity 소멸 사례의 약 12%가 화면 회전으로 인해, 65%가 finish()로 인해, 23%가 구성 변경으로 인해 발생합니다. onDestroy가 건너뛰어지는 프로세스 사망의 비율은 RAM이 적은 기기(4GB 미만)에 따라 약 5–8%입니다.
onDestroy는 대부분의 표준 시나리오에서 호출됩니다, 그러나 개발자가 고려해야 할 중요한 예외가 있습니다. onDestroy 호출의 보장을 이해하는 것은 애플리케이션 아키텍처에 중요하며, 특히 데이터 저장 및 WorkManager 작업 취소에 있어서 중요합니다.
onDestroy가 호출되는 경우:
onDestroy가 호출되지 않는 경우:
onDestroy 호출 보장 부족으로 인해 Google은 다음을 권장합니다: 중요 데이터 저장을 위해 onDestroy에 절대 의존하지 마십시오. onSaveInstanceState(), WorkManager 또는 자동 저장 기능이 있는 Room을 사용하세요. onDestroy는 리소스 해제를 위한 것이며, 지속성(persistence)을 위한 것이 아닙니다.
onDestroy는 Activity와 Fragment 모두에 존재합니다, 그러나 다른 계약을 가지고 있습니다. Fragment의 생명주기는 더 상세합니다: onDestroy 외에도 onDestroyView(View 계층 구조 소멸)와 onDetach(Activity에서 분리)가 있습니다.
| 컴포넌트 | 소멸 메서드 | 순서 | ViewModel 생존 |
|---|---|---|---|
| Activity | onDestroy | onPause → onStop → onDestroy | 아니요(ViewModelStore가 저장되지 않은 경우에만) |
| Fragment | onDestroyView, onDestroy, onDetach | onPause → onStop → onDestroyView → onDestroy → onDetach | 예(Fragment가 제거되지 않은 경우) |
주요 차이점: Fragment의 View는 Fragment 자체보다 더 자주 다시 생성됩니다. 화면 회전 중에 Fragment는 onDestroyView(View 소멸)를 거치지만, Fragment 자체와 ViewModel은 살아 있습니다. onDestroyView는 메모리 누수를 방지하기 위해 View 참조를 정리하는 올바른 장소입니다. Fragment의 onDestroy는 Activity의 onDestroy와 유사하며, Fragment가 완전히 제거될 때 호출됩니다.
하위 프래그먼트는 상위 Fragment의 onDestroy 전에 소멸됩니다. Activity에서 하위 프래그먼트는 상위 Activity의 onDestroy가 호출될 때 onDestroy를 받습니다. 순서가 보장됩니다: 프래그먼트는 포함하는 Activity보다 먼저 종료됩니다.
onDestroy는 Activity 또는 Fragment보다 오래 살아서는 안 되는 모든 리소스를 해제하기 위한 것입니다. 돌아올 때까지 리소스를 해제하는 onStop과 달리, onDestroy는 최종 정리를 수행합니다.
onDestroy에서 필수 작업 체크리스트:
onDestroy에서 하지 말아야 할 일: onDestroy에서 데이터를 저장하지 마세요 — onPause 또는 onSaveInstanceState를 사용하세요. 새 Service 또는 WorkManager 작업을 시작하지 마세요 — Activity가 소멸되고 결과를 추적할 수 없습니다. UI 업데이트를 시도하지 마세요 — View 계층 구조가 이미 소멸되었거나 소멸 과정에 있습니다. findViewById()를 호출하면 null이 반환됩니다.
ViewModel은 화면 회전 중에 Activity의 onDestroy에서 살아남도록 설계되었습니다, 그러나 finish() 시에는 Activity와 함께 소멸됩니다. 이 비대칭적 동작은 개발자들 사이에서 혼란의 주요 원인입니다.
화면 회전 중:
finish() 중(사용자가 “뒤로”를 누름):
따라서 onDestroy에서 viewModelScope를 취소할 필요가 없습니다 — ViewModel이 자체적으로 수행합니다. lifecycleScope(ViewModel이 아닌 Activity에 바인딩됨)를 사용하는 경우, onDestroy에서 lifecycleScope.cancel()을 통해 취소하거나 Job을 수동으로 관리하세요.
Activity에서 올바른 lifecycleScope 관리를 보여줍니다: 네트워크 상태를 모니터링하기 위해 코루틴이 시작되고 onDestroy에서 취소됩니다.
class NetworkMonitorActivity : AppCompatActivity() {
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
Log.d("NetworkMonitor", "네트워크 사용 가능")
}
override fun onLost(network: Network) {
Log.d("NetworkMonitor", "네트워크 연결 끊김")
}
}
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", "네트워크 모니터링 시작됨")
}
}
override fun onDestroy() {
super.onDestroy()
val connectivityManager = getSystemService(ConnectivityManager::class.java)
connectivityManager.unregisterNetworkCallback(networkCallback)
Log.d("NetworkMonitor", "onDestroy: 콜백이 취소됨")
}
}
onDestroy에서 네트워크 콜백 등록이 취소됩니다. lifecycleScope는 생명주기가 소멸될 때 자동으로 취소됩니다 — 별도의 코루틴 취소가 필요하지 않습니다. 네트워크 콜백은 반드시 등록 취소해야 합니다. 그렇지 않으면 Activity가 소멸된 후에도 시스템에 남아 있습니다.
Fragment는 onDestroyView에서 View 참조를 올바르게 정리하여 클로저로 인한 메모리 누수를 방지합니다.
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가 완전히 소멸됨")
}
}
onDestroyView에서 View 참조가 null로 설정됩니다 — 이는 imageLoader의 클로저가 avatarView에 대한 참조를 보유하는 경우 메모리 누수를 방지합니다. Fragment 자체와 ViewModel은 onDestroy까지 살아 있습니다. imageLoader.cancel()은 Fragment가 화면을 떠날 때 로딩을 취소합니다.
isFinishing()을 사용하면 Activity가 사용자 명령으로 종료되는지 아니면 재생성을 위해 종료되는지 구별할 수 있습니다.
class AnalyticsActivity : AppCompatActivity() {
private val analytics = Analytics()
override fun onDestroy() {
if (isFinishing) {
Log.d("AnalyticsActivity", "finish()로 Activity 종료 — 분석 전송 중")
analytics.sendSessionEnd()
} else {
Log.d("AnalyticsActivity", "Activity 재생성 중(회전/구성) — 분석을 전송하지 않음")
}
super.onDestroy()
}
}
isFinishing() 확인은 분석, 로깅 및 세션 데이터 정리를 위한 중요한 패턴입니다. 회전 중에는 세션 종료 이벤트를 보내면 안 됩니다 — 사용자가 여전히 애플리케이션으로 작업하고 있습니다. Google Analytics에 따르면, 잘못된 isFinishing() 확인은 40%의 잘못된 세션 이벤트 원인입니다.
자주 묻는 질문
네, 그럴 수 있습니다 — 시스템에 의한 프로세스 사망, 사용자에 의한 강제 중지 또는 비정상 종료 시. Google에 따르면, 약 5–8%의 Activity 종료가 onDestroy 호출 없이 발생합니다. 개발자는 중요 데이터 저장을 위해 onDestroy에 의존해서는 안 됩니다 — onPause 또는 onSaveInstanceState를 사용하세요.
finish() — Activity 소멸을 시작하는 호출입니다. onDestroy — finish() 실행 중에 호출되는 콜백입니다. 정상 종료 시 onDestroy가 호출되려면 finish()가 필요합니다. finish()는 시스템이나 개발자에 의해 호출될 수 있으며, onDestroy는 시스템 콜백일 뿐입니다.
네, 반드시 Activity와 Fragment 모두에서. super.onDestroy()는 ChildFragmentManager, LoaderManager 및 기타 시스템 컴포넌트의 적절한 정리를 보장합니다. super.onDestroy()를 건너뛰면 메모리 누수 및 프래그먼트 복원 버그가 발생합니다.
onCleared()는 Activity 또는 Fragment의 onDestroy 후에 호출됩니다, ViewModel이 더 이상 필요하지 않을 때입니다. 화면 회전 중에는 onCleared()가 호출되지 않습니다 — ViewModel이 onDestroy에서 살아남습니다. 순서: Activity/Fragment의 onDestroy → (ViewModelStore 정리) → onCleared().
기술적으로는 가능하지만 권장되지 않습니다. Activity는 onDestroy 직후에 소멸되며, 시작된 Service는 통제 불능 상태가 됩니다. 백그라운드 작업에는 지연이 있는 WorkManager를 사용하세요: WorkManager는 Activity 종료 후에도 실행을 보장하고 프로세스 사망에서 살아남습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.