[Android] ViewBindingPropertyDelegate로 Fragment에서 ViewBinding 관리하기

Minji Jeong·2022년 6월 23일
0

Android

목록 보기
24/39
post-thumbnail
안드로이드를 공부하면서 생명주기 개념에 대한 중요성을 매우 깨닫고 있다. 원래도 중요한 건 알았으나, 비동기 뿐만 아니라 뷰 관련 작업들도 생명주기에 따라 적절하게 해제를 해주어야 하니 정말 공부해야 할 것들이 많다는 생각과 함께 아직 많이 부족함을 느꼈다. 특히, 프래그먼트 내부에서 View Binding을 사용하면서 onDestroyView() 가 호출될 때 프래그먼트 자체는 남아있으니, 바인딩 객체를 해제해주어야 하는 것도 생명주기를 공부하다가 알게 되었다. 역시 좋다고 그냥 막 갖다 쓰면 큰일나는 것 같다. 어쨌든, 프래그먼트에서 View Binding 사용 시 onDestroyView()에서 바인딩 객체를 해제하지 않으면 메모리 릭이 발생한다. 바인딩 객체를 해제하는 방법에는 몇가지가 존재하는데, 개인적으로 ViewBindingPropertyDelegate라는 라이브러리를 잘 사용했기에 이번 포스팅에선 해당 라이브러리에 대해 간단하게 소개하겠다.

👉 Fragment Lifecycle

프래그먼트 내에서 ViewBinding 객체를 생성하고 메모리를 해제하는 것은 매우 중요하다. ViewBinding 객체는 프래그먼트 내에서 레이아웃에 정의된 모든 뷰들을 참조하며, 따라서 메모리를 제때 해제해주지 않으면 프래그먼트가 소멸될 때까지 계속 남아있기 때문에 메모리 릭이 발생한다. 직전 포스팅에서 프래그먼트 생명주기에 대해 설명했었는데, 프래그먼트 '자체'와 프래그먼트 '뷰'가 생성되고 소멸되는 시기는 각각 다르고, 따라서 onDestroyView()에서 프래그먼트 뷰가 소멸되어도 프래그먼트 자체는 여전히 메모리에 남아있기 때문에 뷰에 대한 모든 참조를 제거해야 한다고 했었다. 바로 이 때 ViewBinding 객체를 해제해줘야 한다.

안드로이드 공식 문서에선 ViewBinding 객체를 해제하는 방법으로 다음의 코드를 명시해놓았다. 매우 간단한 방법으로, onDestroyView() 내부에서 ViewBinding 객체를 null로 셋팅하는 것이다.

    private var _binding: ResultProfileBinding? = null
    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
    }
    

하지만 프래그먼트마다 ViewBinding 객체를 null로 만든다면 보일러 플레이트 코드가 많아질 수 있고, 메모리를 해제하지 못하는 실수를 범할 수도 있다.

구글에서는 위 방법 말고 ViewBinding 객체의 초기화를 위임하는 AutoClearedValue라는 방법도 제안했지만, 여러개의 방안들을 구글링을 통해 찾아보면서 나는 다음의 라이브러리를 사용하는 것이 ViewBinding 객체를 관리하기에 제일 편리하다고 생각했다. GitHub에 샘플 소스코드가 굉장히 잘 작성되어 있고, 코드 몇 줄만 바꿔주면 되기 때문에 더욱 추천하는 바이다.

ViewBindingPropertyDelegate

ViewBindingPropertyDelegate를 사용하면 생성된 Binding 객체를 안전하게 관리할 수 있다. README에 명시되어 있는 ViewBindingPropertyDelegate의 장점들은 다음과 같다.

  • ViewBinding의 수명 주기를 관리할 수 있고, 메모리 릭을 예방하기 참조를 해제할 수 있다.
  • View 또는 ViewBinding에 대한 nullable 참조를 유지하지 않아도 된다.
  • ViewBinding 객체를 lazy하게 생성할 수 있다.

나는 Fragment와 DialogFragment에서 ViewBindingPropertyDelegate를 사용해 ViewBinding 객체를 생성해보았다. README에서 no reflection 방식을 사용하는 것이 성능에 더 도움이 된다고 해서 샘플 코드에서 no reflection 코드를 참고했다. 샘플 코드엔 프래그먼트 말고도 액티비티, RecyclerView 뷰홀더 클래스 내에서 ViewBindingPropertyDelegate을 사용하는 것에 대한 예제도 있으니 참고해서 필요한 부분에 사용하면 된다.

먼저 ViewBindingPropertyDelegate를 사용하기 위해선 build.gradle에 다음과 같은 레포지토리와 종속 항목을 추가해야 한다.

allprojects {
  repositories {
    mavenCentral()
  }
}

dependencies {
	// reflection-free flavor
    implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6'
}

MemoFragment.kt

class MemoFragment : Fragment(R.layout.fragment_memo) {

    private val binding by viewBinding(FragmentMemoBinding::bind,
        onViewDestroyed = {
            // reset view
        })

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
    }
    
    override fun onDestroyView() {
    	super.onDestroyView()
        Log.e("MemoFragment", "onDestroyView()")
    }
}

onViewDestroyed는 Binding 객체가 소멸될 때 호출된다. 따라서 { } 내부에는 Binding 객체가 소멸될 때 해제해주어야 하는 작업을 코드로 작성해주면 된다.

onViewDestroyed()와 onDestroyView()가 호출되는 순서가 궁금해서 로그로 찍어보니 다음과 같았다. 뷰가 소멸된 이후에 Binding 객체가 해제되는 듯 하다.

MainDialog.kt

class MainDialog()  : DialogFragment() {

    private val binding by viewBinding(MainDialogBinding::bind)

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.main_dialog, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
    }
}

References

https://cliearl.github.io/posts/android/prevent-fragment-memory-leak-during-viewbinding/
https://yoon-dailylife.tistory.com/57
https://stackoverflow.com/questions/66119231/is-it-necessary-to-set-viewbinding-to-null-in-fragments-ondestroy/66119393#66119393
https://stackoverflow.com/questions/35520946/leak-canary-recyclerview-leaking-madapter

profile
Mobile Software Engineer

0개의 댓글