
안드로이드에서는 레이아웃 XML에 정의된 뷰를 코드에서 조작해야 할 일이 많습니다.
과거에는 findViewById()를 통해 수동으로 뷰를 참조했지만, 타입 안전성 부족, 반복적인 코드 등의 문제가 있었죠.
이런 문제를 해결하기 위해 Android에서 두 가지 주요한 바인딩 기법을 제공 한답니다.
android {
buildFeatures {
viewBinding = true
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/helloText"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.helloText.text = "Hello, World!"
}
}
이러한 예시코드를 작성하면 내부 코드 구조는 어떻게 될까요?
ActivityMainBinding 클래스가 컴파일 시 자동 생성된 실제 코드를 보면 다음과 같습니다.
public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final LinearLayout rootView;
@NonNull
public final TextView helloText;
@NonNull
public final LinearLayout main;
private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull TextView helloText,
@NonNull LinearLayout main) {
this.rootView = rootView;
this.helloText = helloText;
this.main = main;
}
@Override
@NonNull
public LinearLayout getRoot() {
return rootView;
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
int id;
missingId: {
id = R.id.helloText;
TextView helloText = ViewBindings.findChildViewById(rootView, id);
if (helloText == null) {
break missingId;
}
LinearLayout main = (LinearLayout) rootView;
return new ActivityMainBinding((LinearLayout) rootView, helloText, main);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
빌드 시 이러한 코드가 자동으로 생성되게 되는데요.
동작흐름을 설명하자면 아래와 같습니다.

이 흐름 덕분에 직접 findViewById 없이도 안전하게 뷰에 접근할 수 있습니다.
android {
buildFeatures {
dataBinding = true
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.bindingtest.User" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
</layout>
data class User(
val name: String,
)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.user = User("Android")
}
}
DataBinding을 적용하면 컴파일 시 다음과 같은 클래스들이 자동 생성됩니다.
@androidx.databinding.BindingBuildInfo
public class DataBindingTriggerClass {}
public abstract class ActivityMainBinding extends ViewDataBinding {
@Bindable
protected User mUser;
protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount) {
super(_bindingComponent, _root, _localFieldCount);
}
public abstract void setUser(@Nullable User user);
@Nullable
public User getUser() {
return mUser;
}
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToRoot) {
return inflate(inflater, parent, attachToRoot, DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding bind(@NonNull View view) {
return bind(view, DataBindingUtil.getDefaultComponent());
}
@Deprecated
public static ActivityMainBinding bind(@NonNull View view, @Nullable Object component) {
return (ActivityMainBinding)bind(component, view, R.layout.activity_main);
}
}

이 구조 덕분에 LiveData나 ObservableField를 통해 값이 변경되면 UI도 자동으로 갱신됩니다.
| 항목 | ViewBinding | DataBinding |
|---|---|---|
| XML 변수 바인딩 지원 | ❌ | ✅ (@{} 사용 가능) |
| 양방향 바인딩 | ❌ | ✅ (@={} 구문) |
| LiveData 자동 연동 | ❌ | ✅ |
| 코드 생성 시점 | 컴파일 타임 | 컴파일 타임 |
| 성능 | 빠름 (경량) | 무거움 (Expression 처리) |
| 사용 난이도 | 쉬움 | 약간 복잡 |
| MVVM 아키텍처에 적합 | 보통 | 매우 적합 |
| IDE 자동 완성 지원 | ✅ | 부분적 (표현식 제외) |
| 디버깅 난이도 | 낮음 | 높음 |
| 상황 | 추천 바인딩 |
|---|---|
| 단순 UI, 버튼 클릭 위주 화면 | ViewBinding |
| MVVM 아키텍처, 상태 바인딩 필요 | DataBinding |
| 성능이 중요한 리스트/카드 구성 | ViewBinding |
| 복잡한 조건, LiveData 연동이 필요한 화면 | DataBinding |
| 팀원이 DataBinding 미숙하거나 표현식 싫어함 | ViewBinding |
// Fragment에서는 메모리 누수 방지를 위해 onDestroyView에서 바인딩 해제
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
ViewBinding과 DataBinding은 모두 findViewById()를 대체하며 타입 안전하고 깔끔한 UI 접근을 도와준다.
ViewBinding은 간단하고 성능 좋은 방식이며, DataBinding은 MVVM 구조나 동적 표현에 강력한 도구다.
UI 복잡도, 아키텍처, 성능 요구, 팀 성향에 따라 선택해서 사용하길!