欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

[Jetpack]DataBinding源码浅析

程序员文章站 2022-03-11 16:23:14
DataBinding源码探析创建DataBinding项目源码分析DataBinding的出现实现了数据和UI的双向绑定,极大的方便了数据更新时UI能同步显示,下面就以一个简单的Demo作为入口,简要分析一下DataBinding源码。创建DataBinding项目修改build.gradle文件android {//...... dataBinding { enabled = true }新建User类class User(name: Stri...

DataBinding源码探析


DataBinding的出现实现了数据和UI的双向绑定,极大的方便了数据更新时UI能同步显示,下面就以一个简单的Demo作为入口,简要分析一下DataBinding源码。

创建DataBinding项目

  1. 修改build.gradle文件
android {
		//......
    dataBinding {
        enabled = true
    }
  1. 新建User类
class User(name: String, password: String) : BaseObservable() {
    @Bindable
    var name: String = name
        set(value) {
            field = value
            notifyPropertyChanged(BR.name)
        }
    @Bindable
    var password: String = password
        set(value) {
            field = value
            notifyPropertyChanged(BR.password)
        }
}
  1. 修改activity_main.xml如下:
<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="user"
            type="com.hanshow.databinddemo.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@{user.password}" />

    </LinearLayout>
</layout>
  1. 修改MainActivity如下:
class MainActivity : AppCompatActivity() {


    private var user:User? = null
    private var binding: ActivityMainBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        user = User("mfw","123456")
        //1.绑定布局
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
        //2.给UIset数据
        binding?.user = user
        val runnable = Runnable {
            run {
                for (index in 1..10){
                    Thread.sleep(1000)
                    user?.let {
                            it ->
                        it.name = it.name + "1"
                    }
                }
            }

        }
        //3.子线程修改数据
        Thread(runnable).start()
    }
}

实现效果如下:
[Jetpack]DataBinding源码浅析
至此,一个简单的demo就写好了,为了学习源码,我们提出以下三个问题:
1.databinding如何实现Activity和xml文件的绑定?
2.databinding如何更新数据到xml?
3.为什么子线程更新数据后,UI能正常显示?

源码分析

  • 入口1:DataBindingUtil.setContentView
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId) {
        return setContentView(activity, layoutId, sDefaultComponent);
    }

setContentView方法重载:

public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId, @Nullable DataBindingComponent bindingComponent) {
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    }

从这个方法可以看出,Activity和xml文件的绑定还是通过setContentView来完成的,继续往下查看bindToAddedViews方法:

private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {
        final int endChildren = parent.getChildCount();
        final int childrenAdded = endChildren - startChildren;
        if (childrenAdded == 1) {
            final View childView = parent.getChildAt(endChildren - 1);
            return bind(component, childView, layoutId);
        } else {
            final View[] children = new View[childrenAdded];
            for (int i = 0; i < childrenAdded; i++) {
                children[i] = parent.getChildAt(i + startChildren);
            }
            return bind(component, children, layoutId);
        }
    }
 static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }

最终调用到sMapper.getDataBinder,sMapper又是什么呢?

private static DataBinderMapper sMapper = new DataBinderMapperImpl();

最后的最后会调到DataBinderMapperImpl的getDataBinder方法。

 @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;
  }

这时候就不得不说一下databinding的中间产物了,首先会额外生成两个xml文件:
[Jetpack]DataBinding源码浅析
由于篇幅原因就不再剖析生成的xml文件内容,可以自行格式化以后进行分析,out目录下的是对databinding的数据解释和说明并通过tag和layout下的view进行绑定,再看看生成核心java文件:
[Jetpack]DataBinding源码浅析
核心Java类中的内容会按调用顺序分析,继续回到DataBinderMapperImpl的getDataBinder方法,判断view文件的tag,然后初始化ActivityMainBindingImpl,查看layout下的activity_main.xml,根布局的tag就是"layout/activity_main_0",所以下一步进入ActivityMainBindingImpl的构造方法:

 public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
    }

继续查看mapBindings方法:

  protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
            int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
        Object[] bindings = new Object[numBindings];
        mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
        return bindings;
    }

会继续重载到mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) 方法,该方法用于解析xml文件中的内容返回一个Object数据,然后继续看构造方法重载:

 private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 1
            , (android.widget.TextView) bindings[1]
            , (android.widget.TextView) bindings[2]
            );
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.tv1.setTag(null);
        this.tv2.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

