databinding的原理简单分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了databinding的原理简单分析相关的知识,希望对你有一定的参考价值。

参考技术A databinding用来实现 vm层和v层的双向绑定关系;

根据xml布局文件生成了 编译后的布局文件,把所有设置id的view都添加一个 字符串tag;tag的名称 是例如:layout/布局名字+数字

每个编译后的布局文件 都生成 一个对应的BindingImpl类,持有布局文件中的view对象,id信息和设置bean对象;BindingImpl类的名称 是例如:布局文件名称+BindingImpl

生成一个DataBinderMapperImpl类,DataBinderMapperImpl类持有所有的已经生成的 BindingImpl类;
DataBindUtil持有一个 sMapper对象,这个对象就是编译时期生成的DataBinderMapperImpl;

调用setContentView或者inflate 传入layoutId 加载布局时,调用的还是原生的加载方法,生成布局view;

见 ItemKDPageBindingImpl 的setKnowledgeBean方法。
调用binding 对象的赋值方法,
然后调用requestRebind方法然后保证切换到主线程 (使用Handler或者Choreographer.getInstance() )
最后调用executeBindings 对view进行赋值和绑定监听。

更多

DataBinding的双向绑定实现原理


“ 悄悄咪咪告诉你,DataBinding是怎么实现双向绑定的“

在讲DataBinding之前,有必要讲讲ViewBinding

1、ViewBinding

1) 配置

要使用ViewBinding,只需要在gradle 添加如下配置即可

android 
        ...
        viewBinding 
            enabled = true
        
 

如果需要在生成绑定类时忽略某个布局文件,请将 tools:viewBindingIgnore=“true” 属性添加到相应布局文件的根视图中:

<LinearLayout
        ...
            tools:viewBindingIgnore="true" >
        ...
</LinearLayout>

2)用法

在创建xml文件后,Android Studio会自动创建对应的类,类名格式为:XML 文件的名称转换为驼峰式大小写,并在末尾添加Binding一词。

比如,创建了一个activity_view.xml

<LinearLayout 
           ...>
    <TextView
        android:id="@+id/viewBindingView"
        android:layout_width="match_parent"
        android:layout_height="20dp" />
</LinearLayout>

生成的绑定类就叫做ActivityViewBinding,然后在对应的activity中使用布局绑定


public class ViewBindingActivity extends AppCompatActivity 
    private ActivityViewBinding activityViewBinding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        // 初始化布局和view
        activityViewBinding = ActivityViewBinding.inflate(getLayoutInflater());
        // 设置contentView
        setContentView(activityViewBinding.getRoot());
        // 获取到id为viewBindingView的textView,并设置文本
        activityViewBinding.viewBindingView.setText("你好,我是viewBinding");
    


3) 原理

在build之后,会在如下路径生成对应的类

生成如下代码

public final class ActivityViewBinding implements ViewBinding 
    @NonNull
    private final LinearLayout rootView;
    // 通过遍历xml,找到声明id了的view,id作为变量名
    @NonNull
    public final TextView viewBindingView;

    private ActivityViewBinding(@NonNull LinearLayout rootView, @NonNull TextView viewBindingView) 
        this.rootView = rootView;
        this.viewBindingView = viewBindingView;
    

    @Override
    @NonNull
    public LinearLayout getRoot() 
        return rootView;
    

    // 传入LayoutInflater,用于获取对应布局的view
    @NonNull
    public static ActivityViewBinding inflate(@NonNull LayoutInflater inflater) 
        return inflate(inflater, null, false);
    

    @NonNull
    public static ActivityViewBinding inflate(@NonNull LayoutInflater inflater,
                                              @Nullable ViewGroup parent, boolean attachToParent) 
        // 通过指定的布局获取到rootView
        View root = inflater.inflate(R.layout.activity_view, parent, false);
        if (attachToParent) 
            parent.addView(root);
        
        return bind(root);
    
    // 核心代码,通过代码模板,找到对应的view
    @NonNull
    public static ActivityViewBinding bind(@NonNull View rootView) 
        int id;
        missingId:
        
            id = R.id.viewBindingView;
            TextView viewBindingView = rootView.findViewById(id);
            if (viewBindingView == null) 
                break missingId;
            
            return new ActivityViewBinding((LinearLayout) rootView, viewBindingView);
        
        String missingId = rootView.getResources().getResourceName(id);
        throw new NullPointerException("Missing required view with ID: ".concat(missingId));
    

