• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Android JetPackDataBinding源码

武飞扬头像
惜许
帮助1

前言

数据绑定库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

DataBinding支持双向绑定,数据变化的时候界面跟着变化,界面变化也同步给数据;

DataBindingMVVM模式中使用比较多,双向绑定机制实现了View和Model的同步更新。

简单使用

DataBinding一般配合LiveDataViewModel一起使用,这里就简单使用下,便于后续源码分析;

  • build.gradle配置
    buildFeatures {
        dataBinding true
    }
  • 定义数据源
data class User(@Bindable var username: String = "", @Bindable var pwd: String = "") : BaseObservable()
  • 定义数据源绑定的布局文件activity_data_binding.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <!--data标签内定义数据源User类-->
        <variable
            name="User"
            type="com.xixu.jetpack.User" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@ id/tvName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{User.username}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


        <TextView
            android:id="@ id/tvPwd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@{User.pwd}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@ id/tvName" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
学新通
  • 定义Activity使用
class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val dataBinding = DataBindingUtil.setContentView<ActivityDataBindingBinding>(
            this,
            R.layout.activity_data_binding
        )

        lifecycleScope.launch{
            delay(2000)
            dataBinding.user = User("XiXu", "123456")
        }

    }
}
学新通

界面效果:延迟2s后,分别将XiXu123456数据绑定到tvNametvPwd控件上;

源码分析

DataBinding是如何实现将数据绑定到具体视图上的呢?

DataBinding为我们生成了哪些布局文件

首先,DataBinding会使用APT(Annotation Processing Too,注解解析器),在编译器为我们生成如下布局文件
由于使用DataBinding,布局文件中引入了layout标签,我们先看下布局文件变化;

1.build/intermediates/incremental/packageDebugResources/stripped.dir/layout/activity_data_binding.xml,为每个控件都新增了tag属性;
学新通
2.build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_data_binding-layout.xml
定义了多组Target与布局文件中的tag标签控件对应,并标注了每个tag对应控件的view类型,Expressions标签中定义了控件属性对应绑定数据,其中TwoWay标签表示是否是双向绑定;
学新通

小结

使用DataBinding会在编译期生成辅助布局文件,为每个控件新增tag标签,并记录控件类型id属性等信息;

DataBindingUtil.setContentView()做了什么

    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId) {
        return setContentView(activity, layoutId, sDefaultComponent);
    }

    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId, @Nullable DataBindingComponent bindingComponent) {
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    }

调用setContentView方法主要做了如下几件事:

  1. 调用activity对应的setContentView方法绑定布局;
  2. 获取activity对应的R.id.content控件,我们知道即FrameLayout控件;
  3. 调用bindToAddedViews绑定布局中的控件;

这里我们重点看下bindToAddedViews是如何实现布局控件绑定的;

   private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {
        final int endChildren = parent.getChildCount();
        final int childrenAdded = endChildren - startChildren;
        if (childrenAdded == 1) {
            final View childView = parent.getChildAt(endChildren - 1);
            return bind(component, childView, layoutId);
        } else {
            final View[] children = new View[childrenAdded];
            for (int i = 0; i < childrenAdded; i  ) {
                children[i] = parent.getChildAt(i   startChildren);
            }
            return bind(component, children, layoutId);
        }
    }

这里parent即指FrameLayout,因此parent.getChildCount()获取的便是根布局个数,上述例子对应的为1,即ConstraintLayout;最终会调用bind(component, childView, layoutId)方法如下:

    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }

其中sMapper初始化代码如下:

    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    
	public class DataBinderMapperImpl extends MergedDataBinderMapper {
    DataBinderMapperImpl() {
    addMapper(new com.crystal.maniu.DataBinderMapperImpl());
  }
}

因此sMapper.getDataBinder最终调用的为DataBinderMapperImpl【注意包名,与androidx.databinding区分】.getDataBinder方法:

	### DataBinderMapperImpl
  public class DataBinderMapperImpl extends DataBinderMapper {
  private static final int LAYOUT_ACTIVITYDATABINDING = 1;

  private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);

  static {
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.crystal.maniu.R.layout.activity_data_binding, LAYOUT_ACTIVITYDATABINDING);
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = view.getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYDATABINDING: {
          if ("layout/activity_data_binding_0".equals(tag)) {
            return new ActivityDataBindingBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_data_binding is invalid. Received: "   tag);
        }
      }
    }
    return null;
  }
 }
学新通

可见getDataBinder方法其实就是直接new了一个ActivityDataBindingBindingImpl【APT生成的类】对象,并把根布局ConstraintLayout入参;我们看看ActivityDataBindingBindingImpl构造方法里做了什么;

    public ActivityDataBindingBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
    }
    private ActivityDataBindingBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 1
            , (android.widget.TextView) bindings[1]
            , (android.widget.TextView) bindings[2]
            );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.tvName.setTag(null);
        this.tvPwd.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

