안드로이드 시스템·Compose 용어 사전
생명주기viewLifecycleOwner · onDestroyView

Fragment 생명주기

Fragment 자신과 그 뷰의 생명주기가 다르다. 뷰 작업은 viewLifecycleOwner에 묶는다.

Fragment는 생명주기가 두 개다 — Fragment 자신과, 그 Fragment가 가진 뷰.

왜 둘인가

A 화면(Fragment) 에서 B 로 이동하면

  • A 의 뷰는 파괴된다 (메모리 절약)
  • A 의 Fragment 객체는 백스택에 살아 있다

뒤로 가기로 A 로 돌아오면

  • 같은 Fragment 에 뷰만 새로 만든다
  • onDestroyView → (백스택) → onCreateView 재호출
Fragment 생명주기   onCreate ─────────────────────── onDestroy
뷰 생명주기            onCreateView ─ onDestroyView ─ onCreateView ─ ...
                       (여러 번 반복될 수 있다)

여기서 사고가 난다

// ❌ Fragment 자신(this)으로 관찰한다
viewModel.uiState.observe(this) { render(it) }

뷰는 파괴됐는데 Fragment 는 살아 있으니 콜백이 계속 불린다

  • 이미 파괴된 옛 binding 을 건드려 크래시
  • 또는 옛 뷰를 붙잡아 메모리 누수

백스택에 5개 화면이 쌓여 있으면 관찰자가 5개 동시에 살아 있다

// ✅ 뷰 생명주기에 묶는다
override fun onViewCreated(view: View, s: Bundle?) {
    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.uiState.collect { render(it) }
        }
    }
}

viewLifecycleOwner.lifecycleScope로 시작한 코루틴은 onDestroyView에서 자동 취소된다 — 파괴된 뷰를 건드릴 일이 없다.

binding 누수

private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!

override fun onCreateView(...): View {
    _binding = FragmentHomeBinding.inflate(inflater, container, false)
    return binding.root
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null                 // ★ 이걸 빠뜨리면 뷰 트리 전체가 누수된다
}

면접 함정

  • "Fragment도 Activity처럼 생명주기가 하나" → 두 개이고, 뷰 쪽이 여러 번 반복된다.
  • "단일 Activity 아키텍처면 이 문제가 없다" → Navigation의 목적지도 Fragment면 동일하다. Compose 목적지라면 사라진다.

콜백 전체 순서

onAttach → onCreate → onCreateView → onViewCreated → onStart → onResume
  ↓
onPause → onStop → onDestroyView → onDestroy → onDetach

백스택으로 뒤로 갈 때는 onDestroyView 까지만 간다
  → 돌아오면 onCreateView 부터 다시 시작한다

Fragment 간 결과 전달

// setTargetFragment 는 폐기됐다
setFragmentResult("pick", bundleOf("id" to 42L))

setFragmentResultListener("pick") { _, bundle ->
    val id = bundle.getLong("id")
}

FragmentManager 가 중개하므로 두 Fragment 가 서로를 참조하지 않는다 결과는 수신 Fragment 가 STARTED 가 될 때 전달된다 (생명주기 안전)

재생성 시 인자만 살아남는다

// ✅ arguments 는 저장되고 복원된다
MyFragment().apply { arguments = bundleOf("id" to 42L) }

// ❌ 생성자나 세터로 넘긴 값은 재생성 시 사라진다
MyFragment(id = 42L)     // 시스템이 기본 생성자로 다시 만든다 → 크래시 또는 유실

시스템이 Fragment 를 복원할 때는 항상 '빈 생성자' 로 만들고 arguments 를 다시 넣는다

  • 인자는 반드시 arguments 로

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 생명주기 — Activity·Fragment·생명주기 인지