架构师学习--DataBinding原理

一、apt编译

先上一段布局文件代码,如下:

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

    <data>

        <variable
            name="dataBean"
            type="com.xinyartech.mymvpdemo.dataBinding.DataBean2" />
    </data>

    <LinearLayout xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={dataBean.userName}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={dataBean.userPwd}" />

    </LinearLayout>
</layout>

引入layout、data标签需要在项目的gradle中加入如下配置:

dataBinding{
        enabled = true
    }

此时编译代码,发现在编译目录下会多出一些文件,如下截图:

这是因为dataBinding使用了 APT(Annotation Processing Tool)即注解处理器的技术,编译期间会扫描res/layout目录下所有的布局文件,只要有data标签,都会生成两个布局文件:配置文件和带标签的布局文件。

其中配置文件主要是通过tag能够快速定位绑定的控件,生成的布局文件主要是生成控件的tag值。两个文件的代码截图如下:

下面讲解java中如何使用dataBinding实现单项绑定和双向绑定,具体apt是如何进行编译的,后面的博客中会进行讲解。这里只需要知道apt已经为我们生成了数据和控件绑定(即view和model绑定)的代码。

二、使用

1、创建activity类,在onCreate方法中实现dataBinding的setContentView()方法。代码如下:

 ActivityDatabindingBinding databindingBinding = DataBindingUtil.setContentView(this,
                R.layout.activity_databinding);

1、单向绑定,数据变化不会导致view更新

(1)创建model对象DataBean1,代码如下:

public class DataBean1 {
    private String userName;
    private String userPwd;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    @Override
    public String toString() {
        return "DataBean1{" +
                "userName='" + userName + '\'' +
                ", userPwd='" + userPwd + '\'' +
                '}';
    }
}

(2)在activity中实例化对象,并调用绑定方法,代码如下

 mDataBean.setUserName("qb");
        mDataBean.setUserPwd("123456");
        databindingBinding.setDataBean(mDataBean);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mDataBean.setUserName("qb2222");
                mDataBean.setUserPwd("654321");
                Log.e("更新model",mDataBean.toString());
            }
        },3000);

测试发现,界面一开始显示的是"qb","123456"。当3s后,我们虽然改变了实体的字段信息,但是界面没有发生变化。

2、单向绑定,数据变化会导致view更新

(1)创建model对象DataBean2,代码如下:

public class DataBean2 {
    public ObservableField<String> userName = new ObservableField<>();
    public ObservableField<String> userPwd = new ObservableField<>();


    @Override
    public String toString() {
        return "DataBean2{" +
                "userName='" + userName.get() + '\'' +
                ", userPwd='" + userPwd.get() + '\'' +
                '}';
    }
}

(2)在activity中实例化对象,并调用绑定方法,代码如下

mDataBean2.userName.set("qb");
        mDataBean2.userPwd.set("123456");
        databindingBinding.setDataBean(mDataBean2);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mDataBean2.userName.set("qb2222");
                mDataBean2.userPwd.set("654321");
                Log.e("更新model", mDataBean2.toString());
            }
        }, 3000);

测试发现,界面一开始显示的是"qb","123456"。当3s后,我们改变了实体的字段信息,界面也会发生变化。

3、双向绑定,数据变化会导致view更新,view更新也会导致数据变化

(1)同第二种方式基本上一直,只是布局文件发生了小小的变化,代码对比如下:

对比发现,仅仅在"@"后面增加"="号

(2)在activity中实例化对象,并调用绑定方法,代码如下

mDataBean2.userName.set("qb");
        mDataBean2.userPwd.set("123456");
        databindingBinding.setDataBean(mDataBean2);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.e("更新model", mDataBean2.toString());
            }
        }, 10000);

测试发现,界面一开始显示的是"qb","123456"。当我们界面输入新的数据后,打印的就是我们输入的信息。

三、原理

1、Model绑定View(Model变化导致界面刷新)

(1)点击 DataBindingUtil.setContentView()进入源码中。依次跟踪方法setContentView()—>bindToAddedViews()—>bind()—>sMapper.getDataBinder。最终发现这个方法是一个抽象方法,那么我们就需要去子类中查看该方法,这个子类就是DataBinderMapperImpl.class

(2)进入DataBinderMapperImpl.class中,找到getDataBinder()方法。如下代码:

