240417 TIL - Fragment Lifecycle, ListView와 RecyclerView 비교, 직렬화

DoDoBest

·

2024. 4. 18. 01:28

Fragment Lifecycle

 

Fragment View는 Fragment의 생명주기로부터 독립적인 생명주기를 가집니다. Fragment View의 생명주기는 LiveData observing과 같이 화면에 표시되어야만 의미 있는 행위들을 다루는데 유용합니다.

Fragment의 생명주기는 Fragment Manager에 의해 관리되고 설정됩니다. FragmentManager는 Fragment의 생명주기와 더불어 Fragment를 Host Activity에 attach하고, detach 하는 행위를 수행합니다. Fragment는 onAttach, onDetach 콜백 메서드를 통해 이 이벤트가 발생했을 때의 동작 처리를 지원합니다.

따라서 XML에서 Fragment를 직접 사용해서는 안 되고, FragmentContainerView를 사용하여 FragmentManager에 의해 추가되도록 사용해야 합니다.

onAttach는 Fragment가 FragmentManager에 추가되었을 때 호출되고, onDetach는 Fragment가 FragmentManager로부터 지워졌을 때 호출 됩니다.

FragmentManager는 FragmentActivity를 상속하면 사용할 수 있으며, 흔히 사용하는 AppCompActivity는 FragmentActivity를 상속하기 때문에 FragmentMananger를 사용할 수 있습니다.

Fragment는 Fragment가 속한 Parent Fragment 또는 Activity의 생명주기 상태를 넘어설 수 없습니다. Fragment가 생성되려면 Fragment가 속한 Parent Fragment 또는 Activity가 먼저 생성되어야 하며, Parent Fragment 또는 Activity가 STOP 되기 전에 Fragment가 먼저 STOP 되어야 합니다.
즉, 생성은 느리되 소멸은 더 빨리 되어야 합니다.

Fragment의 생명주기는 CREATED -> STARTED -> RESUMED -> STARTED -> CREATED -> DESTROYED 순서대로 호출됩니다.

 

onCreate


Fragment가 CREATED 상태가 됐다는 것은, FragmentManager에 추가되고, onAttach 함수가 호출되었다는 것을 의미합니다. 이때 onCreate 콜백 메서드가 호출되며, Fragment의 View는 아직 생성되지 않은 상태입니다.

 

onCreateView & onViewCreated


이후 onCreateView를 통해 Fragment의 View를 생성할 수 있으며, onCreateView를 통해 생성된 view가 null이 아니면 onViewCreated 콜백 함수가 호출됩니다.
Fragment View의 lifecycle은 Fragment와 다른데, onCreateView부터 onViewCreated까지의 View는 INITIALIZED 상태입니다.

 

onViewStateRestored 


다음으로 View는 CREATED 상태가 되며, onViewStateRestored 콜백 메소드가 호출됩니다. 여기서는 주로 View를 이전 상태로 복원하는 과정을 처리합니다.

 

onStart


다음으로 Fragment와 View는 STARTED 상태가 되며, onStart 콜백 함수가 호출됩니다. STARTED 상태는 View가 존재함을 보장해주므로 lifecycle-aware component들을 STARTED 상태부터 동작하도록 설정하는 것이 권장됩니다.

 

onResume


Fragment가 사용자에게 보이고, Fragment가 사용자와 interaction을 할 준비가 되면 onResumed 함수가 호출되고, Fragment와 View는 RESUMED 상태가 됩니다.

 

onPause


사용자가 Fragment를 막 벗어나기 시작하지만, 아직 Framgne가 보이는 경우에 onPause 함수가 호출됩니다. Fragment와 View는 STARTED 상태가 됩니다.

 

onStop


Fragment가 더 이상 보이지 않으면 Fragment와 View는 CREATED 상태가 되고 onStop 함수가 호출됩니다.

onPause에 이어 onStop 까지만 호출 되는 경우는 홈 화면을 눌러서 Activity가 onStop 되는 경우 입니다.


공식 문서에서는 더 이상 보이지 않는 경우라고 적혀 있지만, FragmentTransaction add 함수를 통해 다른 Fragment가 현재 Fragment를 완전히 가려서 더 이상 보이지 않게 만들더라도 onStop이 호출되지 않고 onResume 상태를 유지합니다.