至此,可以看到xml中申明的view会在解析阶段存储在ActivityMainBindingImpl中,这也就是为什么能在Activity中通过binding直接访问View。

  • 入口2:binding?.user = user
 public void setUser(@Nullable com.hanshow.databinddemo.User User) {
        updateRegistration(0, User);
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }

binding?.user = user调用的是ActivityMainBindingImpl.setUser方法,然后继续深入:

protected boolean updateRegistration(int localFieldId, Observable observable) {
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    }

CREATE_PROPERTY_LISTENER为一个CreateWeakListener常量,

  private 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;
    }

此方法就是把CreateWeakListener 和数据的id形成绑定关系,数据的Id可通过BR文件内容获得,BR文件内容如下:

public class BR {
  public static final int _all = 0;

  public static final int name = 1;

  public static final int password = 2;

  public static final int user = 3;
}

name和password是因为我们重写了set方法,并加入了@Bindable注解,而user 是定义在xml中的,都会在BR文件中生成对应的ID,然后继续看notifyPropertyChanged方法:

 public void notifyPropertyChanged(int fieldId) {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }

这个方法就跟踪到这,后面会继续讨论,接下来看super.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);
            }
        }
    }

可以看到最后需要Android版本,但是最终都会调用到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();
        }
    };

然后继续看executePendingBindings:

 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;
    }

这里最终会调用到executeBindings方法,但他是一个抽象方法,所以看他的实现就好了:

 protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userName = null;
        com.hanshow.databinddemo.User user = mUser;
        java.lang.String userPassword = null;

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


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

                    if (user != null) {
                        // read user.name
                        userName = user.getName();
                    }
            }
            if ((dirtyFlags & 0xdL) != 0) {

                    if (user != null) {
                        // read user.password
                        userPassword = user.getPassword();
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0xbL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
        }
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userPassword);
        }
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
        flag 0 (0x1L): user
        flag 1 (0x2L): user.name
        flag 2 (0x3L): user.password
        flag 3 (0x4L): null
    flag mapping end*/
    //end
}

到这里应该就都能看的懂了,就是通过一层层回调以后最终还是会通过setText来更新UI。
顺便说一下第三个问题的答案,因为我们通过mUIThreadHandler.post来更新UI,所以最终的setText还是在主线程。

  • 入口3:notifyPropertyChanged(BR.name)
 public void notifyPropertyChanged(int fieldId) {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }

接上前面的,单数据发生变更时,都会调用 mCallbacks.notifyCallbacks:

public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
        mNotificationLevel++;
        notifyRecurse(sender, arg, arg2);
        mNotificationLevel--;
        if (mNotificationLevel == 0) {
            if (mRemainderRemoved != null) {
                for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
                    final long removedBits = mRemainderRemoved[i];
                    if (removedBits != 0) {
                        removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
                        mRemainderRemoved[i] = 0;
                    }
                }
            }
            if (mFirst64Removed != 0) {
                removeRemovedCallbacks(0, mFirst64Removed);
                mFirst64Removed = 0;
            }
        }
    }

然后看一下notifyRecurse方法:

 private void notifyRecurse(T sender, int arg, A arg2) {
        final int callbackCount = mCallbacks.size();
        final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;

        // Now we've got all callbakcs that have no mRemainderRemoved value, so notify the
        // others.
        notifyRemainder(sender, arg, arg2, remainderIndex);

        // notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
        // However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
        final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;

        // The remaining have no bit set
        notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
    }
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
            final int endIndex, final long bits) {
        long bitMask = 1;
        for (int i = startIndex; i < endIndex; i++) {
            if ((bits & bitMask) == 0) {
                mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
            }
            bitMask <<= 1;
        }
    }

mNotifier.onNotifyCallback是一个抽象方法,所以看他的实现类PropertyChangeRegistry,
然后继续调用WeakPropertyListener的onPropertyChanged方法:

 @Override
        public void onPropertyChanged(Observable sender, int propertyId) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder == null) {
                return;
            }
            Observable obj = mListener.getTarget();
            if (obj != sender) {
                return; // notification from the wrong object?
            }
            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
        }

又回调到ViewDataBind的handleFieldChange方法:

  private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        if (mInLiveDataRegisterObserver) {
            // We're in LiveData 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();
        }
    }

最后还是回到了requestRebind

本文地址:https://blog.csdn.net/qq_22783769/article/details/107952300