*개인적으로 분석한 내용이므로 틀린 내용이 있을 수 있습니다. 틀린 내용을 댓글로 남겨주시면 수정하도록 하겠습니다!
DataBinding 라이브러리는 UI Component 들이 데이터 소스에 Programmatically 한 방식이 아닌 선언적 방식을 통해 결합하도록 지원하는 Jetpack 라이브러리이다.
[1]. DataBinding 사용방법
*DataBinding 을 사용하는 방법은 수많은 블로그에 이미 자세히 기록되어 있기 때문에 간략히 작성하고 넘어간다.
1. gradle 파일에서 DataBinding 활성화
buildFeatures {
dataBinding true
}
2. layout 파일을 DataBinding 사용 형식으로 변경
<data> 내의 <variable> 태그를 선언하면 DataBinding 라이브러리에 의해 자동으로 생성되는 Binding class 의 멤버변수로 선언되게 된다.
<?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="testViewModel"
type="com.test.databinding.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/output_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{testViewModel.count.toString()}"
android:textSize="30sp"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/increase_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/output_text_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="30dp"
android:text="increase" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3. DataBinding 에 사용할 ViewModel class 생성
해당 ViewModel 을 Binding class에서 참조하고 있으며, layout xml 파일의 View 의 속성이 ViewModel 의 LiveData를 참조하고 있기 때문에 LiveData 의 값이 업데이트되면 자동으로 View 도 업데이트 될 수 있다.
class MainViewModel : ViewModel() {
private var _count: MutableLiveData<Int> = MutableLiveData()
val count: LiveData<Int> get() = _count
init {
_count.value = 0
}
fun increase() {
_count.value = _count.value?.plus(1)
}
}
4. Activity Component 에서 DataBinding 초기화
class MainActivity : AppCompatActivity() {
private val binding by lazy { DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)}
private val viewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initDataBinding()
initViews()
}
private fun initDataBinding() {
binding.lifecycleOwner = this
binding.testViewModel = viewModel
}
private fun initViews() {
binding.increaseButton.setOnClickListener {
viewModel.increase()
}
}
}
[2]. LiveData 옵저버를 등록하는 코드 분석
DataBinding 을 LiveData 와 함께 사용하지 않으면 데이터가 변경되었을 때 invalidateAll 메서드를 호출하여 데이터가 변경되었음을 View 에 직접 알려야 한다. 하지만 LiveData 를 사용하면 데이터가 변했을 때 옵저버를 통해 자동으로 View 에 통지가 가기 때문에 더 간결한 코드를 작성할 수 있다. 이 섹션에서는 DataBinding 과 LiveData 를 함께 사용할 경우 어떻게 옵저버를 등록하게 되는지를 분석한다.
1. 생명주기 소유자의 생명주기가 ON_START 가 되면 리스너를 통해 LiveData 의 값을 View 에 최초로 바인딩하기 위한 작업을 시작한다.
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();
}
}
}
2. 데이터를 바인딩하기 전에 옵저버가 등록되어 있는 LiveData 인지 검사하는 메서드인 updateLiveDataRegistration 을 호출한다. 만약 옵저버가 등록되지 않은 LiveData 라면(최초의 바인딩이라면) 이 곳에서 옵저버를 등록한다.
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags; //dirtyFlag == 0x6L
mDirtyFlags = 0;
}
com.test.databinding.MainViewModel testViewModel = mTestViewModel;
androidx.lifecycle.LiveData<java.lang.Integer> testViewModelCount = null;
java.lang.Integer testViewModelCountGetValue = null;
java.lang.String testViewModelCountToString = null;
if ((dirtyFlags & 0x7L) != 0) {
if (testViewModel != null) {
// read testViewModel.count
testViewModelCount = testViewModel.getCount();
}
//옵저버가 등록되지 않은 LiveData 라면 옵저버를 등록하는 메서드
updateLiveDataRegistration(0, testViewModelCount);
if (testViewModelCount != null) {
// read testViewModel.count.getValue()
testViewModelCountGetValue = testViewModelCount.getValue();
}
if (testViewModelCountGetValue != null) {
testViewModelCountToString = testViewModelCountGetValue.toString();
}
}
// batch finished
if ((dirtyFlags & 0x7L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.outputTextView, testViewModelCountToString);
}
}
protected boolean updateLiveDataRegistration(int localFieldId, LiveData<?> observable) {
mInLiveDataRegisterObserver = true;
try {
return updateRegistration(localFieldId, observable, CREATE_LIVE_DATA_LISTENER);
} finally {
mInLiveDataRegisterObserver = false;
}
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
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;
}
[3]. LiveData 의 값이 업데이트될 때 View 를 자동으로 업데이트하는 내용 분석
1. 관찰대상인 LiveData 의 값이 변화하면 자동으로 onChanged 메서드가 호출된다.
@Override
public void onChanged(@Nullable Object o) {
//binder == ActivityMainBindingImpl 객체
ViewDataBinding binder = mListener.getBinder();
if (binder != null) {
//target == 관찰중인 LiveData 객체
binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0);
}
}
2. requestRebind 메서드를 호출하여 변화된 값에 대해 View 업데이트를 시작한다.
protected void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver || mInStateFlowRegisterObserver) {
// We're in LiveData or StateFlow registration, which always results in a field change
// that we can ignore. The value will be read immediately after anyway, so
// there is no need to be dirty.
return;
}
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
final LifecycleOwner owner = this.mLifecycleOwner;
if (owner != null) {
Lifecycle.State state = owner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
3. 최종적으로 Binding Impl 클래스의 executeBindings 메서드를 호출하여 View 업데이트하는 코드를 실행한다.
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags; //dirtyFlag == 0x6L
mDirtyFlags = 0;
}
com.test.databinding.MainViewModel testViewModel = mTestViewModel;
androidx.lifecycle.LiveData<java.lang.Integer> testViewModelCount = null;
java.lang.Integer testViewModelCountGetValue = null;
java.lang.String testViewModelCountToString = null;
if ((dirtyFlags & 0x7L) != 0) {
if (testViewModel != null) {
// read testViewModel.count
testViewModelCount = testViewModel.getCount();
}
updateLiveDataRegistration(0, testViewModelCount);
if (testViewModelCount != null) {
// read testViewModel.count.getValue()
testViewModelCountGetValue = testViewModelCount.getValue();
}
if (testViewModelCountGetValue != null) {
//TextView 의 Text 를 업데이트한다.
testViewModelCountToString = testViewModelCountGetValue.toString();
}
}
// batch finished
if ((dirtyFlags & 0x7L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.outputTextView, testViewModelCountToString);
}
}
'android > Jetpack' 카테고리의 다른 글
| 5-1. Jetpack Navigation의 구성요소 (0) | 2022.10.09 |
|---|---|
| 4-2 단방향 데이터 바인딩, 양방향 데이터 바인딩 (0) | 2022.10.08 |
| 3. LiveData (0) | 2022.10.03 |
| 2. ViewModel 과 ViewModelFactory (0) | 2022.10.02 |
| 1. ViewBinding (1) | 2022.10.01 |