学新通

其中mapBindings方法会对根据tag对布局文件进行xml解析得到控件数组 Object[],super会调用父类ActivityDataBindingBinding【APT生成的类】的构造方法,完成View的初始化工作 invalidateAll()会进行数据的初始化绑定,具体如何绑定我们下一步在分析!

小结

调用DataBindingUtil.setContentView方法会调用setContentView方法并完成控件的初始化工作,从而代替FindViewById的工作;

DataBinding如何实现数据更新

我们再看下调用dataBinding.user = User("XiXu", "123456")是如何实现将数据更新到控件上去的,具体实现交给ActivityDataBindingBindingImpl.setUser方法;

  public void setUser(@Nullable com.xixu.jetpack.User User) {
        updateRegistration(0, User);
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.User);
        super.requestRebind();
    }

其中BR文件如下,定义一系列常量用于区分更新字段:

public class BR {
  public static final int User = 1;

  public static final int _all = 0;

  public static final int pwd = 2;

  public static final int username = 3;
}

可以看到调用setUser主要做了如下几件事:

  1. 调用updateRegistration(0, User)方法;User作为被观察者,会先判断User是否为null,如果为null解除注册,否则会将User绑定到WeakListener虚引用对象上,并包装成WeakPropertyListener对象,用于后续处理数据更新操作;

  2. 调用notifyPropertyChanged(BR.User)方法;经过层层处理会执行WeakPropertyListener.onPropertyChanged方法如下:

    ### WeakPropertyListener.onPropertyChanged
   public void onPropertyChanged(Observable sender, int propertyId) {
   			//1.先判断绑定的ViewDataBinding是否为null
            ViewDataBinding binder = mListener.getBinder();
            if (binder == null) {
                return;
            }
            //2.obj即为传入的User对象,判断和之前绑定的是否一致
            Observable obj = mListener.getTarget();
            if (obj != sender) {
                return; 
            }
            //3.调用ViewDataBinding.onFieldChange方法;
            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
        }

ViewDataBinding.onFieldChange代码如下:

    protected void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        if (mInLiveDataRegisterObserver || mInStateFlowRegisterObserver) {
            return;
        }
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
        	//内部会进行Lifecycle生命周期判断,从而实现更新逻辑与生命周期绑定
            requestRebind();
        }
    }
	
    protected void requestRebind() {
        if (mContainingBinding != null) {
        	//设置包含绑定
            mContainingBinding.requestRebind();
        } else {
        	//这里会进行组件活跃判断,如果不是STARTED、RESUMED状态,不执行更新数据操作
            final LifecycleOwner owner = this.mLifecycleOwner;
            if (owner != null) {
                Lifecycle.State state = owner.getLifecycle().getCurrentState();
                if (!state.isAtLeast(Lifecycle.State.STARTED)) {
                    return;
                }
            }
            synchronized (this) {
                if (mPendingRebind) {
                    return;
                }
                mPendingRebind = true;
            }
            if (USE_CHOREOGRAPHER) {
            	//SDK_INT >= 16 使用编舞者处理,最终还是调用mRebindRunnable.run方法
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                mUIThreadHandler.post(mRebindRunnable);
            }
        }
    }

学新通

其中onFieldChange调用的是ActivityDataBindingBindingImpl.onFieldChange方法根据mLocalFieldId以及fieldId匹配BR文件中的常量,判断能否更新;

如果返回true,则调用requestRebind()方法,最终又会回到 ActivityDataBindingBindingImpl.executeBindings方法

  protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.xixu.jetpack.User user = mUser;
        java.lang.String userUsername = null;
        java.lang.String userPwd = 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.pwd
                        userPwd = user.getPwd();
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0xbL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userUsername);
        }
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvPwd, userPwd);
        }
    }
学新通

这里采用了取巧的方式处理,使用位运算操作计算要更新哪些控件,最终调用TextViewBindingAdapter.setText完成数据更新;

更新数据流程图学新通

总结

DataBinding通过APT技术于编译期生成相关辅助类,当调用DataBindingUtil.setContentView方法时帮助我们完成布局控件绑定工作,减少FindViewById操作,当调用数据更新操作时,数据作为被观察者,会绑定到WeakListener虚引用对象上,更新过程中会使用Lifecycle进行生命周期判断,最终通过调用androidx.databinding.adapters包下的辅助工具类完成控件更新操作;

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhefafba
系列文章
更多 icon
同类精品
更多 icon
继续加载