7. 궁금했던 것들 2편 - 바인딩 클래스와생명 주기

궁금했던 것 2편이다. 오늘은 궁금했던 것들 1편 - View Binding의 성능 향상 글의 연장선상에 있는 이야기를 하려고 한다. View Binding에 관련된 얘기다. View Binding에 대한 설명은 위의 글에서 설명해놓았으니 궁금하다면 보고 오자.

Activity - Fragment에서 View Binding 구현사항의 차이

View Binding은 Activity, Fragment 모두에게 적용할 수 있다. 바인딩 클래스 인스턴스를 할당한 binding 변수를 통해 사용하게 된다.

안드로이드 공식 문서 - View Binding

activity에서 view binding 적용 코드

    private lateinit var binding: ResultProfileBinding

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        binding = ResultProfileBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    } 

fragment에서 view binding 적용 코드

 private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

확연히 다르다. fragment에서 var binding 변수는왜 onCreateView~onDestroyView 주기 사이에서만 유효한 것일까? activity는 그런 제약이 없는데. 그리고 소름돋게도, 안드로이드 공식 문서는 바로 밑에서 이에 대해 설명한다.

참고 : 프래그먼트는 뷰보다 오래 지속됩니다. 프래그먼트의 onDestroyView() 메서드에서 결합 클래스 인스턴스 참조를 정리해야 합니다.

더 소름돋는 것은, 읽어도 잘 모르겠다.

Fragment의 수명 주기?

인터넷에 해답은 아주아주 많겠지만, 한 번 둘러가보려고 한다. 먼저 Fragment의 수명 주기에 대해 검색해봤다.

안드로이드 문서 - fragment lifecycle

먼저 프래그먼트는 뷰보다 오래 지속된다는 것은 이해가 됐다. 따라서 onCreateView()에서 바인딩 변수에 인스턴스를 할당해준 다음, onDestroyView()에서 바인딩 변수 값을 null로 주며 view lifecycle과 맞춰준다는 것이다.

하지만 하나의 의문이 더 생긴다. 프로젝트에서 해당 코드

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

를 지워도 잘 동작한다는 것이다. 동작엔 문제가 없다면, 잠깐이나마(onDestroy) 메모리 누수가 발생하기 때문에 해당 코드를 적어주는 건가? activity에서는 해당 코드를 적용하라는 가이드가 없었는데. 단순히 View의 생명주기와 맞춰주기 위해 지워준다는 것인지, 아니면 내가 모르는 뭔가가 있는지 궁금했다. 그리고 액티비티 수명주기에선 onDestroyView()라는 콜백이 없었는데 말이다.

Fragment Lifecycle의 특성

일단 Fragment는 고유한 생명주기를 가지고 있으면서도, View와 관련된 생명주기를 가지고 있다. Activity의 Lifecycle과 다르게 onCreateView(), 그리고 onDestroyView()라는 콜백이 따로 존재한다. 이러한 이유는 프래그먼트가 액티비티 상에서 동작하기 때문이다. onCreate()와 onDestroy()는 프래그먼트가 생성되고 소멸될 때 발생하는 콜백, 그리고 onCreateView()와 onDestroyView()는 뷰가 구성되고 지워질 때 발생하는 콜백이다. 이 부분에서 Activity의 Lifecycle과 크게 다르다. 따라서 화면 구성에 관련된 생명주기는 onCreateView()와 onDestroyView()이다. 일단 하나는 이해된다. 바인딩 클래스는 애초에 layoutInflater를 인자로 넣는, 화면 구성에 관련된 클래스이기 때문에. 프래그먼트에서 해당 인스턴스가 살아있는 시간이 onCreateView()부터 onDestroyView() 사이가 된다는 것은 당연한 얘기인 것 같다.

Fragment replace 동작 이후 상황

그렇다면, onDestroyView()에서 해당 인스턴스를 null 처리하지 않았는데도 정상 작동이 된 이유는 무엇일까? 메모리 누수가 발생한 걸까. 그리고 해당 질문에 대한 해답을 찾았다. Fragment는 하나의 Activity 상에서 replace되며 작동한다. replace를 한 순간, 이전 프래그먼트의 모든 View 요소들은 제거되지만, 프래그먼트 클래스의 인스턴스들은 그대로 backstack에 저장된다고 한다. 여기서 새롭게 교체된 프래그먼트와 관계없는 인스턴스들이 정리되지 않아 있다면, Memory Leak(메모리 누수)의 가능성이 생기는 것이다. 따라서 결과적으로는 View Lifecycle과 클래스 인스턴스의 정리를 위해 해당 처리(binding = null)를 해주는 것이었다.

결론

Activity, Fragment의 구현 사항이 다르다는 점에서 시작해서, 프래그먼트에선 바인딩 클래스의 유효 범위가 있다는 것을 알게되었고, 이는 Fragment Lifecycle의 특성으로 인해 View LifeCycle을 고려한 것, 그리고 인스턴스가 죽지 않고 Memory Leak을 발생시킬 수 있기 때문에 이러한 구현 사항 차이가 발생했다는 것을 알게 되었다. 앞으로도 안드로이드 개발을 진행하며 프래그먼트와 더욱 친해져야 할텐데, 이러한 정보들을 알게 되어 상당히 기쁘다.

좋은 웹페이지 즐겨찾기