FragmentTransaction replace 함수를 호출하면, 현재 Fragment는 onDestroyView까지 호출되어 view는 파괴되고 Fragment만 남아있는 상태가 됩니다.

 

onSaveInstanceState

API28 이전에는 state를 저장하는 onSaveInstanceState 함수가 onStop보다 먼저 호출되었는데, API 28부터는 onStop이 onSaveInstanceState 함수보다 먼저 호출됩니다.

 

onDestroyView


이후 View가 window에서 detached 되면, onDestroyView 함수가 호출됩니다. 이때 Fragment는 아직 CREATED 상태이고, View는 DESTROYED 상태가 됩니다.

 

onDestroy


Fragment가 제거되거나, Fragment가 속한 FragmentManager가 파괴되면, Fragment는 DESTROYED 상태가 되고, onDestroy 함수가 호출됩니다.

 

 

Q. RecyclerView는 무엇인지, 또한 사용하는 이유를 ViewHolder와 연관지어 설명하세요.

예전에는 Layout에 addView를 통해 화면에 표시할 UI를 직접 생성해서 추가해주고 관리해야 했습니다.

이후 데이터에 따라 필요한 View를 생성하고 관리해주는 ListView가 나왔습니다. ListView는 스크롤을 통해 화면에서 사용자에게 보이지 않게된 View를 보여줘야 하는 데이터를 표시하는 데 재활용할 수 있습니다.(getView 함수에서 선택적으로 재활용하거나 매번 재생성하도록 구현할 수 있습니다.)

그 다음으로 나온 RecyclerView는 View를 property로 포함해서 관리하는 ViewHoler라는 개념을 도입했습니다. 보이지 않게된 View가 재활용 되도록 내부에서 처리해주기 때문에, 재활용하기 위한 로직을 직접 구현하지 않아도 됩니다.

보이지 않게 된 ViewHolder는 즉시 재활용되지 않고, 해당 UI의 데이터를 유지하는 캐싱 Pool로 옮깁니다. 사용자가 다시 스크롤하여 보이지 않게 된 ViewHolder를 다시 볼 수 있기 때문입니다.

캐싱 Pool에 ViewHolder가 일정 수준 이상 차게 되면, 가장 먼저 캐싱 Pool에 들어온 ViewHolder를 재활용할 수 있는 ViewHolder가 모여있는 Recycled view pool로 옮깁니다.

ListView와 다른 RecyclerView의 또 다른 특징은, ViewType을 통해 데이터 유형에 따라 다른 View가 생성되도록 지원하는 점입니다.

 

 

RecyclerView와 ListView의 cache 차이 알아보기

ListView가 더 이상 보이지 않는 View를 바로 재활용한다는 사실을 확인하기 위해 ListView에 사용할 BaseAdapter getView 함수에서 로그를 찍도록 설정했다.

 

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var v = convertView
        Log.i(TAG, "$position -> ${v?.findViewById<TextView>(R.id.tv)?.text}")
        if (v == null) {
            v = ItemTextBinding.inflate(LayoutInflater.from(parent.context), parent, false).root
        }
        v.findViewById<TextView>(R.id.tv).text = getItem(position)
        return v
    }

 

앱을 실행해보면, View가 보이지 않는 즉시 재활용됨을 확인할 수 있다.

 

 

RecyclerView는 ViewHolder가 보이지 않더라도 즉시 재활용되지 않는다.

이를 확인하기 위해 아래와 같이 로그가 찍히도록 설정한 후 실행해봤다.

 

class MyRecyclerViewAdapter(): RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder>() {

    ...
    
    val TAG = MyRecyclerViewAdapter::class.java.simpleName

    var count = 0
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        Log.i(TAG, "onCreateViewHolder is called - ${count++}")
        return ViewHolder(
            ItemTextBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

   ...

    override fun onViewRecycled(holder: ViewHolder) {
        super.onViewRecycled(holder)
        Log.i(TAG, "${holder.binding.tv.text}")
    }
}

 

직렬화란?

https://dodobest.tistory.com/100

 

직렬화란 무엇이고, 왜 필요하며, 어떻게 직렬화를 할 수 있을까?

직렬화란? 직렬화란 Application에서 사용하는 데이터를 네트워크를 통해 전송할 수 있는 형태, 데이터베이스나 파일에 저장할 수 있는 형태로 변환하는 작업을 의미합니다. 역직렬화란 외부 소스

dodobest.tistory.com