JetPack之DataBinding源码解析

作者: 奔跑吧李博 | 来源:发表于2020-08-21 09:28 被阅读0次

在没有Databinding之前,布局文件通常只负责UI控件的布局工作,我们是在页面中通过id找到控件,接着在页面中通过代码对控件进行操作。为了减轻页面的工作量,google推出Databinding,让布局文件承担了部分原本属于页面的工作,也使页面与布局文件之间的耦合度进一步降低。

Databinding优势:
  • 项目更简洁,可读性更高。部分与UI控件相关的代码可以在布局文件中完成。
  • 不再需要findViewById()方法。
  • 布局文件可以包含简单的业务逻辑。UI控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互。
DataBinding生成布局管理文件

每个使用了databiding的xml文件经过编译之后,都会通过Apt(annotation-processing-tool)注解处理器生成相应的java文件,文件名为xml文件名加上Binding后缀,这个类会继承于ViewDataBinding。它是该xml文件view的管理类,我们可以通过操作这个文件来对xml文件生成的view进行操作。

比如activity_main.xml会生成对应的ActivityMainBinding.java文件。生成路径为:


抽象类ActivityMainBinding代码:

public abstract class ActivityMainBinding extends ViewDataBinding {
  @NonNull
  public final TextView myTextview;

  protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
      TextView myTextview) {
    super(_bindingComponent, _root, _localFieldCount);
    this.myTextview = myTextview;
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot) {
    return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
  }

  @NonNull
  @Deprecated
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) {
    return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, root, attachToRoot, component);
  }

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

  public static ActivityMainBinding bind(@NonNull View view) {
    return bind(view, DataBindingUtil.getDefaultComponent());
  }

  @Deprecated
  public static ActivityMainBinding bind(@NonNull View view, @Nullable Object component) {
    return (ActivityMainBinding)bind(component, view, R.layout.activity_main);
  }
}

如何获取ActivityMainBinding对象

这里需要用到DataBindingUtil类获取ActivityMainBinding

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

DataBindingUtil中相关代码:

public class DataBindingUtil {
         private static DataBinderMapper sMapper = new DataBinderMapperImpl();

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

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

与activity中设置布局方式一样,内部也是调用了activity.setContentView(layoutId),然后获取activity的decorView,然后获取内层的contentView,调用bindToAddedViews方法,获取添加的子view,然后调用了bind(component, children, layoutId)方法。

    @SuppressWarnings("unchecked")
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }

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

该bind方法内部调用了sMapper.getDataBinder方法。

抽象类DataBinderMapper代码:

@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("WeakerAccess")
public abstract class DataBinderMapper {
    public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId);
    public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent,
            View[] view, int layoutId);
    public abstract int getLayoutId(String tag);
    public abstract String convertBrIdToString(int id);
    @NonNull
    public List<DataBinderMapper> collectDependencies() {
        // default implementation for backwards compatibility.
        return Collections.emptyList();
    }
}

getDataBinder方法的具体实现是在DataBinderMapperImpl中,实现代码如下:

  @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_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,然后初始化ActivityMainBindingImpl,查看layout下的activity_main.xml,根布局的tag就是"layout/activity_main_0",所以下一步进入ActivityMainBindingImpl的构造方法:

public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds));
    }

    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            , (android.widget.TextView) bindings[1]
            );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

再调用ViewDataBinding中的mapBindings方法:

    protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
            int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
        Object[] bindings = new Object[numBindings];
        mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
        return bindings;
    }

该方法内部用于解析xml文件中的内容返回一个bindings数组,该数组为解析之后的各个view。在xml中声明的view会在解析阶段之后存储在ActivityMainBindingImpl中,那么就能实现通过databinding获取到我们声明的控件了。

相关文章

网友评论

    本文标题:JetPack之DataBinding源码解析

    本文链接:https://www.haomeiwen.com/subject/varvrktx.html