dataBinding实现和原理分析

28 篇文章 2 订阅
1 篇文章 0 订阅

dataBinding是Google退出的数据绑定支持库,使用该库可以直接实现数据Model和页面的双向绑定。

实现方法

1、在app下的build.gradle的android{…}中添加:

    dataBinding {
        enabled = true
    }

2、新建bean对象:

public class UserInfo{

    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

3、在layout的xml文件中添加数据源:
格式是:
在这里插入图片描述
如:

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

    <data>
        <variable
            name="user"
            type="com.example.mvvp2demo.bean.UserInfo" />
    </data>
    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center"
        tools:context=".MainActivity">

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

        <EditText
            android:layout_marginTop="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.password}"/>

    </LinearLayout>

</layout>

其中的data标签用来标识model,type不是类型,而是bean类的路径。将EditText的text属性设置成@{user.xx},就可以获取自定义bean对象的属性。

4、rebuild一下,apt工具会自动在build文件夹下生成多个文件在这里插入图片描述
5、在MainActivity.xml中通过DataBindingUtil工具类设置布局文件,并来获取binding对象,bingding类在刚刚rebuild的时候已自动生成,它的类名是:布局名+Binding,如布局文件名是:activity_main.xml,那么生成的类名是ActivityMainBinding.
使用DataBindingUtil.setContentView方法替换原本的setContentView方法,并获取它返回的对象:

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

通过类对象的set方法,就可以设置自定义的bean对象了。

binding.setUser(info);

完整的MainActivity.java:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        UserInfo info = new UserInfo();
        info.setUsername("username");
        info.setPassword("password");
        binding.setUser(info);
    }
}

此时数据已经能显示到控件上了:
在这里插入图片描述
试试看Model层的数据改变时控件的数据是否会改变,设置一个定时器:

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            info.setUsername("username2");
            info.setPassword("password2");
        }
    }, 5000);

5秒过后,什么也没有发生!
这是因为此时的数据还没有设置监听,需要对Bean类的属性做一些额外的设置:

import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.library.baseAdapters.BR;

public class UserInfo extends BaseObservable {

    private String username;
    private String password;

    @Bindable
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
        notifyPropertyChanged(BR.username);
    }

    @Bindable
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
        notifyPropertyChanged(BR.password);
    }
}

继承BaseObservable,给set方法设置notifyPropertyChanged(BR.password);给get方法设置注解,然后运行。

这时发现model层的值变了,5秒后控件的数据值改变了,同时控件收到了这个变化,于是控件的值也改变了。这就是单向绑定:Model层→View层。

还有一种方式,直接将bean改成这样:

import androidx.databinding.BaseObservable;
import androidx.databinding.ObservableField;

public class UserInfo extends BaseObservable {

    public ObservableField<String> username = new ObservableField<>();
    public ObservableField<String> password = new ObservableField<>();
}

这样的效果与上一种相同,只是属性值的获取稍微发生了变化:
MainActivity.java:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        final UserInfo info = new UserInfo();
        info.username.set("username");
        info.password.set("password");
        binding.setUser(info);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                info.username.set("username2");
                info.password.set("password2");
            }
        }, 5000);
    }
}

5秒过后View层的控件值变了,效果与之前的一样!但是这样,View层控件值改变,Model层的数据能监听到吗?

试试这样:

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            usernameEt.setText("123456789");
            passwordEt.setText("abcdefg");
            Log.d(TAG, "run: " + "username:"+info.getUsername()+",password:"+info.getPassword());
        }
    }, 5000);

设置一个定时器,5秒后控件的值发生变化,然后打印Model层的数据值:

在这里插入图片描述
可见Model层的数据值并没有随着控件的值发生改变。

这时其实只要在之前View层的控件值中,这样修改:

<EditText
            android:id="@+id/et_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.username}"/>

        <EditText
            android:id="@+id/et_pwd"
            android:layout_marginTop="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.password}"/>

改为:

	<EditText
	            android:id="@+id/et_name"
	            android:layout_width="wrap_content"
	            android:layout_height="wrap_content"
	            android:text="@={user.username}"/>

        <EditText
            android:id="@+id/et_pwd"
            android:layout_marginTop="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={user.password}"/>

这样,就完成了View层→Model层的绑定,双向绑定就完成了!

原理分析

其实原理分析就是看看自动生成的那些文件里都做了什么。

首先从DataBindingUtil的setContentView方法进入:
在这里插入图片描述

继续进入:

在这里插入图片描述

可以看出,这个方法把我们传递的activity和layoutId在这里设置了,这就是为啥在MainActivity中可以去掉setContentView的原因。然后获取了应用应用的根布局,传入了这个bindToAddedViews方法。

在这里插入图片描述

可以看出这个方法调用了bind方法:

在这里插入图片描述

