Databinding基本使用和原理

一、Databinding基本使用

实体类

class User(name: String) : BaseObservable() {

    private var name: String

    init {
        this.name = name
    }

    @Bindable
    fun getName(): String {
        return name
    }

    fun setName(name: String) {
        this.name = name
        notifyPropertyChanged(BR.name)
    }
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.sun.jetpack.databinding.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

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

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={user.name}" />
    </LinearLayout>

</layout>

基本使用

class DataBindingTestActivity : AppCompatActivity() {
    private lateinit var binding: ActivityDatabindingTestBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_databinding_test)
        val user = User("sun")
        binding.user = user
    }
}
二、Databinding原理分析

Databinding使用了apt技术,我们build项目时Databinding会生成多个文件,我们可以在build文件中查看。


databinding生成的文件.png

Databinding会将原有的activity_databinding_test.xml进行了拆分,分别是activity_databinding_test.xml和activity_databinding_test-layout.xml


activity_databinding_test.png

通过上面的代码我们发现:Databinding将原有的layout和data标签去除了,并为根布局声明了一个layout/文件名_0的tag,为其它使用到@{}或@={}的控件按顺序添加了一个binding_X的tag。
activity_databinding_test-layout.png

该配置文件详细记述了:<Variables name="user" declared="true" type="com.sun.jetpack.databinding.User"> 我们声明的变量,变量指向的数据类型的绝对路径。<Target tag="binding_1" view="TextView"> tag对应的View类型。 <Expression attribute="android:text" text="user.name"> 控件绑定具体属性和Model中的具体属性。 <TwoWay>false</TwoWay> 是否是双向的。

接下来我们从 DataBindingUtil.setContentView(this,R.layout.activity_databinding_test) 入手

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的setContentView加载布局,并通过window找到id为content的ViewGroup,它是一个FrameLayout用于加载我们添加的布局文件。接下来就是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);
    }
}

parent中的子View就是我们布局文件中的根布局LinearLayout,所以走if中的代码bind。

static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
        int layoutId) {
    return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}

bind方法又调用了DataBinderMapperImpl(apt生产的类)中的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_ACTIVITYDATABINDINGTEST: {
        if ("layout/activity_databinding_test_0".equals(tag)) {
          return new ActivityDatabindingTestBindingImpl(component, view);
        }
        throw new IllegalArgumentException("The tag for activity_databinding_test is invalid. Received: " + tag);
      }
    }
  }
  return null;
}

通过一系列条件判断之后返回一个ActivityDatabindingTestBindingImpl对象。

public ActivityDatabindingTestBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
    //这里的3表示3个节点
    this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}

mapBindings调用了ViewDataBinding类中的mapBindings方法,在加载这个类的时候,会先执行静态块,调用onViewAttachedToWindow,所有没调用setUser之前数据绑定就是这么做的。
问题:没调用setUser之前数据绑定是怎么做的?ViewDataBinding类中有个静态块,调了onViewAttachedToWindow(v)

static {
    if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
        ROOT_REATTACHED_LISTENER = null;
    } else {
        ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
            @TargetApi(VERSION_CODES.KITKAT)
            @Override
            public void onViewAttachedToWindow(View v) {
                // execute the pending bindings.
                final ViewDataBinding binding = getBinding(v);
                binding.mRebindRunnable.run();
                v.removeOnAttachStateChangeListener(this);
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
            }
        };
    }
}

静态块中有个OnAttachStateChangeListener()监听事件,当控件状态发生改变,会执行一个Runnable,找到这个mRebindRunnable。

/**
 * Runnable executed on animation heartbeat to rebind the dirty Views.
 */
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();
    }
};
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;
}

 private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        //...
        if (isRoot && tag != null && tag.startsWith("layout")) {
           //...
                    bindings[index] = view;
           //...     
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            //...
                bindings[tagIndex] = view;
           //...
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
           //...
             bindings[index] = view;
          //...
        }

        if (view instanceof  ViewGroup) {
            //...
            for (int i = 0; i < count; i++) {
              //...
              bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
              //...
                
        }
    }

mapBindings方法匹配布局中含有databinding赋值的tag控件一一存入bindings的Object的数组中并返回。ViewDataBinding持有Activity或Fragment和View的引用,主要作用一次遍历View,实例化所有的子View,并存储到数组中,解决了findViewById性能问题,同时为我们省去了findViewById操作。

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

 @Override
public void invalidateAll() {
  synchronized(this) {
    mDirtyFlags = 0x4L;
  }
  requestRebind();
}

将获取的View数组赋值给成员变量,执行了invalidateAll()方法,接着执行了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);
        }
    }
}

protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
        mBindingComponent = bindingComponent;
        mLocalFieldObservers = new WeakListener[localFieldCount];
        this.mRoot = root;
        if (Looper.myLooper() == null) {
            throw new IllegalStateException("DataBinding must be created in view's UI Thread");
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer = Choreographer.getInstance();
            mFrameCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    mRebindRunnable.run();
                }
            };
        } else {
            mFrameCallback = null;
            mUIThreadHandler = new Handler(Looper.myLooper());
        }
    }

mChoreographer.postFrameCallback(mFrameCallback)实际上也执行了mRebindRunnable.run():

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

接着看Runnable中的executePendingBindings()方法

/**
  * Evaluates the pending bindings, updating any Views that have expressions bound to
  * modified variables. This <b>must</b> be run on the UI thread.
  */
