기존에 Fragment를 선언하며 메모리 누수를 방지하기위해 바인딩 객체를 _binding
과 binding
의 형태로 만들어 onCreateView
에서 _binding
객체를 선언하고 get() = _binding
을 통해 바인딩 객체를 가져와 사용하도록 했다.
그리고 onDestroyView
단계에서 _binding
객체를 null
로 대입해 줌으로써 메모리 누수를 방지 할 수 있는데 이를 짧게 설명해보면 다음과 같다.
Fragment 1
에서Fragment 2
로 화면이 전환될 경우onDestroyView
를 통해 화면의View
객체들은 사라지지만onDestroy
는 실행되지 않기 때문에Fragment 1
의 바인딩 객체는 살아있는 상태가 된다.
이때 만약Fragment 1
의 코드에서LiveData
를 옵저빙 하고 있으며LifeCycleOwner
에viewLifeCycleOwner
가 사용되어Fragment 1
과 생명주기를 함께하게 된다면Fragment 1
은 화면에 없음에도 불구하고LiveData
가 업데이트 될때마다 해당 업데이트를 받게된다.
즉, 메모리 누수가 일어나게 된다.
그렇기 때문에onDestroyView
단계에서 바인딩 객체를null
로 바꿔줌으로써
현재 사용하지 않는Fragment 1
의LifeCycle
를 비활성화된 상태로 바꿔 메모리 누수를 막는 것이다.
하지만 위의 방법을 모든 Fragment
마다 똑같이 사용하는 것은 불필요한 반복이라 느껴졌고,
해당 코드들을 상속해서 사용할 수 있도록 바꿔주었다.
우선 Fragment
마다 해당 코드를 실행하도록 하려면, 그 코드를 가지고 있는 부모 클래스가 필요했기 때문에 Fragment
클래스를 상속받는 Base Fragment
를 작성했다.
이 경우 어떠한 바인딩 객체를 받더라도 inflate
해서 사용할 수 있어야 했기 때문에 제네릭을 사용했다.
// Inflate 타입의 변수를 지정 이후에 (...)의 변수를 받아서 실행한다.
typealias Inflate<T> = (LayoutInflater, ViewGroup?, Boolean) -> T
// inflate로 바인딩Fragment의 inflate함수가 들어온다.
abstract class BaseFragment<VB : ViewBinding>(private val inflate: Inflate<VB>) : Fragment() {
private var _binding: VB? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 들어온 inflate함수에 Inflate<T> 가 실행된다
_binding = inflate.invoke(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
이제 어떤 Fragment
에서든 Base Fragment
를 상속받아서 사용하면 위의 코드를 따로 입력하지 않아도 메모리 누수를 방지할 수 있게된다.
// 사용되는 fragment의 타입을 명시해주고 ::inflate를 통해 해당 바인딩의 inflate메서드를 참조하도록 보내준다.
class Fragment1 : BaseFragment<Fragment1>(Fragment1Binding::inflate) { ... }
살아... 남아요... 우리...