Android 개발에서 어려운 것 중 하나가 Lifecycle 관리이다.
Activity와 그 위에 올려져있는 Fragment 인스턴스에도 Activity와는 다른 자체 생명 주기가 있다.
또한 Fragment Lifecycle과 다른 Fragment View Lifecycle이라는 독립적인 뷰 라이프사이클도 존재한다.
Fragment는 왜 이런 view Lifecycle 를 따로 가지는 것일까? ૮꒰ ྀི . . ꒱ა ..
Fragment의 생명주기와 Fragment View Lifecycle에서 LiveData는 어떻게 다뤄야하는지까지 이야기해본다.
0. 함께 했을 때의 생명주기 흐름
Activity위에 Fragment가 Attach 되었을 때의 생명주기 흐름이다. 여러개의 Fragment를 하나의 Activity와 함께 쓸 수도, 하나의 Fragment를 여러 Activity에 재사용할 수도 있다. Fragment는 아래와 같이 Activity와 유사하게 자체적인 생명주기를 가지고 있지만 Arttach되는 Activity의 생명주기에 영향을 받는다.
Fragment의 Lifecycle에 대해 자세히 알아본다.
1. Fragment Lifecycle
Activity 생명주기와 비교해보면, onCreateView(), onViewCreated(), onViewStateRestored() 가 추가로 있고, 소멸시에는 onSaveInstanceState(), onDestroyView() 가 추가로 더 있는 것을 볼 수 있다.
기본적으로 Lifecycle은 위에서 아래 방향으로 진행되는데, 예를 들어 Fragment가 백스택에서 최상단으로 올라왔을 경우 생명주기가 CREATED - STARTED - RESUMED 순으로 진행되고, 반대로 백스택에서 pop 됐을 경우에는 RESUMED - STARTED - CREATED - DESTROYED 순으로 진행된다.
1-1) onCreate()
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [BlankFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class BlankFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
먼저 Fragment만 CREATED 된 상태이다. FragmentManager에 add 됐을 때 도달하며 onCreate() 콜백함수를 호출한다. 주의할점은 onCreate() 전에 onAttach()가 먼저 호출된다.
그리고 이 시점에는 아직 Fragment View가 생성되지 않았기 때문에 Fragment의 View와 관련된 작업을 하면 안 된다.
그렇다면 onCreate() 시점에는 어떤 일을 하는게 좋나?
onCreate() 콜백 시점에는 bundle 타입으로 savedInstanceState 파라미터가 함께 제공되는데, 이는 onSaveInstanceState() 콜백 함수에 의해 저장된 bundle 값이다. saveInstanceState 파라미터는 프래그먼트가 처음 생성 됐을 때만 null로 넘어오며, onSaveInstanceState() 함수를 재정의하지 않았더라도 그 이후 재생성부터는 non-null 값으로 넘어온다.
1-2) onCreateView(), onViewCreate()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false)
}
onCreate() 이후에는 onCreateView(), onViewCreate() 콜백함수가 이어서 호출된다.
onCreateView()의 반환값으로 정상적인 Fragment View 객체를 제공했을 때만 Fragment View의 Lifecycle이 생성된다.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
onCreateView()를 통해 반환된 View 객체는 onViewCreated() 의 파라미터로 전달되는데, 이 시점부터는 Fragment View의 Lifecycle이 INITIALZIED 상태로 업데이트 되었기 때문에 view 의 초기값을 설정해주거나 LiveData 옵저빙, RecyclerView, ViewPager2에 사용될 Adapter 세팅등은 onViewCreated()에서 해주는 것이 좋다. (하단에서 더 자세히 다루도록 한다)
1-3) onViewStateRestored()
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
}
onViewStateRestored() 함수는 저장해둔 모든 state 값이 Fragment의 View 계층구조에 복원 됐을 때 호출된다. 따라서 여기서부터는 체크 박스 위젯이 현재 체크 되어있는지 등 각 뷰의 상태값을 확인할 수 있다.
view lifecycle owner는 이 때 INITIALIZED 상태에서 CREATED 상태로 변경됐음을 알린다.
2-1) onStart()
Fragment가 사용자에게 보여질 수 있을 때 호출된다. 주로 Fragment가 attach 되어있는 Activity의 onStart() 시점과 유사하다. 이 시점부터는 Fragment의 child FragmentManager을 통해 FragmentTransaction 을 안전하게 수행할 수 있다.
Fragment의 Lifecycle이 STARTED 로 이동한 후에 Fragment View의 Lifecycle 또한 STARTED로 변환된다.
3-1) onResume()
Fragment 가 보이는 상태에서 모든 Animator 와 Transition 효과가 종료되고, 프래그먼트가 사용자와 상호작용할 수 있을 때 onResume() 콜백이 호출된다. onStart() 와 마찬가지로 주로 Activity 의 onResume() 시점과 유사하다.
Resumed 상태가 됐다는 것은 사용자가 프래그먼트와 상호작용 하기에 적절한 상태가 됐다고 했는데, 이는 반대로 onResume() 이 호출되지 않은 시점에서는 입력을 시도하거나 포커스를 설정하는 등의 작업을 임의로 하면 안된다는 것을 의미한다.
4-1) onPause()
사용자가 Fragment 를 떠나기 시작했지만 Fragment 는 여전히 visible 일 때 onPause() 가 호출된다.
여기서 눈 여겨 볼 점은 Fragment 와 View 의 Lifecycle 이 PAUSED 가 아닌 STARTED 가 된다는 점이다.
엄밀히 따지면 Lifecycle 에 PAUSE 와 STOP 에 해당하는 상태가 없다.
5-1) onStop()
Fragment 가 더이상 화면에 보여지지 않게 되면 Fragment 와 View 의 Lifecycle 은 CREATED 상태가 되고, onStop() 콜백 함수가 호출되게 된다. 이 상태는 부모 액티비티나 프래그먼트가 중단됐을 때 뿐만 아니라, 부모 액티비티나 프래그먼트의 상태가 저장될 때도 호출된다.
Fragment 의 onStop() 의 경우 주의해야할 점이 있는데, API 28 버전을 기점으로 onSaveInstanceState() 함수와 onStop() 함수 호출 순서가 달라졌다. 아래 사진에서 보시다시피 API 28 버전부터 onStop() 이 onSaveInstanceState() 함수보다 먼저 호출됨으로써 onStop() 이 FragmentTransaction 을 안전하게 수행할 수 있는 마지막 지점이 되었다.
5-2) onDestroyView(), onDestroy()
모든 exit animation 과 transition 이 완료되고, Fragment 가 화면으로부터 벗어났을 경우 Fragment View 의 Lifecycle 은 DESTROYED 가 되고 onDestroy() 가 호출된다.
이 시점부터는 getViewLifecycleOwnerLiveData() 의 리턴값으로 null 이 반환된다.
그리고 해당 시점에서는 Garbage Collector에 의해 수거될 수 있도록 Fragment View 에 대한 모든 참조가 제거되어야 한다.
Fragment 가 제거되거나 FragmentManager 가 destroy 됐을 경우, 프래그먼트의 Lifecycle 은 DESTROYED 상태가 되고, onDestroy() 콜백 함수가 호출됩니다. 해당 지점은 Fragment Lifecycle 의 끝을 알린다.
그리고 onAttach() 가 onCreate() 이전에 호출 됐던 것처럼 onDetach() 또한 onDestroy() 이후에 호출된다.
2. Fragment View Lifecycle
Fragment View LifeCycle은 LifeCycle 사용 패턴을 개선하기 위해서 도입된 LifeCycle이다.
Google Android Developer인 Ian Lake님의 수정 로그를 보자.
Add Fragment#getViewLifecycleOwner
The Fragment's View lifecycle can diverge
from the Fragment's lifecycle in cases of
detached Fragments. This can cause issues with
LiveData where old observers should be cleared
when the View is destroyed to prevent duplication
with new observers created in onCreateView/onViewCreated.
By exposing a separate LifecycleOwner specifically for
the Fragment's View, developers can use that in place
of the Fragment itself to better model the Lifecycle
they actually care about.
Also adds a getViewLifecycleOwnerLiveData() for
observing changes in the View LifecycleOwner (i.e.,
creation, destruction, and recreation).
링크: https://android.googlesource.com/platform/frameworks/support/+/eb89fcf1decf9044f53330ea4bb689d25d2328b1%5E%21/fragment/src/main/java/androidx/fragment/app/Fragment.java
Fragment는 Fragment View보다 긴 생명주기를 가지며, 일반적으로 UI를 업데이트용으로는 Fragment View Lifecycle 이 적절하다. 그리고, Fragment View Lifecycle 도입으로 LiveData observe에서 사용하는 Observer 중복 호출 문제도 해결할 수 있다. Fragment 사용 시 데이터 갱신에 대한 Lifecycle을 Fragment Lifecycle보다 Fragment View Lifecycle이 올바르다고 언급하고 있다.
- Fragment Lifecycle : onCreate ~ onDestroy
- Fragment View Lifecycle : onCreateView ~ onDestoryView
3. viewLifecycleOwner 생성과정
viewLifecycleOwner 는 Fragment 생명주기의 어느 단계에서부터 생성이 되는가?
아래는 Android의 Fragment 클래스 내부의 performCreateView 메서드 부분이다. Fragment가 화면에 보여질 때 뷰를 생성하고 초기화하는 과정이다. 뷰의 라이프사이클을 관리하는 viewLifecycleOwner가 onCreateView 단계부터 초기화됨을 알 수 있다.
void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
mChildFragmentManager.noteStateNotSaved();
mPerformedCreateView = true;
mViewLifecycleOwner = new FragmentViewLifecycleOwner(this, getViewModelStore());
mView = onCreateView(inflater, container, savedInstanceState);
if (mView != null) {
// Initialize the view lifecycle
mViewLifecycleOwner.initialize();
// Tell the fragment's new view about it before we tell anyone listening
// to mViewLifecycleOwnerLiveData and before onViewCreated, so that calls to
// ViewTree get() methods return something meaningful
ViewTreeLifecycleOwner.set(mView, mViewLifecycleOwner);
ViewTreeViewModelStoreOwner.set(mView, mViewLifecycleOwner);
ViewTreeSavedStateRegistryOwner.set(mView, mViewLifecycleOwner);
// Then inform any Observers of the new LifecycleOwner
mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner);
} else {
if (mViewLifecycleOwner.isInitialized()) {
throw new IllegalStateException("Called getViewLifecycleOwner() but "
+ "onCreateView() returned null");
}
mViewLifecycleOwner = null;
}
}
onCreateView 단계에서 viewLifecycleOwner가 어떻게 생성되는지 그 순서와 과정을 자세히 보자.
mViewLifecycleOwner = new FragmentViewLifecycleOwner(this, getViewModelStore());
mView = onCreateView(inflater, container, savedInstanceState);
viewLifecycleOwner는 onCreateView 가 호출되기전에 생성된다.
1. mViewLifecycleOwner 생성: FragmentViewLifecycleOwner 클래스의 생성자를 호출하여 새로운 뷰 라이프사이클을 관리할 객체를 생성한다.
2. onCreateView 호출: onCreateView 메서드가 호출되면서 뷰 계층 구조가 생성된다. 뷰를 인플레이션하고 초기화한 후, 해당 뷰를 반환한다.
따라서 뷰 라이프사이클 객체는 onCreateView 메서드가 호출되기 전에 생성되며, 이후에 onCreateView 내에서 생성된 뷰의 이벤트를 수신하고 라이프 사이클을 관리하게 된다. 따라서 inflation이 되지 않더라도, mViewLifecycleOwner는 생성되고 프래그먼트(this)와 뷰 모델 스토어(getViewModelStore())를 연결하여 라이프사이클 이벤트를 추적하고 관리하기 위한 기본적인 구조를 설정한다. 하지만, 소유자가 생성되었다고 하더라도 아직 초기화되지 않았으므로 라이프사이클 이벤트를 처리하거나 관찰할 준비는 되어있지 않다.
뷰가 인플레이션이 되지 않았을 경우를 보자.
if (mView != null) {
...
} else {
if (mViewLifecycleOwner.isInitialized()) {
throw new IllegalStateException("Called getViewLifecycleOwner() but "
+ "onCreateView() returned null");
}
mViewLifecycleOwner = null;
}
onView가 null이라는 것은 onCreateView에서 뷰를 인플레이션하거나 생성하지 않았음을 의미한다.
이 경우 mViewLifecycleOwner 또한 null로 설정되게 됩니다. 이는 mViewLifecycleOwner가 생성된 뷰의 라이프사이클을 관리하기 위해 사용되기 때문이다. 만약 뷰가 생성되지 않았다면, 뷰의 라이프사이클을 관리할 mViewLifecycleOwner가 존재할 이유가 없다.
뷰가 성공적으로 인플레이션이 되었을 경우를 다시 보자.
if (mView != null) {
// Initialize the view lifecycle
mViewLifecycleOwner.initialize();
// Tell the fragment's new view about it before we tell anyone listening
// to mViewLifecycleOwnerLiveData and before onViewCreated, so that calls to
// ViewTree get() methods return something meaningful
ViewTreeLifecycleOwner.set(mView, mViewLifecycleOwner);
//뷰의 라이프사이클을 뷰에 연결하여 라이프사이클 이벤트를 전달할 수 있도록 한다.
ViewTreeViewModelStoreOwner.set(mView, mViewLifecycleOwner);
//뷰의 라이프사이클과 ViewModel 스토어를 연결한다.
ViewTreeSavedStateRegistryOwner.set(mView, mViewLifecycleOwner);
//뷰의 라이프사이클과 SavedStateRegistry를 연결한다.
// Then inform any Observers of the new LifecycleOwner
mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner);
//뷰의 라이프사이클을 라이프사이클 라이브데이터에 설정하여 관찰자에게 알린다.
} else {
...
}
mViewLifecycleOwner.initialize() 에서 뷰 라이프사이클 소유자를 실제 활성화하고, 뷰의 라이프사이클을 뷰에 연결하여 라이프사이클 이벤트를 전달할 수 있도록 한다. 뷰 라이플사이클 소유자가 뷰의 라이프사이클 이벤트를 관리하고 처리할 준비가 되었음을 나타낸다. 이를 통해 뷰의 생성, 시작, 중단, 정지 및 소멸과 같은 라이플 사이클 이벤트를 관찰하고 처리할 수 있다.
그리고 mViewLifecycleOwnerLiveData 에 소유자를 할당하는데, mViewLifecycleOwnerLiveData 는 뷰 라이프사이클 소유자의 변경을 관찰하는데 사용되는 LiveData이다. 이 부분은 뷰 라이프사이클 소유자가 초기화된 후에 이를 다른 관찰자에게 알리기 위해 사용된다. 즉, 뷰 라이프사이클 소유자가 언제 생성되고 초기화되었는지에 관계없이 다른 구성 요소(뷰 모델, 액티비티 등)에서 이 변경 사항을 감지하고 응답할 수 있게 된다. 이 부분은 아래서 더 자세히 다루도록 한다.
4) LiveData 의 Observing 는 어디서?
java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
LiveData의 observing은 어디서부터 하는게 좋은가?
Fragment의 View가 생성이 되고 뷰 계층 구조에 포함된 이후인 onViewCreated에서 LiveData를 관찰하는 것이 더욱 안전할 수 있다.
뷰 계층 구조에 포함되었을 때부터 LiveData를 관찰하기 시작하는데, onCreateView는 레이아웃을 인플레이션하는 단계이므로 이 시점에서는 뷰가 아직 완전히 인플레이션되지 않았을 수 있다. onViewCreated 는 이에 대한 보다 안전한 타이밍이라고 할 수 있다.
view가 생성이 안된 경우 viewLifeCycle이 초기화 되었는지 안되었는지에 대한 상태에 따라 에러를 주거나 null로 값을 초기화시키는데, 이는 완전히 뷰 계층 구조에 포함된 후에 호출되는 onViewCreated 에서 호출되는것이 더욱 안전하다.
⚡️ Use viewLifecycleOwner as the LifecycleOwner
LiveData를 observing 하기 위해서 observe 함수의 첫 번째 파라미터를 this가 아닌 viewLifecycleOwner를 호출하도록 한다. this를 사용하면 위와 같은 에러를 만나게 될 것이다.
Framgnet 1.2.0 버전부터는 LiveData과 관련한 viewLifecycleOwner 사용을 권장한다.
Fragment Version 1.2.0
- New Lint checks: Added a new Lint check that ensures you are using getViewLifecycleOwner() when observing LiveData from onCreateView(), onViewCreated(), or onActivityCreated().
viewLifecycleOwner로 수정함으로써 우리가 생각하는 view의 생명주기에 맞게 데이터가 갱신되는 것을 볼 수 있을 것이다.
observer로 viewLifecyclerOwner를 추가했을 때 주의해야할 것은 무엇일까?
fragment lifecycle과 달리 onCreateView ~ onDestroyView 까지 생명주기를 갖는 다는 점을 주의해야한다.
mViewLifecycleOwner의 선언부의 주석을 보면 아래와 같다.
This is initialized in perforCreateView and unavailable outside of the onCreateView/onDestroyView lifecycle
// androidx.fragment.app.Fragment.java
// This is initialized in performCreateView and unavailable outside of the
// onCreateView/onDestroyView lifecycle
@Nullable FragmentViewLifecycleOwner mViewLifecycleOwner;
MutableLiveData<LifecycleOwner> mViewLifecycleOwnerLiveData = new MutableLiveData<>();
nullable 한 viewLifecycleOwner는 아래 destroyFragmentView에서 null 이 된다. 즉 onDestroyView가 끝난 이후에 mViewLifecycleOwner가 null 이 된다.
// androidx.fragment.app.Fragment.java
private void destroyFragmentView(@NonNull Fragment fragment) {
fragment.performDestroyView();
mLifecycleCallbacksDispatcher.dispatchOnFragmentViewDestroyed(fragment, false);
fragment.mContainer = null;
fragment.mView = null;
// Set here to ensure that Observers are called after
// the Fragment's view is set to null
fragment.mViewLifecycleOwner = null;
fragment.mViewLifecycleOwnerLiveData.setValue(null);
fragment.mInLayout = false;
}
4) ViewLifecycleOwnerLiveData
위와 같이 observer로 viewLifecyclerOwner를 추가했을 때 뷰의 생명주기안에서 다뤄야한다는점, 그리고 fragment life cycle도 함께 신경써야한다는 점등 번거로워진다. 그럴 때 viewLifecycleOwnerLiveData를 이용할 수 있다.
viewLifecycleOwner가 바뀔 때마다 viewLifecycleOwnerLiveData에 전달되므로 Fragment view의 생성과정을 떠나 순수하게 viewLifecycleOwner만 바라보고 관련된 작업을 분리해서 관리할 수 있다.
예를 들어, viewLifecycleOwner 를 이용해서 LiveData를 관리했던 경우 아래와같이 onViewCreated에서 옵저빙을 하고 있다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("TAG", "onCreateView")
_binding = FragmentFemaleListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d("TAG", "onViewCreated")
viewModel.output.observe(viewLifecycleOwner) {
...
}
}
viewLifecycleOwnerLiveData를 이용해서 onCreate 에서 옵저빙을 한다면?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewLifecycleOwnerLiveData.observe(this) {
Log.d("TAG", "view life cycle owner live data")
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("TAG", "onCreateView")
_binding = FragmentFemaleListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d("TAG", "onViewCreated")
}
...
Log가 아래와 같은 순서로 찍힐 것이다. fragment 생명주기에 따라 onCreateView - onViewCreated 가 생성이 되고, onCreateView에서 뷰 라이프사이클 소유자 객체가 생성, LiveData 객체에도 소유자를 부여함으로써 이를 옵저빙하고 있는 onCreate 안의 viewLifecycleOwnerLiveData 블럭안 로그가 생성과 초기화 후 순차적으로 찍히게 되는 것이다.
viewLifecycleOwnerLiveData.observe(this) {
...
}
D/TAG: onCreateView
D/TAG: onViewCreated
D/TAG: view life cycle owner live data
참고자료
'🐸 Android' 카테고리의 다른 글
FCM Notification 1 - 안드로이드 13에서 Notification 권한 허가 변경 (0) | 2023.12.12 |
---|---|
[Android] 렌더링 관점에서 효율적인 ListAdapter (1) | 2023.09.07 |
[Android] 안드로이드의 Process와 Thread (0) | 2023.08.09 |
[Android] 안드로이드 로컬 데이터베이스에 데이터 저장 (0) | 2023.08.01 |
[Android] 안드로이드에서 Deep Links 처리하기 (0) | 2023.07.04 |