DataBinding

Grow Up!·2024년 2월 2일
0

Dive in Android

목록 보기
4/4

목차

  1. DataBinding 이 뭐고 왜 필요해요?
  2. DataBinding 클래스 어느정도 깊이로 들어가보자
  3. 요약은 깔끔하게


1. DataBinding 이 뭐고 왜 필요해요?

DataBinding 은 Data 와 Binding 이 합쳐진 단어로 직관적으로 해석하면 “데이터를 어딘가에 묶는 것” 입니다. 좀 더 자세히 말하면 데이터를 특정 UI Component 와 묶어서 데이터가 변경되면 UI Component 가 변경된 데이터를 반영할 수 있도록 하는 기술 정도로 이해할 수 있을 것 같습니다. 물론 two-way DataBinding 의 경우는 반대의 경로로도 갈 수 있습니다. Android 에서는 DataBinding 을 쉽게 구현할 수 있도록 Jetpack DataBinding Library 를 지원합니다.

이전까지는 DataBinding 이 무엇인지에 집중했습니다. 그렇다면 DataBinding 은 왜 필요할까요? Android 개발 관점에서 바라본다면 우선 DataBinding 을 사용하면 Activity 또는 Fragment 내의 UI 업데이트 로직을 Layout 으로 옮길 수 있게되며 이는 곧 관심사를 분리시켜 유지보수에 도움을 줄 수 있으며, 그리고 개인적으로 생각하는 가장 큰 필요성은 앱 내에 MVVM 패턴을 적용하여 UI 업데이트 로직을 신경쓰지 않고 앱의 비즈니스 로직에 집중하여 개발할 수 있다는 것입니다.



2. DataBinding 클래스 어느정도 깊이로 들어가보자

분석에 사용된 소스코드와 레이아웃은 아래와 같습니다.

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val viewModel by lazy { ViewModelProvider(this)[MainViewModel::class.java] }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.lifecycleOwner = this
        binding.vm = viewModel

        binding.button.setOnClickListener {
            viewModel.update()
        }
    }
}
class MainViewModel : ViewModel() {
    private val _count = MutableLiveData<Int>(0)
    val count: LiveData<Int> get() = _count

    fun update() {
        _count.value = _count.value?.inc()
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="vm"
            type="com.wantique.databinding.MainViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/address_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(vm.count)}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

        <Button
            android:id="@+id/button"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>


  1. DataBinding 클래스의 인스턴스를 가져옵니다.
    • DataBindingUtil 클래스의 static 필드인 sMapper 에 DataBinderMapperImpl 인스턴스 참조를 저장합니다. DataBinderMapperImpl 클래스는 MergedDataBinderMapper 를 상속받으며 생성자에서 MergedDataBinderMapper 클래스의 addMapper() 메서드를 호출하여 빌드 시 생성된 DataBinderMapperImpl 인스턴스 참조를 List 에 저장합니다.
    • DataBindingUtil.setContentView() 메서드의 마지막에 MergedDataBinderMapper 의 List 에 저장된 DataBinderMapperImplgetDataBinder() 메서드를 호출하여 ViewDataBinding 을 상속받은 ActivityMainBindingImpl 인스턴스 참조를 반환합니다.
public class DataBindingUtil {
    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
		
		static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
}
public class DataBinderMapperImpl extends MergedDataBinderMapper {
		DataBinderMapperImpl() {
				addMapper(new com.wantique.databinding.DataBinderMapperImpl());
		}
}
public class MergedDataBinderMapper extends DataBinderMapper {
		private List<DataBinderMapper> mMappers = new CopyOnWriteArrayList<>();

		public void addMapper(DataBinderMapper mapper) {
        Class<? extends DataBinderMapper> mapperClass = mapper.getClass();
        if (mExistingMappers.add(mapperClass)) {
            mMappers.add(mapper);
            final List<DataBinderMapper> dependencies = mapper.collectDependencies();
            for(DataBinderMapper dependency : dependencies) {
                addMapper(dependency);
            }
        }
    }

		@Override
    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId) {
        for(DataBinderMapper mapper : mMappers) {
            ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
            if (result != null) {
                return result;
            }
        }
        if (loadFeatures()) {
            return getDataBinder(bindingComponent, view, layoutId);
        }
        return null;
    }
}
public class DataBinderMapperImpl extends DataBinderMapper {
		private static final int LAYOUT_ACTIVITYMAIN = 1;
		private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);