生成的类通过LayoutInflater获取布局,然后依次初始化view。

2、DataBinding

1)配置

DataBinding 跟ViewBinding一样,只需要在gradle中添加如下配置即可

android 
        ...
        dataBinding 
            enabled = true
        
    

2)用法

我们先看activity_main.xml布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="video"
            type="com.example.databinding.VideoBean" />
    </data>
    <LinearLayout xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/bind_text1"
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:gravity="center"
            android:text="@video.title" />

        <Button
            android:id="@+id/bind_text2"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@String.valueOf(video.score + 1)" />

    </LinearLayout>
</layout>

与正常的xml布局不一样的是,DataBinding 以layout作为第一层。第二层分别是 data和view的根布局。

其中data下的name作为对象,type为类路径名

package com.example.databinding;
public class VideoBean 
    public String title;
    public int score;
    public VideoBean(String title, int score) 
        this.title = title;
        this.score = score;
    

然后在布局中就可以把对象的属性值显示到TextView上

<TextView
     ...
     android:text="@video.title" />

在对应的activity中,使用的方式与ViewBinding类似

public class MainActivity extends AppCompatActivity 
    private ActivityMainBinding mainBinding;
    private VideoBean video;

    @Override
    protected void onCreate(@Nullable  Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        // 获取对应的binding类
        mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mainBinding.getRoot());
        video = new VideoBean("小黄人", 8);
        // 设置对应的数据
        mainBinding.setVideo(video);
        // 设置周期监听,可选
        mainBinding.setLifecycleOwner(this);
    

全程我们没有初始化对应的view,只是获取bean,然后更新一下即可

当然,我们也可以在布局中添加对应的事件响应

      <Button
            android:id="@+id/bind_text2"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:onClick="@video::increaseScore"
            android:text="@String.valueOf(video.score + 1)" />

在类中添加 increaseScore方法,如下所示,即可实现点击button修改score的值,并更新ui。

public class VideoBean extends BaseObservable 
    public String title;
    // 声明该属性是可绑定监听的
    @Bindable
    public int score;
    public VideoBean(String title, int score) 
        this.title = title;
        this.score = score;
    

    public void increaseScore(View view) 
        score++;
        // 通知score属性变化了
        notifyPropertyChanged(BR.score);
    


我们只需要修改少量代码即可实现双向绑定,接下来将会讲解对应的原理。

更多见布局和绑定表达式

3)原理

view的初始化

生成的类路径与ViewBinding一样,直接看看对应的类

// 抽象类
public abstract class ActivityMainBinding extends ViewDataBinding 
  @NonNull
  public final Button bindText2;
  @Bindable
  protected VideoBean mVideo;
  protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
                                TextView bindText1, Button bindText2) 
    super(_bindingComponent, _root, _localFieldCount);
    this.bindText1 = bindText1;
    this.bindText2 = bindText2;
  

  // 把data下的参数都声明了get/set方法
  public abstract void setVideo(@Nullable VideoBean video);

  @Nullable
  public VideoBean getVideo() 
    return mVideo;
  

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) 
    return inflate(inflater, DataBindingUtil.getDefaultComponent());
  

  @NonNull
  @Deprecated
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
                                            @Nullable Object component) 
    return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);
  
  ...

这里的ActivityMainBinding 继承了ViewDataBinding,但是内部却没有类似ViewBinding那样,直接通过findViewById方式初始化我们需要的view,那这些view是怎么来的?

这个问题我们先放一放,上面的inflate方法调用了ViewDataBinding的inflateInternal 方法

