美文网首页
Android DataBinding框架的使用以及简单分析

Android DataBinding框架的使用以及简单分析

作者: 陌过生人丶 | 来源:发表于2020-10-10 17:33 被阅读0次

    转载请标明出处https://www.jianshu.com/p/fb8d33168f57

    前言:毕业快3年了,出来工作之后一直是倾向于 Android相关平台逆向以及广告SDK的开发和学习,一直想补一下Android开发相关的知识点,国庆前群里老哥们聊起了MVVM和DataBinding,我心里终究是按耐不住好奇心了,于是乎也打开了浏览器,学了起来,其实本来一开始是想学MVVM先,毕竟databinding也是配合MVVM的一套工具,一种实现。但是机缘巧合先学起了这个,后面再补一篇MVVM的学习。

    1.DataBinding有什么用,能解决什么问题

    DataBinding是一个Google官方发布的框架,是为了应对日常开发中数据层和UI层之间的传递的。我们日常开发中需要自己手动去写findViewById,setOnClickListener这类厚重的代码。这个框架就可以简便的实现数据和UI的传递,UI对数据的监听,UI的刷新。

    2.DataBinding的使用

    DataBinding作为官方直推,更是不用添加依赖使用,而是gradle直接支持了,我们只需要在项目app下build.gradle之中的android下添加下面代码就可以使用,如果添加了之后无法使用,可以尝试升级AS版本。

    android {
        dataBinding {
            enabled = true;
        }
    }
    

    3.DataBinding的代码实现

    3.1.视图与数据变量的绑定
    <layout xmlns:bind="http://schemas.android.com/apk/res-auto">
    <data>
            <variable
                name="text"
                type="com.llyyyw.databinding.TextBean" />
            <variable
                name="activity"
                type="com.llyyyw.databinding.MainActivity" />
        </data>
    <!--往下开始常规的UI布局-->
    </layout>
    

    DataBinding对XML布局格式有一定的要求,需要在xml最外层使用layout包裹,之后里面分别写入data以及正常UI布局。其中data标签中声明你变量名以及变量类,我这里就是声明了自己写的TextBean类,命名为text。供下面使用。

    3.2.视图数据的使用以及控件事件监听

    DataBinding会根据activity和xml自动生成一个ViewDataBinding类,这个类负责了view的管理和逻辑,事件监听以及变量的管理。我这个类是放在build文件夹下,这个看看它是如何实现的。


    ViewDataBinding.jpg

    之后在想要操作view的类中进行viewDataBinding的实例化,设置数据。之后XML可以通过@{}的格式配合变量名获取,比方说@{text.title}。

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
            textBean = new TextBean();
            textBean.setTitle("dataBinding你好");
            textBean.setText("dataBinding你好");
            viewDataBinding.setActivity(this);
            viewDataBinding.setText(textBean);
        }
    
     <TextView
                android:id="@+id/tv_title2"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="20dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{text.title}" />
    

    这个text就是传递过去的“dataBinding你好”。
    除了数据的传递,还有对于view事件的监听,比如点击事件。

    <TextView
                android:id="@+id/tv_title2"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="20dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="@{activity::updateTitle}"
                android:text="@{text.title}" />
    

    这里就是调用了传递进去的activity中的updateTitle方法,这里activity::updateTitle这种格式的调用,规定这个方法必须要是一个有View参数的方法。

    public void updateTitle(View view) {
            textBean.setTitle("dataBinding你好" + i);
            i++;
        }
    

    当然这个activity完全可以是你想要调用的任意类,updateTitle也可以是类里的任意方法。只要你在data里绑定了数据。
    除此之外,还有其他比如@{() -> activity.updateTitle(obj)},这种写法无论带不带参数,方法参数对应上即可。所以其实也可以实现在xml中传递参数到方法里。

    3.3.BindingAdapter,视图控件对于数据变化监听。

    BindingAdapter是DataBinding提供的一个用于控件对于变量发生变化的监听的。

    @BindingAdapter("android:text_title_check")
        public static void checkTitle(TextView view, CharSequence text) {
            CharSequence oldText = view.getText();
            if (!haveContentsChanged(text, oldText)) {
                return; // 数据没有变化不进行刷新视图
            }
            view.setText(text);
        }
    

    这里@BindingAdapter()中可以根据你自己的需要进行命名,方法名也可以随意,但是要注意参数以及XML中调用时的对应。

    <TextView
                android:id="@+id/tv_title2"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="20dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text_title_check="@{text.title}"/>
    

    这样子,当传入BindingAdapter标注的方法的变量发生变化时,就会调用该方法,该方法可以进行一定的逻辑操作,比如图片的加载,文本的判定。

    3.4.单向和双向数据绑定

    前面我们使用了单向数据绑定,数据变化引起视图Textview中的更新。
    DataBinding提供了BaseObservable、ObservableField、ObservableCollection三种方式实现。

    我这里使用BaseObservable为例。

    public class TextBean extends BaseObservable implements Serializable {
        private String title;
        private String text;
    
    
        @Bindable
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
            notifyPropertyChanged(BR.title);
        }
    
        @Bindable
        public String getText() {
            return text;
        }
    
        public void setText(String text) {
            this.text = text;
            // 需要手动刷新
            notifyPropertyChanged(BR.text);
        }
    }
    

    Bindable注解get方法,notifyPropertyChanged在set方法刷新。也可以使用ObservableField这个类,对BaseObservable绑定和解绑的进一步封装。
    BaseObservable是官方实现的观察器,该接口自己实现了绑定和解绑,我们只需要专注于数据的传递更新。
    这样子配合@{}就可以达到数据变化,视图也实时变化。

    但是我现在有一个需求,我现在有一个输入框editText,或者一个多选框,这个框的结果我必须知道并拿来使用,正常的开发中,我们会通过获取控件之后getText之类的方法获取框中的实时内容,很麻烦。但是DataBinding提供了我们双向绑定。我们只需要通过@={}的写法,就可以实现
    视图改变==>数据实时改变。
    数据改变==>视图实时改变。

    <EditText
                android:id="@+id/et_title"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="20dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@={text.title}" />
    
    效果如下: demo.gif

    像这样,点击textview的事件中对数据进行修改,由于textview单向绑定,editText双向绑定,都指向同一个数据,所以都实时的改变。editText修改的时候也同理。

    3.5.DataBinding自提供变量

    如果现在我们有一个需求,需要在布局中使用上下文的变量,那我们该怎么做。正常思路就是继续使用data,Variable标签。
    其实DataBinding自己也提供了一些Variable的,比方说context。 是可以直接使用的. 代表View的getContext()。

    <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{()->activity.packageName(context.applicationContext.packageName)}"
            android:text="@{context.applicationContext.packageName}"/>
    

    我这里就配合上面讲的控件事件,将context.applicationContext.packageName作为一个string参数,传递给了packageName方法。

     public void packageName(String packageName) {
            Toast.makeText(this, packageName, Toast.LENGTH_SHORT).show();
        }
    
    contextToast.jpg
    3.6.DataBinding和include的使用

    关于include的使用,其实主要是主activity和包含布局之间要使用 bind来传递数据。包含布局中也要用data和variable实现数据变量和view的绑定。注意bind:xxx和variable name="xxx",这里两者xxx必须是一样的才能匹配到。

    <include
            layout="@layout/include_main"
            bind:textText="@{text.text}"/>
    
    <layout>
       <data>
            <variable
                name="textText"
                type="String" />
        </data>
    
        <TextView
            tools:context=".MainActivity"
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{textText}">
        </TextView>
    </layout>
    

    4.DataBinding的简单分析

    DataBinding的简单使用可以说是很简单,省去了很多代码,简化了数据和视图之间的交互。不禁我们就想知道,他其实是怎么实现的,我也很好奇,然后点开了源码,看的云里雾里,但是大概是,DataBinding对自己定义的结构的xml进行了解析,配合activity创建ViewDataBinding,获取各种view以及监听数据的过程。

    ActivityMainBinding对象的生成

    由DataBinding对象获取的方法DataBindingUtil.setContentView最终追溯到这个方法。

    public class DataBindingUtil {
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
                int layoutId) {
            return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
        }
    }
    
    这个方法最终返回的其实就是相对应ActivityMainBindingImpl的对象。 ActivityMainBindingImpl.jpg

    ViewDataBinding实例化:

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

    并且ViewDataBinding会通过view创建对应的WeakListener,弱引用类型接口作为观察者,利于被回收,同时很好的处理内存泄露的情况。之后则是update时的逻辑。

    @Override
        public boolean setVariable(int variableId, @Nullable Object variable)  {
            boolean variableSet = true;
            if (BR.activity == variableId) {
                setActivity((com.llyyyw.databinding.MainActivity) variable);
            }
            else if (BR.text == variableId) {
                setText((com.llyyyw.databinding.TextBean) variable);
            }
            else {
                variableSet = false;
            }
                return variableSet;
        }
        public void setText(@Nullable com.llyyyw.databinding.TextBean Text) {
            updateRegistration(0, Text);
            this.mText = Text;
            synchronized(this) {
                mDirtyFlags |= 0x1L;
            }
            notifyPropertyChanged(BR.text);
            super.requestRebind();
        }
    
    /**
         * @hide
         */
        protected boolean updateRegistration(int localFieldId, Observable observable) {
            return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
        }
    
        /**
         * @hide
         */
        protected boolean updateRegistration(int localFieldId, ObservableList observable) {
            return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
        }
    
        /**
         * @hide
         */
        protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
            return updateRegistration(localFieldId, observable, CREATE_MAP_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 final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
            @Override
            public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
                return new WeakListListener(viewDataBinding, localFieldId).getListener();
            }
        };
    
        private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
            @Override
            public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
                return new WeakMapListener(viewDataBinding, localFieldId).getListener();
            }
        };
    

    最终流程是:

    源代码全部不好贴出来,口述一下。相对应ActivityMainBindingImpl的实例化,数据绑定在视图UI线程中创建,以及调用父类ViewDataBinding的executePendingBindings直到executeBindings这个抽象方法,指向的是ActivityMainBindingImpl的executeBindings方法,注册WeakListener监听。
    当我们设置数据,BindingImpl的setVariable调用控件set方法,调用updateRegistration监听。当BaseObservable变化,触发notifyPropertyChanged,触发回调,也就是对应的WeakListener。就会更新视图。

    这里我们讲完了数据变化从而view变化的原理,那view变化引起数据变化呢。
    首先要知道ViewDataBinding是如何具体保存view的。前面说明了ActivityMainBindingImpl才是整个DataBinding的核心实现类,也调用了它的executeBindings方法,这个方法里其实就是对控件的一系列操作,控件的设值,控件的各种监听事件。

            if ((dirtyFlags & 0x15L) != 0) {
                // api target 1
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etTitle, textTitle);
                com.llyyyw.databinding.dbAdapter.TextBindingAdapter.checkTitle(this.tvTitle2, textTitle);
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle2, textTitle);
            }
            if ((dirtyFlags & 0x10L) != 0) {
                // api target 1
                androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etTitle, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etTitleandroidTextAttrChanged);
                this.tvTitle.setOnClickListener(mCallback1);
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle, contextApplicationContextPackageName);
            }
            if ((dirtyFlags & 0x19L) != 0) {
                // api target 1
                this.mboundView01.setTextText(textText);
            }
            if ((dirtyFlags & 0x12L) != 0) {
                // api target 1
                this.tvTitle2.setOnClickListener(activityUpdateTitleAndroidViewViewOnClickListener);
            }
    
    private androidx.databinding.InverseBindingListener etTitleandroidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
            @Override
            public void onChange() {
                // Inverse of text.title
                //         is text.setTitle((java.lang.String) callbackArg_0)
                java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(etTitle);
                // localize variables for thread safety
                // text.title
                java.lang.String textTitle = null;
                // text
                com.llyyyw.databinding.TextBean text = mText;
                // text != null
                boolean textJavaLangObjectNull = false;
    
    
    
                textJavaLangObjectNull = (text) != (null);
                if (textJavaLangObjectNull) {
    
    
    
    
                    text.setTitle(((java.lang.String) (callbackArg_0)));
                }
            }
        };
    

    只看其中的EditText控件可以发现,DataBinding是这样通过etTitleandroidTextAttrChanged这个监听器去实时获取控件的值进而对数据进行实时更新的。

    5.总结

    DataBinding的使用并不难,当然DataBinding还有许多其他的使用。深入去了解DataBinding可以发现里面的大奥秘,我这里云里雾里也只能理解这么冰山一角。学习使我快乐!

    相关文章

      网友评论

          本文标题:Android DataBinding框架的使用以及简单分析

          本文链接:https://www.haomeiwen.com/subject/lmejpktx.html