반응형

많은 앱들이 여러가지 이유로 single activity application 을 지향하고 있습니다. 따라서 Fragment 로 UI 를 구성하는 경우가 굉장히 많은데요. 이때 많은 개발자들이 Activity 의 Lifecycle 에 대해서는 잘 알고 있지만 Fragment 의 생명주기에 대해서는 정확하게 모르는 경우가 많습니다. 따라서 이번 포스팅에서는 여러가지 실험을 통해 Fragment 의 Lifecycle 에 대해 알아보도록 하겠습니다.

 

Fragment Lifecycle

안드로이드 디벨로퍼 사이트에 설명되어 있는 프래그먼트의 생명주기는 아래와 같습니다.

 

Fragment Lifecycle

 

액티비티의 생명주기에 따른 콜백함수와 비교해봤을 때 생성 시에는 onViewCreated() - onViewStateRestored() 가 추가로 있고, 파괴(?) 시에는 onSaveInstanceState() - onDestroyView() 가 추가된 것을 확인할 수 있습니다.

 

액티비티도 마찬가지지만 기본적으로 Lifecycle 은 위에서 아래 방향으로 진행됩니다. 이를테면 Fragment 가 백스택에 최상단으로 올라왔을 경우에는 생명주기가 CREATED - STARTED - RESUMED 순으로 진행되고, 반대로 백스택에서 pop 됐을 경우에는 RESUMED - STARTED - CREATED - DESTROYED 순으로 진행됩니다.

 

그림을 잘 보시면 Fragment Lifecycle 과 View Lifecycle 이 상이한 것을 볼 수 있는데요. Fragment 의 Lifecycle 이 변화되는 순간 Fragment Callback 함수를 호출하게 되고, 해당 콜백 함수가 종료되는 시점에 View 의 Lifecycle 에 이벤트를 전달하게 됩니다.

 

디벨로퍼 사이트의 내용을 기반으로 Fragment 와 View 의 각 Lifecycle 에 대해 간단하게 정리해보도록 하겠습니다.

 

onCreate()

먼저, Fragment 만 CREATED 가 된 상황입니다.

이는 FragmentManager 에 add 됐을 때 도달하며 onCreate() 콜백함수를 호출합니다. 주의할 점은 onCreate() 이전에 onAttach() 가 먼저 호출된다는 것입니다. 생각보다 onAttach() 가 먼저 호출된다는 걸 잊어버리기 쉽기 때문에 신경써서 꼭 기억해둬야 합니다.

 

그리고 이 시점에는 아직 Fragment View 가 생성되지 않았기 때문에 Fragment 의 View 와 관련된 작업을 두기에 적절하지 않습니다.

 

onCreate() 콜백 시점에는 Bundle 타입으로 savedInstanceState 파라미터가 함께 제공되는데, 이는 onSaveInstanceState() 콜백 함수에 의에 저장된 Bundle 값입니다. 여기서 또 알아야할 부분은 savedInstanceState 파라미터는 프래그먼트가 처음 생성 됐을 때만 null 로 넘어오며, onSaveInstanceState() 함수를 재정의하지 않았더라도 그 이후 재생성부터는 non-null 값으로 넘어옵니다.

 

onCreateView(), onViewCreated()

onCreate() 이후에는 onCreateView() 와 onViewCreated() 콜백함수가 이어서 호출됩니다. onCreateView() 의 반환값으로 정상적인 Fragment View 객체를 제공했을 때만 Fragment View 의 Lifecycle 이 생성됩니다.

 

onCreateView() 를 재정의 하여 Fragment View 를 직접 생성하고 inflate 할 수 있지만, LayoutId 를 받는 Fragment 의 생성자를 사용하여 해당 리소스 아이디 값을 통해 onCreateView() 재정의 없이도 Fragment View 를 생성할 수도 있습니다.

 

onCreateView() 를 통해 반환된 View 객체는 onViewCreated() 의 파라미터로 전달되는데, 이 시점부터는 Fragment View 의 Lifecycle 이 INITIALIZED 상태로 업데이트 됐기 때문에 View 의 초기값을 설정해주거나 LiveData 옵저빙, RecyclerView 또는 ViewPager2 에 사용될 Adapter 세팅 등은 onViewCreated() 에서 해주는 것이 적절하겠습니다.

 

onViewStateRestored()

onViewStateRestored() 콜백 함수는 아마 생소하신 분들이 많을 것 같은데요.

 

onViewStateRestored() 함수는 저장해둔 모든 state 값이 Fragment 의 View 계층구조에 복원 됐을 때 호출됩니다. 따라서 여기서부터는 체크박스 위젯이 현재 체크 되어있는지 등 각 뷰의 상태값을 체크할 수 있습니다.

 

View lifecycle owner 는 이때 INITIALIZED 상태에서 CREATED 상태로 변경됐음을 알립니다.

 

onStart()

Fragment 가 사용자에게 보여질 수 있을 때 호출됩니다. 이는 주로 Fragment 가 attach 되어있는 Activity 의 onStart() 시점과 유사합니다. 이 시점부터는 Fragment 의 child FragmentManager 통해 FragmentTransaction 을 안전하게 수행할 수 있습니다.

 

역시나 Fragment 의 Lifecycle 이 STARTED 로 이동한 후에 Fragment View 의 Lifecycle 또한 STARTED 로 변환됩니다.

 

onResume()

Fragment 가 보이는 상태에서 모든 Animator 와 Transition 효과가 종료되고, 프래그먼트가 사용자와 상호작용할 수 있을 때 onResume() 콜백이 호출됩니다. onStart() 와 마찬가지로 주로 Activity 의 onResume() 시점과 유사합니다.

 