		static {
				INTERNAL_LAYOUT_ID_LOOKUP.put(com.wantique.databinding.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
		}

		@Override
		public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
				int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
				if(localizedLayoutId > 0) {
						final Object tag = view.getTag();
						if(tag == null) {
								throw new RuntimeException("view must have a tag");
						}
						switch(localizedLayoutId) {
								case  LAYOUT_ACTIVITYMAIN: {
										if ("layout/activity_main_0".equals(tag)) {
												return new ActivityMainBindingImpl(component, view);
										}
								throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
						}
				}
		}
		return null;
}


  1. DataBinding 클래스에서 LiveData 변경사항을 관찰하는데 사용할 LifecycleOwner 를 설정합니다.
    • 전달된 LifecycleOwner 의 타입이 Fragment 인 경우 viewLifecycleOwner 를 설정하라는 경로를 출력합니다. FragmentLifecycleOwner 로 설정한 경우에는 FragmentView 가 파괴된 경우에 LiveData 변경에 의한 View 업데이트가 발생한 경우 예외가 발생할 수 있습니다.
    • 전달된 LifecycleOwnerOnStartListener Observer 를 등록하여 LifecycleOwner 의 Lifecycle 의 State 가 STARTED 로 이동할 때 DataBinding 클래스의 executePendingBindings() 메서드를 호출하여 데이터가 View 에 바인딩될 수 있도록 합니다.
public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {
		@MainThread
    public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
        if (lifecycleOwner instanceof Fragment) {
            Log.w("DataBinding", "Setting the fragment as the LifecycleOwner might cause"
                    + " memory leaks because views lives shorter than the Fragment. Consider"
                    + " using Fragment's view lifecycle");
        }
        if (mLifecycleOwner == lifecycleOwner) {
            return;
        }
        if (mLifecycleOwner != null) {
            mLifecycleOwner.getLifecycle().removeObserver(mOnStartListener);
        }
        mLifecycleOwner = lifecycleOwner;
        if (lifecycleOwner != null) {
            if (mOnStartListener == null) {
                mOnStartListener = new OnStartListener(this);
            }
            lifecycleOwner.getLifecycle().addObserver(mOnStartListener);
        }
        for (WeakListener<?> weakListener : mLocalFieldObservers) {
            if (weakListener != null) {
                weakListener.setLifecycleOwner(lifecycleOwner);
            }
        }
    }
		
		static class OnStartListener implements LifecycleObserver {
        final WeakReference<ViewDataBinding> mBinding;
        private OnStartListener(ViewDataBinding binding) {
            mBinding = new WeakReference<>(binding);
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        public void onStart() {
            ViewDataBinding dataBinding = mBinding.get();
            if (dataBinding != null) {
                dataBinding.executePendingBindings();
            }
        }
    }
}


  1. Layout 에 선언한 Layout 변수를 설정합니다.
    • ViewModel 을 저장한 후 ViewDataBindingrequestRebind() 메서드를 호출하여 LifecycleOwner 의 Lifecycle State 가 STARTED 이상인 경우 executePendingBindings() 메서드를 호출하여 데이터가 View 에 바인딩될 수 있도록 합니다.
public class ActivityMainBindingImpl extends ActivityMainBinding  {
		public void setVm(@Nullable com.wantique.databinding.MainViewModel Vm) {
        this.mVm = Vm;
        synchronized(this) {
            mDirtyFlags |= 0x2L;
        }
        notifyPropertyChanged(BR.vm);
        super.requestRebind();
    }
}


