View binding과 관련 팁

홍성덕·2024년 7월 11일

View binding은 view와 상호작용하는 코드를 더 쉽게 작성할 수 있게 해주는 기능이다.

android {
    ...
    buildFeatures {
        viewBinding = true
    }
}

이렇게 모듈 수준의 build.gradle에서 viewBinding을 사용 설정하면, 각 XML layout 파일의 binding class가 컴파일 타임에 생성된다. 생성된 binding class는 모듈의 build > generated > data_binding_base_source_out 패키지에 위치해있다.

binding class 인스턴스에는 대응하는 레이아웃 안의 id가 있는 모든 View의 직접 참조가 포함된다. 그래서 findViewbyId()를 사용할 필요 없이 View에 직접 접근 가능하다.

사용하는 방법은 안드로이드 view binding 공식 가이드를 살펴보면 된다. 나는 여기서 내가 추가적으로 공부한 부분에 대해 기록하겠다.


왜 Fragment에서는 _binding을 nullable로 만드는 것일까?

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
}

위의 코드는 안드로이드 공식 가이드 Use view binding in fragments 파트에 있는 예시 코드이다. 여기서는 _binding을 nullable하게 만들어주었는데 이유가 무엇인지 생각해보았다.

그 답은 공식 가이드에서 유추할 수 있었다. 해석해보면 Fragment의 생명주기가 해당 Fragment가 가지고 있는 View의 생명주기보다 길다는 뜻이다. 실제로 Fragment 생명주기 callback 메서드를 보면 onCreateView() 이전에 onCreate()가 호출되고, onDestroyView() 이후에 onDestroy()가 호출된다.


binding 객체를 onDestroyView()에서 null로 초기화해주어야 한다. binding 객체가 프래그먼트의 View를 참조하고 있기 때문이다. 만약 onDestroyView()에서 null로 초기화하지 않고 binding 객체를 유지하면, 이미 파괴된 View를 참조하는 상태가 되고, GC가 View 객체들을 수거하지 못해서 메모리 누수가 발생한다.

용어 정리
GC(Garbage Collector) : 더 이상 참조되지 않는 객체를 메모리에서 해제하는 것을 담당
메모리 누수 : 불필요한 메모리를 계속 사용하고 있는 현상

즉 binding 객체 참조는 위의 예시 코드 주석에도 나와있듯이 onCreateView()onDestroyView() 사이에서만 유효하게 해야 한다. 그래서 _binding 인스턴스를 nullable로 만드는 것이다.


참고로 binding 객체가 View를 참조하고 있다는 것은 코드로도 확인 가능하다. 모듈의 build.generated.data_binding_base_class_source_out.debug...(생략) 패키지를 찾아보면 확인 가능하다.

public final class FragmentHistoryBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;

  @NonNull
  public final Button buttonEditHistory;

  @NonNull
  public final RecyclerView recyclerViewHistory;
  
  //...
}

binding을 멤버 선언 영역에서 초기화해주면 어떻게 될까?

그냥 이건 단순한 궁금증으로 실험해보았다.
binding 인스턴스가 onCreateView()onDestroyView() 사이에서만 유효하게 만들어야 하고, 그래서 nullable로 선언해주어야 한다는 것은 이미 공부해서 알고 있다. 근데 만약 binding 인스턴스를 멤버 선언 영역에서 초기화해주면 어떻게 되는지 궁금해서 실험해보았다.

private var binding: FragmentSearchBinding = FragmentSearchBinding.inflate(layoutInflater)

이렇게 선언하고 Run을 하였더니,

java.lang.IllegalStateException: onGetLayoutInflater() cannot be executed until the Fragment is attached to the FragmentManager.

위의 IllegalStateException이 발생하였다. Fragment를 관리하는 FragmentManger에 아직 Fragment가 첨부되지 않았기 때문에 getLayoutInflater() 메서드를 호출할 수 없다.


참고자료

profile
안드로이드 주니어 개발자

0개의 댓글