发现return了sMapper的getDataBinder方法。进入getDataBinder方法就会发现这是个抽象方法,
在此之前有个重要的xml文件得看,它的路径是app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
在这里插入图片描述
可以发现它和之前的xml文件布局一模一样,工具会把原xml文件拆分成2个xml,一个是被附上tag交由Android OS渲染,并且会附上tag方便快速找到控件,另一个这是在DataBinding处理。

看看它的子类DataBinderMapperImpl实现,路径在app/build/generated/ap_generated_sources/debug/out/包名/下的就能找到:

在这里插入图片描述

其中做了判断,就是把打上tag的view作为参数传给ActivityMainBindingImpl做处理,继续看看ActivityMainBindingImpl,路径在
app/build/generated/ap_generated_sources/debug/out/包名/databinding/ActivityMainBindingImpl

在这里插入图片描述

其中mapBindings方法较长,大致意思就是把打上tag的控件存入到一个bindings的数组中,
然后把他们一一赋值成mboundView0、mboundView1…
可以看看他们的初始化:

与布局文件中控件也是一一对应的。

然后调用invalidateAll方法,invalidateAll方法调用requestRebind方法:

requestRebind方法:
在这里插入图片描述

一开始是一些判断返回之类的信息,然后就是较重要的mUIThreadHandler.post(mRebindRunnable),
看看mRebindRunnable:

在这里插入图片描述

可以看到这里添加了一个监听器:ROOT_REATTACHED_LISTENER,定位后看看:
在这里插入图片描述

它定义在static中,并且使用binding.mRebindRunnable重新回到之前的实现中。

再来看executePendingBindings方法:
在这里插入图片描述
调用的executeBindingsInternal方法,大多是一些判断监听的实现:
在这里插入图片描述

直接进入executeBindings方法:
在这里插入图片描述
这是个抽象方法,它的实现类是ActivityMainBindingImpl:
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
com.example.mvvp2demo.bean.UserInfo user = mUser;
java.lang.String userUsername = null;
java.lang.String userPassword = null;

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


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

                    if (user != null) {
                        // read user.username
                        userUsername = user.getUsername();
                    }
            }
            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.mboundView1, userUsername);
        }
        if ((dirtyFlags & 0x8L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);
            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);
        }
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userPassword);
        }
    }

这里发现mUser就是通过setUser赋值而来的,并且有:

androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userUsername);

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

androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);

androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userPassword);

这样4个方法,看看他们的实现:

在这里插入图片描述

当model层的数据改变时就会调用这个方法,并且比较和之前的值后也是通过简单的view.setText(text)改变值,

在这里插入图片描述

给view设置监听器:在这里插入图片描述

当控件的值发生变化时,就会调用textAttrChanged.onChange(),onChange()实现:

在这里插入图片描述

然后Model层的user对象通过user.setUsername(((java.lang.String) (callbackArg_0)))完成Model层数据的赋值。

Model层变化,view层的数据也变化

为什么Bean类中使用ObservableField和notifyPropertyChanged+@Bindable都能实现类的监听呢?进入notifyPropertyChanged方法:

在这里插入图片描述

和看看ObservableField类的set方法:

在这里插入图片描述

本质上都是一样的。

我们进去看看它是怎么实现的:

在这里插入图片描述

它调用了一个叫notifyRecurse的方法:

在这里插入图片描述

进入notifyCallbacks方法(notifyRemainder自己进入看看就知道了,最后又调回来了…):

在这里插入图片描述
可以发现调用了

mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);

这个mNotifier又是什么呢?它是通过构造方法赋值的:

在这里插入图片描述

它的子类PropertyChangeRegistry的构造方法传入了这个对象:

在这里插入图片描述

这是一个实现了的监听器,监听器中完成了:

 @Override
        public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,int arg, Void notUsed) {
            callback.onPropertyChanged(sender, arg);
        }

实现监听器的onPropertyChanged方法。

梳理一下逻辑就是:notifyCallbacks调用
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);

mNotifier.onNotifyCallback的实现调用:

callback.onPropertyChanged(sender, arg);

总结就是执行:mCallbacks.get(i).onPropertyChanged(),这个mCallbacks.get(i)获得的对象就是在BaseObservable中的addOnPropertyChangedCallback添加进来的:

在这里插入图片描述

addOnPropertyChangedCallback是在ViewDataBinding的addListener方法执行的。

在这里插入图片描述

根据这个this可以找到onPropertyChanged的实现:

在这里插入图片描述

查看handleFieldChange的实现:

在这里插入图片描述
onFieldChange是个抽象方法,谁实现了它?ActivityMainBindingImpl实现了它:

在这里插入图片描述

当notifyPropertyChanged被执行时,mDirtyFlags就会被赋于不同的值,executeBindings方法被调用时根据mDirtyFlags不同的值为不同的控件设置值。

总结

所谓的双向绑定,其实无非是双向监听而已,只不过将他封装成了一个工具,甚至还有不少缺点(比如数据层大时,占用内存也很大)

demo地址:https://download.csdn.net/download/weixin_45253393/12351162

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哒哒呵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值