  1. LifecycleOwner 의 Lifecycle State 가 STARTED 가 된 경우 데이터를 View 에 바인딩합니다.
    • executeBindingsInternal() 메서드 내에서 ActivityMainBindingImplexectueBindings() 메서드를 호출합니다.
    • executeBindings() 메서드 내에서 dirtyFlag 를 연산하여 연산 결과가 0이 아닌 경우 ViewModel 의 LiveData 를 가져온 후 updateLiveDataRegistration() 메서드를 호출하여 LiveData 에 Observer 를 등록합니다. 그리고 LiveData 에서 데이터를 가져온 후 DataBinding Library 에서 제공하는 기본 BindingAdapter 를 사용하여 데이터를 View 에 바인딩합니다.
    • LiveData 가 업데이트된 경우에는 등록한 Observer 의 onChanged() 메서드가 호출되며, 최종적으로 exectueBindings() 메서드가 다시 호출되어 위와 동일한 과정을 거쳐 업데이트된 데이터를 View 에 바인딩합니다.
public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {
		public void executePendingBindings() {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else {
            mContainingBinding.executePendingBindings();
        }
    }

		private void executeBindingsInternal() {
        if (mIsExecutingPendingBindings) {
            requestRebind();
            return;
        }
        if (!hasPendingBindings()) {
            return;
        }
        mIsExecutingPendingBindings = true;
        mRebindHalted = false;
        if (mRebindCallbacks != null) {
            mRebindCallbacks.notifyCallbacks(this, REBIND, null);

            // The onRebindListeners will change mPendingHalted
            if (mRebindHalted) {
                mRebindCallbacks.notifyCallbacks(this, HALTED, null);
            }
        }
        if (!mRebindHalted) {
            executeBindings();
            if (mRebindCallbacks != null) {
                mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
            }
        }
        mIsExecutingPendingBindings = false;
    }

		protected boolean updateLiveDataRegistration(int localFieldId, LiveData<?> observable) {
        mInLiveDataRegisterObserver = true;
        try {
            return updateRegistration(localFieldId, observable, CREATE_LIVE_DATA_LISTENER);
        } finally {
            mInLiveDataRegisterObserver = false;
        }
    }

		protected boolean updateRegistration(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return unregisterFrom(localFieldId);
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }
        if (listener.getTarget() == observable) {
            return false;//nothing to do, same object
        }
        unregisterFrom(localFieldId);
        registerTo(localFieldId, observable, listenerCreator);
        return true;
    }

		protected void registerTo(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return;
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            listener = listenerCreator.create(this, localFieldId, sReferenceQueue);
            mLocalFieldObservers[localFieldId] = listener;
            if (mLifecycleOwner != null) {
                listener.setLifecycleOwner(mLifecycleOwner);
            }
        }
        listener.setTarget(observable);
    }

		private static final CreateWeakListener CREATE_LIVE_DATA_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(
                ViewDataBinding viewDataBinding,
                int localFieldId,
                ReferenceQueue<ViewDataBinding> referenceQueue
        ) {
            return new LiveDataListener(viewDataBinding, localFieldId, referenceQueue)
                    .getListener();
        }
    };

		private static class LiveDataListener implements Observer,
            ObservableReference<LiveData<?>> {
				final WeakListener<LiveData<?>> mListener;
			
				public LiveDataListener(
                ViewDataBinding binder,
                int localFieldId,
                ReferenceQueue<ViewDataBinding> referenceQueue
        ) {
            mListener = new WeakListener(binder, localFieldId, this, referenceQueue);
        }

				@Override
        public void addListener(LiveData<?> target) {
            LifecycleOwner lifecycleOwner = getLifecycleOwner();
            if (lifecycleOwner != null) {
                target.observe(lifecycleOwner, this);
            }
        }

				@Override
        public void onChanged(@Nullable Object o) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder != null) {
                binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0);
            }
        }
		}
}
public class ActivityMainBindingImpl extends ActivityMainBinding  {
		@Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String stringValueOfVmCount = null;
        com.wantique.databinding.MainViewModel vm = mVm;
        androidx.lifecycle.LiveData<java.lang.Integer> vmCount = null;
        int androidxDatabindingViewDataBindingSafeUnboxVmCountGetValue = 0;
        java.lang.Integer vmCountGetValue = null;

        if ((dirtyFlags & 0x7L) != 0) {



                if (vm != null) {
                    // read vm.count
                    vmCount = vm.getCount();
                }
                updateLiveDataRegistration(0, vmCount);


                if (vmCount != null) {
                    // read vm.count.getValue()
                    vmCountGetValue = vmCount.getValue();
                }


                // read androidx.databinding.ViewDataBinding.safeUnbox(vm.count.getValue())
                androidxDatabindingViewDataBindingSafeUnboxVmCountGetValue = androidx.databinding.ViewDataBinding.safeUnbox(vmCountGetValue);


                // read String.valueOf(androidx.databinding.ViewDataBinding.safeUnbox(vm.count.getValue()))
                stringValueOfVmCount = java.lang.String.valueOf(androidxDatabindingViewDataBindingSafeUnboxVmCountGetValue);
        }
        // batch finished
        if ((dirtyFlags & 0x7L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.addressTextView, stringValueOfVmCount);
        }
    }
}


3. 요약은 깔끔하게

DataBinding 은 데이터가 업데이트되었을 때 UI Component 가 자동으로 업데이트될 수 있도록 지원합니다. Android 는 Jetpack 에서 DataBinding Library 를 제공하여 매우 간편하게 DataBinding 을 적용할 수 있습니다.

0개의 댓글

관련 채용 정보