当前位置 : 主页 > 操作系统 > centos >

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

来源:互联网 收集:自由互联 发布时间:2023-09-06
目录 ​​一 代码Demo​​ ​​二 解析​​ ​​2.1关键的ActivityMainBindingImp()​​ ​​2.2​​ ​​2.3​​ ​​三 总结​​ ​​3.1 内存消耗的三个地方:​​ ​​3.2如何实现双向绑定的


目录

​​一 代码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方法:

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

       点击找到setContentView根方法:

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

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

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

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

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

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

sMapper.getDataBinder(bindingComponent, root, layoutId);

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

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

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

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

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

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

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

2.2 

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

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

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

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

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

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

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

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

2.3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

       这里的逻辑:如果绑定了就不需要再绑定了;如果没有绑定我们需要绑定一下。这就是执行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);
}

 

 

 

 

 

【文章原创作者:大丰网页设计 http://www.1234xp.com/dafeng.html 复制请保留原URL】
网友评论