Android - DataBinding源码解读(内存消耗和双向绑定原理分析)

目录

一 代码Demo

二 解析

2.1 关键的ActivityMainBindingImp()

2.2 

2.3

三 总结

3.1 内存消耗的三个地方:

3.2 如何实现双向绑定的


一 代码Demo

https://github.com/LucasXu01/AndroidDemo/tree/master/NetEase_DataBinding

先熟悉demo中的用法,会使用DataBinding;


二 解析

2.1 关键的ActivityMainBindingImp()

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

       我们先从MainActivity中的绑定代码中Command+点击,进入到DataBindingUtil中的setContentView方法:

       点击找到setContentView根方法:

       在setContentView方法中,我们用acticvity获取了根布局,可以看到return了bindToAddViews方法,此方法在上图中最下方就是。可以看到bindToAddViews方法中,添加了所有的子控件,并且最终都调用了bind方法,我们再次进入bind方法中进行分析。

       可以看到bind方法,最终都会调用一个getDataBinder的方法,再点进去我们会发现,这是一个抽象方法:

       抽象方法必然被实现,我们回到上一步的:

sMapper.getDataBinder(bindingComponent, root, layoutId);

      这行代码中,按快捷键option+command+b (mac),在提示中选择我们项目的类,这里是选择DataBinderMapperImpl类(图中第二个),点击进入此类。

       找到此类中的实现的getDataBinder方法:

       可以看到,抛去异常等处理,关键在于ActivityMainBindingImpl方法,再点进去。

       可以看到有两个同名重载方法,这边是个关键,停一下仔细研究下。

2.2 

       接上面的ActivityMainBindingImpl方法,首先可以看到,在activity中的findVireByid方法,在这里同样有了,所以不存在说DataBinding没做findViewByid的说法,但是这边DataBinding有一个明显的额外开销就是:数组!就是bindings[i],数组是从标签里读出来的。

       这边可以看到,数组中每一项都被取出来然后做了一个强转,这其实就是我们布局中的每个控件。那么谁返回一个onject数组呢,看源码可以看到,就是mapBindings方法!这边我们先来看看mapBindings方法中做了什么。ok我们先点进去mapBindings方法:

       可以看到,它在通过快速地匹配文件,把左右的控件找出来并放到了数组里面返回。这边有一个注意的地方,就是在进行上那一步,加载这个类的时候,系统会先执行静态块 ,我们找一下该类中的静态块:

        我们找到这样一个静态块。可以看到这边有一个OnAttachStateChangeListener()监听事件,只要当控件发生任何状态改变,binding它new了一个Runable去执行了!我们看下这个mRebindRunnable做了什么,在看之前其实可以想象,我们DataBinding的作用不就是双向绑定,那么可以猜测,这个Runnable里面,应该是修改了双方状态。我们继续抱着这个猜想往下看,找到这个Runnable:

        这边注意了!new了一个Runnable,每个activity都要new一个线程,每个activity中数据发生变化都要去刷新UI,造成这些线程就会很多很多,造成的结果就是会消耗大量内存!(它需要对每一个绑定的view做监听)