public void executePendingBindings() {
    if (mContainingBinding == null) {
        executeBindingsInternal();
    } else {
        mContainingBinding.executePendingBindings();
    }
}

如果绑定了就不需要再绑定了,如果没有绑定就执行绑定。

/**
 * Evaluates the pending bindings without executing the parent bindings.
 */
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;
}

executeBindingsInternal方法对一些状态进行了判断添加回调,核心是executeBindings()方法,执行绑定操作。executeBindings()是一个抽象方法,它在哪实现的呢?我们回到build生成的ActivityDatabindingTestBindingImpl中。
核心代码

@Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userName = null;
        com.sun.jetpack.databinding.User user = mUser;

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

                if (user != null) {
                    // read user.name
                    userName = user.getName();
                }
        }
        // batch finished
        if ((dirtyFlags & 0x7L) != 0) {
            // api target 1

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

            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
        }
    }

dirtyFlags用于表示哪个属性发生变化,如果是M中的name被修改了 ,dirtyFlags & 0x7L —> 010 & 111 —> 010不为0,读取user.name,调用setText设置值,这是M—>V的过程。当(dirtyFlags & 0x4L) != 0成立,就是V—>M,它为双向绑定的控件添加了一个内容变化的监听mboundView2androidTextAttrChanged,当控件内容发生变化时,就会更新到Model上,这就是V—>M的过程。

private androidx.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
    @Override
    public void onChange() {
        // Inverse of user.name
        // is user.setName((java.lang.String) callbackArg_0)
        java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2);
        // localize variables for thread safety
        // user.name
        java.lang.String userName = null;
        // user != null
        boolean userJavaLangObjectNull = false;
        // user
        com.sun.jetpack.databinding.User user = mUser;

        userJavaLangObjectNull = (user) != (null);
        if (userJavaLangObjectNull) {
            user.setName(((java.lang.String) (callbackArg_0)));
        }
    }
};

我们接着分析一下ViewModel上值的变化,当我们调用binding.setUser(user)时会执行ActivityDatabindingTestBindingImpl中的setUser方法

public void setUser(@Nullable com.sun.jetpack.databinding.User User) {
    updateRegistration(0, User);
    this.mUser = User;
    synchronized(this) {
        mDirtyFlags |= 0x1L;
    }
    notifyPropertyChanged(BR.user);
    super.requestRebind();
}
protected boolean updateRegistration(int localFieldId, Observable observable) {
    return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
    @Override
    public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
        return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
    }
};

 private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
        final WeakListener<Observable> mListener;

        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
            mListener = new WeakListener<Observable>(binder, localFieldId, this);
        }

        @Override
        public WeakListener<Observable> getListener() {
            return mListener;
        }

        @Override
        public void addListener(Observable target) {
            target.addOnPropertyChangedCallback(this);
        }

        @Override
        public void removeListener(Observable target) {
            target.removeOnPropertyChangedCallback(this);
        }

        @Override
        public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
        }

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

private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
        private final ObservableReference<T> mObservable;
        protected final int mLocalFieldId;
        private T mTarget;

        public WeakListener(ViewDataBinding binder, int localFieldId,
                ObservableReference<T> observable) {
            super(binder, sReferenceQueue);
            mLocalFieldId = localFieldId;
            mObservable = observable;
        }
  //...
}

从上面可以看到CREATE_PROPERTY_LISTENER是一个CreateWeakListener对象,CreateWeakListener调用create得到WeakPropertyListener,WeakPropertyListener内有变量WeakListener,WeakListener持有ViewDataBinding和Observable(即VM User)。

接着看updateRegistration

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

从mLocalFieldObservers中取localFieldId对应的WeakListener,如果为null就调用registerTo进行注册。与之前注册不一致就重新注册。

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);
        mLocalFieldObservers[localFieldId] = listener;
        if (mLifecycleOwner != null) {
            listener.setLifecycleOwner(mLifecycleOwner);
        }
    }
    listener.setTarget(observable);
}

 public void setTarget(T object) {
            unregister();
            mTarget = object;
            if (mTarget != null) {
               //调到上面的WeakPropertyListener中的addListener
                mObservable.addListener(mTarget);
            }
        }
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
  //...
    @Override
    public void addListener(Observable target) {
        target.addOnPropertyChangedCallback(this);
    }
  //...
}

registerTo方法把CreateWeakListener存储在mLocalFieldObservers里面。
listener.setTarget(observable)设置ViewModel的监听事件。

三、小结

1、DataBinding的关键类

  • ActivityDatabindingTestBinding持有绑定类的引用。
  • ActivityDatabindingTestBindingImpl具体实现了绑定。
  • BR存储了VM的id,功能和R文件类似,还用于确定监听器。
  • ViewDataBinding持有Activity或者Fragment和View的引用,主要作用是遍历View,实例化所有的子View,并存储在数组中,省去了findviewbyid的操作。
  • DataBinderMapperImpl提供布局文件layoutId到ViewDataBinding类对应的映射,主要用于加载layout返回的ViewDataBinding对象
    2、VM变化如何通知到View

设置监听:updateRegistration—>registerTo—>listener.setTarget(observable)—>target.addOnPropertyChangedCallback(this) 设置ViewModel的监听事件

VM—>V:notifyPropertyChanged(BR.id)—>mCallbacks.notifyCallbacks—>executeBindings

3、View变化如何同步到VM

设置监听:requestRebind—>executeBindings—>setTextWatcher设置对View的监听

V—>VM:回调InverseBindingListener的onChange设置值

4、流程图


DataBinding.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容