public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
    //找到布局文件的父节点是否有tag,没有的话就抛出异常
      final Object tag = view.getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYDATABINDING: {
        //找到的话,进入ActivityDatabindingBindingImpl
          if ("layout/activity_databinding_0".equals(tag)) {
            return new ActivityDatabindingBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_databinding is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

(3)进入ActivityDatabindingBindingImpl.class中,构造方法代码如下:

// views
    @NonNull
    private final android.widget.LinearLayout mboundView0;
    @NonNull
    private final android.widget.EditText mboundView1;
    @NonNull
    private final android.widget.EditText mboundView2;


public ActivityDatabindingBindingImpl(@Nullable android.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
    }
    private ActivityDatabindingBindingImpl(android.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 2
            );
        
        //根据tag找到控件
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView1 = (android.widget.EditText) bindings[1];
        this.mboundView1.setTag(null);
        this.mboundView2 = (android.widget.EditText) bindings[2];
        this.mboundView2.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

将bindings数组强转为我们的控件类型。数组的生成过程如下代码

 private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
        ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding == null) {
         ...
          
            if (view instanceof ViewGroup) {
                ViewGroup viewGroup = (ViewGroup)view;
                count = viewGroup.getChildCount();
                int minInclude = 0;
 
                for(int i = 0; i < count; ++i) {
             if (childTag.endsWith("_0") && childTag.startsWith("layout") &&                                     
                  childTag.indexOf(47) > 0) {

                     ...//遍历子view,根据tag,将view放到数组中
                }
                  
               
            }

        }
    }

也就是说所有的布局控件都会放到一个数组对象中,那么这个数组对象大小是不定的,如果你有多个activity就会存在多个数组对象,这是比较占用内存的。

(4)回到构造方法,继续跟踪invalidateAll()—>requestRebind()。方法部分代码如下:

/**
     * @hide
     */
    protected void requestRebind() {
        if (mContainingBinding != null) {
            mContainingBinding.requestRebind();
        } else {
           ...
            if (USE_CHOREOGRAPHER) {
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {

                //通过handler形式更新ui
                mUIThreadHandler.post(mRebindRunnable);
            }
        }
    }

也就说所有的model更新都是通过handler通知view进行更新的,handler内部拥有一个Looper对象,是不断的在执行消息循环。每一个activity都会存在一个handler,这样也会导致内存的大量消耗。

(5)进入mRebindRunnable,跟踪最终会进入ActivityDatabindingBindingImpl.class的executeBindings()方法。代码如下:

protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String dataBeanUserPwdGet = null;
        java.lang.String dataBeanUserNameGet = null;
        android.databinding.ObservableField<java.lang.String> dataBeanUserPwd = null;
        android.databinding.ObservableField<java.lang.String> dataBeanUserName = null;
        com.xinyartech.mymvpdemo.dataBinding.DataBean2 dataBean = mDataBean;

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


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

                    if (dataBean != null) {
                        // read dataBean.userPwd
                        dataBeanUserPwd = dataBean.userPwd;
                    }
                    updateRegistration(0, dataBeanUserPwd);


                    if (dataBeanUserPwd != null) {
                        // read dataBean.userPwd.get()
                        dataBeanUserPwdGet = dataBeanUserPwd.get();
                    }
            }
            if ((dirtyFlags & 0xeL) != 0) {

                    if (dataBean != null) {
                        // read dataBean.userName
                        dataBeanUserName = dataBean.userName;
                    }
                    updateRegistration(1, dataBeanUserName);


                    if (dataBeanUserName != null) {
                        // read dataBean.userName.get()
                        dataBeanUserNameGet = dataBeanUserName.get();
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0xeL) != 0) {
            // api target 1

            android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, dataBeanUserNameGet);
        }
       ...
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1

            android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, dataBeanUserPwdGet);
        }
    }

代码中很清晰的看到,通过dataBeanUserPwd.get()和 dataBeanUserPwd.get()获取Model的字段值。通过android.databinding.adapters.TextViewBindingAdapter.setText()方法显示在View层。

 

2、View绑定Model(View变化导致Model字段值更新)

同样在executeBindings()方法中,代码如下:

protected void executeBindings() {
        
        if ((dirtyFlags & 0x8L) != 0) {
        android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);
            android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
        }
        
    }

针对每一个控件都添加了一个监听mboundView1androidTextAttrChanged,mboundView2androidTextAttrChanged。针对第一个监听,进入监听回调方法。代码如下:

private android.databinding.InverseBindingListener mboundView1androidTextAttrChanged = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {

            //获取控件的值

            java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView1);

            ...

            if (dataBeanJavaLangObjectNull) {


                dataBeanUserName = dataBean.userName;

                dataBeanUserNameJavaLangObjectNull = (dataBeanUserName) != (null);
                if (dataBeanUserNameJavaLangObjectNull) {


                    //更新model属性值

                    dataBeanUserName.set(((java.lang.String) (callbackArg_0)));
                }
            }
        }
    };

首先当监听到控件有变化时,会通过回调的形式拿到控件的值,并赋值给Model属性值。

另外这里看到,针对每一个控件都会存在一个回调对象,针对多个activity,也会导致内存的大量消耗

四、总结

1、dataBinding只是一种工具,和MVVM(设计思想)是完全没有关系的,MVC、MVP同样可以使用dataBinding。

2、使用dataBinding会导致大量的内存消耗,具体在3个地方会导致该情况发生。

(1)会产生多余的数组,存放view对象

(2)存在大量handler,looper不断的循环消息

(3)针对每一个控件都会产生一个回调对象

3、dataBinding使用APT技术,编译期间生成控件与model的绑定代码。所以会产生很多类,随着布局文件的增加,编译会越来越慢。

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值