Fragment Lifecycle는 Android가 Fragment의 전체 수명 동안 호출하는 콜백 메서드의 엄격하게 정의된 시퀀스입니다: 생성(onAttach)부터 완전한 제거(onDetach)까지. Fragment는 Activity보다 더 복잡한 생명주기를 가지며 11개의 상태와 7개의 주요 콜백을 포함합니다. Fragment Lifecycle은 FragmentManager를 통해 관리되며 이를 포함하는 Activity의 생명주기와 밀접하게 연결됩니다. Google에 따르면 Fragment는 API Level 21+에서 실행되는 Android 애플리케이션의 74%에서 사용되며, 전문적인 Android 개발을 위해 Fragment Lifecycle 이해가 필수적입니다. Fragment Lifecycle에 대한 Android 문서는 모든 상태와 호출 보증을 설명합니다.
핵심 사항
Fragment Lifecycle는 각 Fragment 인스턴스가 생성부터 소멸까지 거치는 상호 연결된 상태 및 메서드의 집합입니다. Activity와 달리 Fragment의 생명주기는 두 가지 컨텍스트에 연결됩니다: Fragment 자체(onAttach에서 onDetach까지 유지)와 그 View(onCreateView에서 onDestroyView까지 유지)입니다. 이 분리는 Fragment의 주요 기능으로, Fragment 자체를 소멸시키지 않고 화면 회전 시 View 소멸에서 살아남을 수 있게 합니다.
Fragment 콜백의 전체 시퀀스:
Google에 따르면 최신 애플리케이션의 평균 프래그먼트는 사용자 세션당 3~5회 전체 주기를 거칩니다(화면 회전 및 탐색으로 인해). 모든 단계의 올바른 처리는 UI 안정성의 기초입니다.
FragmentManager는 5가지 주요 상태를 통해 Fragment를 관리합니다. 이는 Fragment.State 클래스에 정의되어 있습니다. 각 상태는 실행된 콜백의 특정 집합에 해당합니다.
| 상태 | 의미 | 실행된 콜백 |
|---|---|---|
| INITIALIZED | Fragment가 생성되었지만 View는 아직 사용할 수 없음 | onAttach, onCreate |
| CREATED | View가 생성되었지만 Fragment가 표시되지 않음 | + onCreateView, onViewCreated |
| STARTED | Fragment가 표시되지만 활성화되지 않음 | + onStart |
| RESUMED | Fragment가 활성화되어 사용자와 상호 작용 | + onResume |
| DESTROYED | Fragment가 소멸됨 | + onDestroyView, onDestroy, onDetach |
FragmentManager는 사용자 작업 및 시스템 이벤트에 따라 Fragment를 상태 간에 이동시킵니다. 컨테이너에 Fragment를 추가하면 INITIALIZED → CREATED → STARTED → RESUMED를 순차적으로 거칩니다. 제거 시 — RESUMED → STARTED → CREATED → DESTROYED입니다.
CREATED 상태는 특별합니다: View는 소멸될 수 있지만(onDestroyView 후), Fragment 자체는 CREATED 상태로 유지됩니다(onDestroyView 후, onDestroy 전). 이를 통해 FragmentManager는 View 없이 Fragment를 메모리에 유지할 수 있으며, 이는 화면 회전에서 살아남는 데 필요합니다.
Fragment Lifecycle와 Activity Lifecycle는 밀접하게 관련되어 있지만 근본적인 차이가 있습니다. Fragment는 항상 Activity 내에서 존재하며, 그 생명주기는 호스트 Activity에 의존하지만 동일하지는 않습니다.
| 측면 | Activity | Fragment |
|---|---|---|
| 콜백 수 | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| View를 위한 별도 Lifecycle | 없음 | 있음 (viewLifecycleOwner) |
| 회전에서 생존 | 아니오 (소멸됨) | 예 (ViewModel + Fragment 생존) |
| 호스트 의존성 | 아니오 | Activity Lifecycle에 의존 |
| 상태 저장 | onSaveInstanceState | onSaveInstanceState (Fragment 수준) |
| 관리 | 시스템 | FragmentManager |
주요 실용적 차이: 화면 회전 시 Activity는 완전히 소멸(onDestroy)되고 다시 생성(onCreate)됩니다. Fragment는 회전 중 onDestroyView(View 소멸) → onCreateView(View 재생성)를 거치지만 Fragment 자체와 ViewModel은 살아 있습니다. 이는 Fragment가 구성 변경에서 살아남아야 하는 UI 로직에 이상적인 컨테이너가 되게 합니다.
화면 회전 중 호출 순서: Activity.onPause → Fragment.onPause → Activity.onStop → Fragment.onStop → Activity.onDestroy → Fragment.onDestroyView → (Activity 소멸) → Activity.onCreate → Fragment.onAttach → Fragment.onCreate → Fragment.onCreateView → Fragment.onViewCreated → Activity.onStart → Fragment.onStart → Activity.onResume → Fragment.onResume.
FragmentManager는 프래그먼트를 추가, 제거, 교체하고 그 상태를 관리하는 중앙 클래스입니다. FragmentManager는 BackStack을 유지하고 트랜잭션 중 올바른 콜백 순서를 보장합니다. 각 Activity와 각 중첩된 Fragment는 자체 FragmentManager를 가지고 있습니다.
FragmentManager의 주요 작업:
BackStack은 FragmentManager의 트랜잭션 스택입니다. 시스템 뒤로 가기 버튼을 누르면 BackStack의 마지막 트랜잭션이 롤백됩니다(popBackStack()). popBackStack을 통해 제거된 Fragment가 복원됩니다. BackStack이 비어 있으면 뒤로 가기를 누르면 Activity가 종료됩니다.
Google에 따르면 Fragment 문제(중복, 빈 화면, IllegalStateException)의 78%는 FragmentManager의 잘못된 사용과 관련됩니다. 주요 규칙: 컨텍스트에 따라 commit()(비동기) 또는 commitNow()(동기)를 통해 트랜잭션을 실행합니다. commit()은 여러 트랜잭션에서 올바른 순서를 보장합니다.
Fragment는 onSaveInstanceState를 통해 자체 상태 저장 메커니즘을 지원합니다. 이는 Activity와 독립적으로 작동합니다. Fragment는 Bundle에 상태를 저장하며, 이 Bundle은 복원 시 onCreate와 onCreateView에 전달됩니다.
Fragment가 상태를 저장하는 경우:
최신 접근 방식: Fragment 상태 저장을 위해 ViewModel에서 SavedStateHandle을 사용하세요. SavedStateHandle은 수동 onSaveInstanceState 없이 화면 회전 및 프로세스 종료 시 데이터를 자동으로 저장하고 복원합니다. Google은 Fragment에서 UI 상태 저장의 선호 방법으로 SavedStateHandle을 권장합니다.
setRetainInstance(Fragment 1.3부터 사용 중단): 이전에는 화면 회전 시 setRetainInstance(true)를 통해 Fragment를 유지할 수 있었습니다. 이 접근 방식은 ViewModel + SavedStateHandle로 대체되었으며, 더 안정적으로 작동하고 특별한 구성이 필요하지 않습니다.
viewLifecycleOwner는 Fragment View에 연결된 Lifecycle입니다(onCreateView부터 onDestroyView까지). 이것은 근본적으로 중요한 개념입니다: viewLifecycleOwner를 통해 이루어진 LiveData/Flow 구독은 View가 소멸(onDestroyView)될 때 자동으로 취소되지만 Fragment 자체에는 영향을 미치지 않습니다.
viewLifecycleOwner와 Fragment lifecycle의 차이:
이것이 중요한 이유: Fragment lifecycle(this)을 통해 LiveData를 구독하면 onDestroyView 후에도 구독이 활성 상태로 유지되어 LiveData가 null View를 업데이트하려고 시도하여 NPE가 발생합니다. viewLifecycleOwner를 통한 구독은 onDestroyView 후에 UI 업데이트가 발생하지 않음을 보장합니다.
규칙: Fragment에서 LiveData, Flow 및 UI 관련 코루틴 구독에는 항상 viewLifecycleOwner를 사용하세요. ViewModel 코루틴에는 viewModelScope를 사용하세요 — 이는 Fragment가 아닌 ViewModel에 연결됩니다.
올바른 UI 초기화 및 viewLifecycleOwner를 통한 LiveData 구독을 보여줍니다.
class UserListFragment : Fragment() {
private val viewModel: UserListViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_user_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val button: Button = view.findViewById(R.id.load_button)
button.setOnClickListener { viewModel.loadUsers() }
viewModel.users.observe(viewLifecycleOwner) { users ->
Log.d("UserListFragment", "목록 업데이트 중: ${users.size} 사용자")
}
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("UserListFragment", "onDestroyView: View 소멸됨")
}
}
Fragment는 onCreateView에서 레이아웃을 인플레이트하고, onViewCreated에서 UI를 구성하고 LiveData를 구독합니다. viewLifecycleOwner를 통한 구독은 메모리 누수를 방지하기 위한 필수 요구 사항입니다. onDestroyView는 View 소멸을 로깅합니다 — Fragment가 화면 회전에서 살아남는다는 확인입니다.
Activity에서 FragmentManager를 통한 Fragment 추가, BackStack을 사용한 교체 및 복원을 보여줍니다.
class HostActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, HomeFragment())
.addToBackStack(null)
.commit()
}
}
fun openDetail(userId: String) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DetailFragment.newInstance(userId))
.addToBackStack(null)
.commit()
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack()
} else {
super.onBackPressed()
}
}
}
class DetailFragment : Fragment() {
companion object {
fun newInstance(userId: String): DetailFragment {
return DetailFragment().apply {
arguments = Bundle().apply { putString("user_id", userId) }
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userId = arguments?.getString("user_id")
Log.d("DetailFragment", "사용자 세부 정보 로드 중: $userId")
}
}
Activity는 supportFragmentManager를 사용하여 프래그먼트를 관리합니다. BackStack과 함께 add() 트랜잭션은 뒤로 가기 버튼을 누를 때 HomeFragment가 복원되도록 보장합니다. openDetail()은 현재 Fragment를 인수와 함께 DetailFragment로 교체합니다. savedInstanceState == null 확인은 화면 회전 시 프래그먼트 중복을 방지합니다.
반응형 UI 업데이트를 위한 viewLifecycleOwner와 함께 Fragment에서 Flow 및 StateFlow 사용.
class SearchFragment : Fragment() {
private val viewModel: SearchViewModel by viewModels()
private var binding: FragmentSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSearchBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.searchButton?.setOnClickListener {
viewModel.search(binding?.queryInput?.text.toString())
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.searchResults.collectLatest { results ->
Log.d("SearchFragment", "검색 결과: ${results.size}")
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
Fragment는 View 액세스에 View Binding을 사용합니다. viewLifecycleOwner.lifecycleScope.launch 코루틴은 View가 소멸될 때 자동으로 취소됩니다. 메모리 누수를 방지하기 위해 onDestroyView에서 binding이 null로 설정됩니다. StateFlow는 View 재생성 시 데이터의 최신성을 보장합니다.
자주 묻는 질문
onCreateView는 Fragment의 루트 View를 생성하고 반환합니다. onViewCreated는 View 생성 직후 호출되어 View가 완전히 초기화되고 구성(findViewById, 구독)할 준비가 되었음을 보장합니다. Google은 onCreateView에서 레이아웃만 인플레이트하고 모든 UI 구성은 onViewCreated에서 수행할 것을 권장합니다.
onDestroy — Fragment가 객체로 소멸됩니다(ViewModel이 정리되고 코루틴이 취소됩니다). onDetach는 마지막 콜백으로, 이후 Fragment가 Activity에서 분리됩니다. 실질적으로 모든 리소스는 onDestroyView(View)와 onDestroy(Fragment)에서 해제되어야 합니다. onDetach는 Activity에 대한 참조를 정리하는 데 사용됩니다.
Fragment가 BackStack 보존과 함께 트랜잭션을 통해 FragmentManager에 추가되지 않았거나 Activity가 onCreate에서 FragmentManager를 복원하지 않는 경우 Fragment가 사라집니다. 해결책: savedInstanceState == null 확인과 함께 onCreate에서 supportFragmentManager.beginTransaction().add()를 통해 프로그래밍 방식으로 Fragment를 추가하세요.
아니요. Fragment는 항상 FragmentManager를 통해 Activity에 연결됩니다. 화면 회전 시에도 Activity가 다시 생성되고 Fragment가 새 Activity에 다시 연결됩니다. Activity 외부에서 Fragment를 생성하는 것은 불가능합니다 — Fragment 생성자는 시스템 복원을 위해 빈 생성자가 필요합니다.
중첩 프래그먼트(nested fragments)는 다른 Fragment 내의 Fragments입니다. 복잡한 화면(탭 패널, 탭이 있는 패널, 마스터-디테일)을 구축하는 데 사용됩니다. 중첩 프래그먼트는 하위 FragmentManager(childFragmentManager)에 의해 관리됩니다. Google은 성능 문제를 피하기 위해 2단계 이상의 중첩을 권장하지 않습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.