프로젝트 팀원중 한분이 Databinding(or ViewBinding)과 관련된 메모리 누수를 LeakCanary를 통해 발견 했다고 리포팅해주셨다.

메모리 누수가 발생했다!

DataBinding을 사용한지 벌써 꽤 많은 시간이 지났는데, 난 계속 메모리 누수 코드를 작성하고 있었던 것인가..

ViewBinding 공식 문서에 예제 코드를 살펴보면 다음과 같다.

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
}
    

onDestroyView에서 _binding의 레퍼런스를 해제하는 것을 확인할 수 있다.

Fragment는 생명주기와 Fragment 내의 View 생명주기가 다르기 때문이다. Fragment는 View보다 오래 지속된다. 그러므로 onDestroyView() 메서드에서 해당 바인딩 클래스의 인스턴스 레퍼런스를 해제해줘야한다.

해결방법은..?

모든 Fragment를 쫓아다니며, onDestroyView에서 바인딩 인스턴스를 해제할 수는 없다.
Kirill Rozov라는 분이 좋은 솔루션을 만들어 라이브러리 형태로 배포하고 있다.

해당 솔루션을 사용하면 다음과 같이 viewBinding() 코드만 작성하면 된다.

class ProfileFragment : Fragment(R.layout.profile) {

    private val viewBinding: ProfileBinding by viewBinding()

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

viewBinding() 내부를 살펴보면 viewLifecycleOwner에 Observer를 추가하여 View 생명주기를 관찰하다가, Destoryed 타이밍에 바인딩 레퍼런스를 해제하는 식으로 구현되어 있다. 

카테고리: 미분류

2개의 댓글

해피솔로 · 2022년 11월 8일 4:16 오후

BaseFragment를 만들어서 그곳에서 destroyView를 호출해서 초기화 하는 방법은 안되나요??

    Charlezz · 2022년 12월 1일 11:01 오전

    그러한 방식을 사용하셔도 됩니다.

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.