2.3

       这边我们先回到2.1末尾的ActivityMainBindingImpl方法中,注意看上面的图,在该方法中,在获得了所有控件和对每个控件绑定了监听事件后,执行了invalidateAll()方法。

       可以看到,最终调用到了requestRebind()方法,我们command+点击,看看这个方法里有啥:

       这边可以看到,重点是最后分if else四行,可以点进去看下postFrameCallBack,可以发现,里面也是用的Handler。这里我们就知道了:一旦Model的数据发生变更被监听到,这边会通过Handler去刷新。这也会造成内存消耗!

       在2.2末尾的这个Runnable中,可以看到executePendingBindings()这个方法。点进去看一看:

       它只有为空的时候会绑定,点进去executeBindingsInternal()方法看看:

       executeBindingsInternal()方法中无关紧要的可以不看,定位到最重要的一个executeBindings()方法,执行绑定操作。(如果没有执行绑定,执行这个方法) ,在代码的倒数第四行。command+点击可以追查到,这是个抽象方法。那它在哪里被实现了呢,在该类(ViewDataBinding.class)中的static静态块中,查找一下这个静态块:

       可以看到倒数第五行左右,有个OnAttachStateChangeListener(),只要view发生了任何的attach改变,就会执行一个Runnable,这个Runable看看,其实就是上面那个Runnable:

       可以看到,执行了ecvutePendingBindings(),执行了一个延时的绑定;Command+点击我们继续:

       这里的逻辑:如果绑定了就不需要再绑定了;如果没有绑定我们需要绑定一下。这就是执行executeBindings()方法前面的逻辑;

       那么回到刚刚的executeBindings(),它是一个抽象方法,在哪里实现的呢?在我们rebuild生成的ActivityMainBindlmpl.java中!

 @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.netease.databinding.model.UserInfo user = mUser;
        java.lang.String userUsernameGet = null;
        android.databinding.ObservableField<java.lang.String> userUsername = null;
        java.lang.String userPasswordGet = null;
        android.databinding.ObservableField<java.lang.String> userPassword = null;

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


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

                    if (user != null) {
                        // read user.username
                        userUsername = user.username;
                    }
                    updateRegistration(0, userUsername);


                    if (userUsername != null) {
                        // read user.username.get()
                        userUsernameGet = userUsername.get();
                    }
            }
            if ((dirtyFlags & 0xeL) != 0) {

                    if (user != null) {
                        // read user.password
                        userPassword = user.password;
                    }
                    updateRegistration(1, userPassword);


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

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

            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);
        }
        if ((dirtyFlags & 0xeL) != 0) {
            // api target 1

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

       可以看到第七行左右,这是我们的model对象。model对象下面四 行是该model的属性。再看倒数第一行或者倒数第六行左右,都有一个setText方法。很清晰了吧,用model的属性赋值给控件,这不就是Model到View的过程嘛!model的所有值都会直接刷新控件UI赋值。

       那么相对应的又有一个问题来了,View怎么刷新Model呢?可以看到倒数第三行第四行,有个setTextWatcher,有个监听的过程。最终执行看一个mboundView1androidTextAttrChanged方法(拉倒最右边可以看到,这行代码有点长),我们command+点击进入mboundView1androidTextAttrChanged这个方法看看(这边就选一个view作为例子):

private android.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of user.password.get()
            //         is user.password.set((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2);
            // localize variables for thread safety
            // user != null
            boolean userJavaLangObjectNull = false;
            // user
            com.netease.databinding.model.UserInfo user = mUser;
            // user.password != null
            boolean userPasswordJavaLangObjectNull = false;
            // user.password.get()
            java.lang.String userPasswordGet = null;
            // user.password
            android.databinding.ObservableField<java.lang.String> userPassword = null;



            userJavaLangObjectNull = (user) != (null);
            if (userJavaLangObjectNull) {


                userPassword = user.password;

                userPasswordJavaLangObjectNull = (userPassword) != (null);
                if (userPasswordJavaLangObjectNull) {




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

       这边很清晰了,一旦你的view发生了变更,被监听到了,那么它view的值就会被get出来:android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2);拿到这个值之后,就会到model对象赋值,也就是说,View的值输入发生了任何变更,都可以改变Model! 这就是双向绑定!和反射没有任何关系。

       ActivityMainBindlmpl.java是通过APT技术自己生成出来的。


三 总结

3.1 内存消耗的三个地方:

1 绑定view时额外的object数组;

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

2 静态块中对每一个activty对应的view建立线程监听,activity那么多,view与之建立的线程也会特别多;

 if (VERSION.SDK_INT < 19) {
            ROOT_REATTACHED_LISTENER = null;
        } else {
            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
                @TargetApi(19)
                public void onViewAttachedToWindow(View v) {
                    ViewDataBinding binding = ViewDataBinding.getBinding(v);
                    binding.mRebindRunnable.run();
                    v.removeOnAttachStateChangeListener(this);
                }

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

3 Handler中的Looper,一直在等待的管道; 

 if (USE_CHOREOGRAPHER) {
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                mUIThreadHandler.post(mRebindRunnable);
            }

3.2 如何实现双向绑定的

       双向绑定的过程其实就在上面的源码分析中,主要就在我们rebuild生成的ActivityMainBindlmpl.java中!回看。

 // batch finished
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1

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

            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);
        }
        if ((dirtyFlags & 0xeL) != 0) {
            // api target 1

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

 

 

 

 

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
要使用双向绑定,需要在项目中引入Data Binding库。在项目的build.gradle文件中添加以下依赖项: ``` android { ... dataBinding { enabled = true } } dependencies { ... implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' } ``` 接下来,我们需要创建ViewModel类。在ViewModel中定义我们需要绑定的变量,并提供getter和setter方法。例如: ``` class MyViewModel : ViewModel() { var name = MutableLiveData<String>() fun setName(newName: String) { name.value = newName } fun getName(): String? { return name.value } } ``` 接下来,我们需要在布局文件中设置双向绑定。例如: ``` <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="android.view.View"/> <variable name="viewModel" type="com.example.MyViewModel"/> </data> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={viewModel.name}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Set Name" android:onClick="@{() -> viewModel.setName(editText.text.toString())}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name}" /> </layout> ``` 在这个布局文件中,我们使用了双向绑定来绑定EditText和TextView中的文本。我们还定义了一个Button来设置ViewModel中的变量。在Button的onClick属性中,我们调用了ViewModel中的setName()方法来设置变量的值。 最后,我们需要在Activity中创建ViewModel实例,并将其绑定到布局文件中。例如: ``` class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) val viewModel = ViewModelProvider(this).get(MyViewModel::class.java) binding.viewModel = viewModel binding.lifecycleOwner = this } } ``` 在这个例子中,我们使用了DataBindingUtil类来绑定布局文件。我们还创建了一个ViewModel实例,并将其绑定到布局文件中。最后,我们设置了lifecycleOwner,以确保数据绑定可以正确地处理生命周期事件。 这样,我们就完成了使用dataBinding和ViewModel进行双向绑定的过程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

许进进

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

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

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

打赏作者

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

抵扣说明:

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

余额充值