首页 > 系统相关 >Android - DataBinding源码解读(内存消耗和双向绑定原理分析)

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

时间:2022-11-29 18:35:40浏览次数:68  
标签:TextViewBindingAdapter databinding 源码 user DataBinding Android null adapters and


目录

​​一 代码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源码解读(内存消耗和双向绑定原理分析)_双向绑定_04

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

sMapper.getDataBinder(bindingComponent, root, layoutId);

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

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

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

Android - DataBinding源码解读(内存消耗和双向绑定原理分析)_双向绑定_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源码解读(内存消耗和双向绑定原理分析)_双向绑定_09

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

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

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

2.3

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

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

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

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

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

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

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

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

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

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

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

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

Android - DataBinding源码解读(内存消耗和双向绑定原理分析)_Android_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);
}

 

 

 

 

 

标签:TextViewBindingAdapter,databinding,源码,user,DataBinding,Android,null,adapters,and
From: https://blog.51cto.com/u_12853553/5896514

相关文章

  • Android Error: java.lang.IllegalArgumentException: You cannot start a load for a
    一:Glide有时会出现这样的异常:java.lang.IllegalArgumentException:Youcannotstartaloadforadestroyedactivityatcom.bumptech.glide.manager.RequestManagerRe......
  • Android:性能优化工具之内存泄露-LeakCanary
    目录​​一简介​​​​二使用​​​​三进阶用法​​​​四hprof分析复杂内存泄露问题​​​​五使用小结​​​​六使用踩坑​​​​6.1权限​​​​6.2 NullPoi......
  • Android 调试桥:adb的入门与最佳实践(无线连接调试)
    商业转载请联系作者获得授权,非商业转载请注明出处。目录​​一adb简介​​​​二 adb工作原理​​​​三配置adb环境​​​​四常用的adb命令​​​​4.1help命令​​......
  • Android手把手,发布开源组件至 MavenCentral仓库
    一前言有时候,在我们写了一个组件想将之开源给更多人分享和使用时,就需要我们发布开源组件到公开的远程仓库,如Jitpack、JenCenter、MavenCentral。其中,MavenCentral是最......
  • Android:按下与提起的状态background
    <?xmlversion="1.0"encoding="utf-8"?><selectorxmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:drawable="@color/btn_pressed_green_......
  • Function源码解析与实践
    作者:陈昌浩1导读if…else…在代码中经常使用,听说可以通过Java8的Function接口来消灭if…else…!Function接口是什么?如果通过Function接口接口消灭if…else…呢?让我们一......
  • 使用LRU算法缓存图片,android 3.0
    在您的UI中显示单个图片是非常简单的,如果您需要一次显示很多图片就有点复杂了。在很多情况下(例如使用ListView,GridView或者 ​​​ViewPager​​​控件),显示在屏幕......
  • android viewgroup事件分发机制
    1、案例首先我们接着上一篇的代码,在代码中添加一个自定义的LinearLayout:[java]​​viewplain​​​​copy​​package  importimportimportimportimpor......
  • android混淆
    为了防止自己的劳动成果被别人窃取,混淆代码能有效防止被反编译,下面来总结以下混淆代码的步骤:1.大家也许都注意到新建一个工程会看到项目下边有这样proguard-project.txt......
  • android自定义view实现progressbar的效果
    一键清理是很多Launcher都会带有的功能,其效果也比较美观。实现方式也许有很多中,其中常见的是使用图片drawable来完成的,具体可以参考这篇文章:​​模仿......