Resumed 상태가 됐다는 것은 사용자가 프래그먼트와 상호작용 하기에 적절한 상태가 됐다고 했는데, 이는 반대로 onResume() 이 호출되지 않은 시점에서는 입력을 시도하거나 포커스를 설정하는 등의 작업을 임의로 하면 안된다는 것을 의미합니다.

 

onPause()

사용자가 Fragment 를 떠나기 시작했지만 Fragment 는 여전히 visible 일 때 onPause() 가 호출됩니다.

여기서 눈 여겨 볼 점은 Fragment 와 View 의 Lifecycle 이 PAUSED 가 아닌 STARTED 가 된다는 점입니다.

엄밀히 따지면 Lifecycle 에 PAUSE 와 STOP 에 해당하는 상태가 없습니다.

 

onStop()

Fragment 가 더이상 화면에 보여지지 않게 되면 Fragment 와 View 의 Lifecycle 은 CREATED 상태가 되고, onStop() 콜백 함수가 호출되게 됩니다. 이 상태는 부모 액티비티나 프래그먼트가 중단됐을 때 뿐만 아니라, 부모 액티비티나 프래그먼트의 상태가 저장될 때도 호출됩니다. 

 

Fragment 의 onStop() 의 경우 주의해야할 점이 있는데, API 28 버전을 기점으로 onSaveInstanceState() 함수와 onStop() 함수 호출 순서가 달라졌습니다. 아래 사진에서 보시다시피 API 28 버전부터 onStop() 이 onSaveInstanceState() 함수보다 먼저 호출됨으로써 onStop() 이 FragmentTransaction 을 안전하게 수행할 수 있는 마지막 지점이 되었습니다.

 

API 28 버전부터 onStop() 과 onSaveInstanceState() 의 순서가 바뀌었다.

 

onDestroyView()

모든 exit animation 과 transition 이 완료되고, Fragment 가 화면으로부터 벗어났을 경우 Fragment View 의 Lifecycle 은 DESTROYED 가 되고 onDestroy() 가 호출됩니다.

 

이 시점부터는 getViewLifecycleOwnerLiveData() 의 리턴값으로 null 이 반환됩니다.

그리고 해당 시점에서는 가비지 컬렉터에 의해 수거될 수 있도록 Fragment View 에 대한 모든 참조가 제거되어야 합니다.

 

onDestroy()

Fragment 가 제거되거나 FragmentManager 가 destroy 됐을 경우, 프래그먼트의 Lifecycle 은 DESTROYED 상태가 되고, onDestroy() 콜백 함수가 호출됩니다. 해당 지점은 Fragment Lifecycle 의 끝을 알립니다.

그리고 onAttach() 가 onCreate() 이전에 호출 됐던 것처럼 onDetach() 또한 onDestroy() 이후에 호출됩니다.

 

실제로 어떻게 호출되는지 직접 찍어보자!

백문이 불여일견.

Fragment 의 Lifecycle 이 실제로 어떻게 찍히는지 직접 샘플을 통해 살펴보도록 하겠습니다.

 

case 1. FragmentTransaction 을 통해 Fragment 위에 Fragment 를 add 하는 경우

Lifecycle 에 따른 callback 은 위에서 살펴본 순서대로 호출되는 것을 확인할 수 있는데, FragmentTransaction 을 통해 add 하는 경우에는 아래에 깔린 FirstFragment 의 Lifecycle 은 여전히 RESUMED 상태입니다.

 

case 2. FragmentTransaction 을 통해 Fragment 를 replace 하는 경우

이번에는 FragmentTransaction 을 통해 replace 하는 경우입니다.

위 사진에서 보듯 FirstFragment 의 Lifecycle 이 onPause() - onStop() 이 호출된 이후에 SecondFragment 가 attach 되고, SecondFragment 의 onStart() 가 호출되고서야 FirstFragment 가 Destroy 되고, 그제서야 SecondFragment 의 onResume() 이 호출됩니다.

 

이 부분은 굉장히 헷갈리기 쉽기 때문에 잘 기억해두는 것이 중요하겠습니다.

 

case 3. FragmentTransaction 을 통해 Fragment 를 replace 하면서 addToBackStack() 하는 경우

이번에는 FragmentTransaction 을 통해 replace 하면서 addBackStack() 까지 실행해주는 경우입니다.

위 사진에서 보듯 이 경우에는 backstack 에 Fragment 가 남아있기 때문에 case 2 와 비교했을 때 SecondFragment 의 onStart() 이후에 FirstFragment 에서 onDestroyView() 까지만 호출되고, 그 이후에 onDestroy() 와 onDetach() 는 호출되지 않습니다.

 

이 부분 역시 굉장히 헷갈리기 쉽기 때문에 잘 기억해두는 것이 중요하겠습니다.

이런 디테일이 훗날 예상치 못한 버그를 막을 수 있습니다.

 

case 4. HOME 버튼을 통해 화면을 빠져나간 경우

이번에는 Fragment 가 화면에 보여진 이후 홈 버튼을 통해 화면을 벗어났을 경우입니다.

위 사진에서 보시다시피 onResume() 이후에 홈 버튼을 누르면 onPause() - onStop() - onSaveInstanceState() 까지만 호출되고, onDestroyView() 부터 그 이후의 라이프 사이클 콜백은 호출되지 않습니다.

단, 이때 알아두셔야 하는 것은 onStop() 상태에 있는 동안 메모리 부족 상황 등의 경우, 시스템에 의해 Destroy 될 수 있습니다.

 

그리고 위 사진은 API 29 레벨에서 테스트 했기 때문에 onStop() 콜백이 onSaveInstanceState() 이전에 호출 되었습니다.

 


위 예제 코드는 Github 에서 확인하실 수 있습니다.

반응형
반응형