public abstract class ViewDataBinding extends BaseObservable implements ViewBinding 
    ...
    protected static <T extends ViewDataBinding> T inflateInternal(
            @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
            boolean attachToParent, @Nullable Object bindingComponent) 
        return DataBindingUtil.inflate(
                inflater,
                layoutId,
                parent,
                attachToParent,
                checkAndCastToBindingComponent(bindingComponent)
        );
    

来到DataBindingUtil 这个工具类

public class DataBindingUtil 
   private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    
   public static <T extends ViewDataBinding> T inflate(
            @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
            boolean attachToParent, @Nullable DataBindingComponent bindingComponent) 
        final boolean useChildren = parent != null && attachToParent;
        final int startChildren = useChildren ? parent.getChildCount() : 0;
        final View view = inflater.inflate(layoutId, parent, attachToParent);
        if (useChildren) 
            return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
         else 
            // activity里面传的parent为null ,所以会走这里
            return bind(bindingComponent, view, layoutId);
        
    
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) 
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    


DataBinderMapperImpl 的实现如下

package androidx.databinding;

public class DataBinderMapperImpl extends MergedDataBinderMapper 
  DataBinderMapperImpl() 
    // 将impl添加到mMappers中
    addMapper(new com.example.databinding.DataBinderMapperImpl());
  

// 对应的基类
public class MergedDataBinderMapper extends DataBinderMapper 
    .... 
    @Override
    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId) 
        // 遍历 mMappers
        for(DataBinderMapper mapper : mMappers) 
            ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
            if (result != null) 
                return result;
            
        
        if (loadFeatures()) 
            return getDataBinder(bindingComponent, view, layoutId);
        
        return null;
    


那就看看com.example.databinding.DataBinderMapperImpl 的实现

public class DataBinderMapperImpl extends DataBinderMapper 
  private static final int LAYOUT_ACTIVITYMAIN = 1;

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

  static 
    // 保存我们创建的layout与 index的映射
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.databinding.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
  

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) 
    // 通过layoutId,获取到index,上面传的是R.layout.activity_main,
    // 所以这里localizedLayoutId为LAYOUT_ACTIVITYMAIN
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) 
       // 获取到TAG,上面的代码没有设置tag的地方,但是通过debug得知对应的tag为"layout/activity_main_0"
      final Object tag = view.getTag();
      if(tag == null) 
        throw new RuntimeException("view must have a tag");
      
      switch(localizedLayoutId) 
        case  LAYOUT_ACTIVITYMAIN: 
          if ("layout/activity_main_0".equals(tag)) 
            return new ActivityMainBindingImpl(component, view);
          
          throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
        
      
    
    return null;
  
  ...

至于view的tag是怎么来的,笔者猜测是Android Studio自动添加的

最终我们获取到了ActivityMainBindingImpl的实例,对应实现如下

public class ActivityMainBindingImpl extends ActivityMainBinding  
    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static 
        sIncludes = null;
        sViewsWithIds = null;
    
    // views
    @NonNull
    private final android.widget.LinearLayout mboundView0;
    // listeners
    private OnClickListenerImpl mVideoIncreaseScoreAndroidViewViewOnClickListener;
    // Inverse Binding Event Handlers

    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) 
        // 通过mapBindings方法获取view的数组
        this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
    
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) 
        // 调用super即ActivityMainBinding的构造函数初始化view
        super(bindingComponent, root, 0
            , (android.widget.TextView) bindings[1]
            , (android.widget.Button) bindings[2]
            );
        this.bindText1.setTag(null);
        this.bindText2.setTag(null);
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        setRootTag(root);
        invalidateAll();
    
    ...
    public void setVideo(@Nullable com.example.databinding.VideoBean Video) 
        this.mVideo = Video;
        synchronized(this) 
            mDirtyFlags |= 0x1L;
        
        notifyPropertyChanged(BR.video以上是关于databinding的原理简单分析的主要内容,如果未能解决你的问题,请参考以下文章

可以将 ViewModel 传递给 RecyclerView(无 DataBinding)吗?

Android JetPack组件之DataBinding的使用详解

Android-DataBinding原理分析

Databinding+LiveData轻松实现无重启换肤

JsonUtils

数据绑定流程分析