五分钟理解–databinding

databinding

是一个数据绑定库,借助该库,您可以使将数据源用声明性格式绑定到界面上。

功能非常强大,支持以下的功能。

  • 表达式语言。
  • 事件处理(调用方法、设置监听器)
  • 导入类、声明变量
  • 使用可观察的数据对象
  • 生成绑定类
  • 绑定适配器
  • 双向数据绑定

看以下生成绑定方法的调用

//绑定过程   
binding = DataBindingUtil.inflate(inflater,R.layout.fragment_notifications,container,false)

//在DataBindingUtil文件中
	//这里生成了DataBinderMapperImpl对象。这一步就加入了编译时生成的类
    private static DataBinderMapper sMapper = new DataBinderMapperImpl();

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

//androidx.databinding.DataBinderMapperImpl
//这个类是androidx下面的类,属于库
public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
      //这里可以看到,初始化的时候就加入了编译时期生成的类文件。
    addMapper(new com.joye.wapp.DataBinderMapperImpl());
  }
}
复制代码

接下里查看以下new com.joye.wapp.DataBinderMapperImpl()这个编译时期生成的类

public class DataBinderMapperImpl extends DataBinderMapper {
  private static final int LAYOUT_FRAGMENTDASHBOARD = 1;
  private static final int LAYOUT_FRAGMENTNOTIFICATIONS = 2;
  private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(2);
  static {
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.joye.wapp.R.layout.fragment_dashboard, LAYOUT_FRAGMENTDASHBOARD);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.joye.wapp.R.layout.fragment_notifications, LAYOUT_FRAGMENTNOTIFICATIONS);
  }

  @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_FRAGMENTDASHBOARD: {
          if ("layout/fragment_dashboard_0".equals(tag)) {
            return new FragmentDashboardBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for fragment_dashboard is invalid. Received: " + tag);
        }
        case  LAYOUT_FRAGMENTNOTIFICATIONS: {
          if ("layout/fragment_notifications_0".equals(tag)) {
            return new FragmentNotificationsBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for fragment_notifications is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View[] views, int layoutId) {
    if(views == null || views.length == 0) {
      return null;
    }
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = views[0].getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
      }
    }
    return null;
  }

  @Override
  public int getLayoutId(String tag) {
    if (tag == null) {
      return 0;
    }
    Integer tmpVal = InnerLayoutIdLookup.sKeys.get(tag);
    return tmpVal == null ? 0 : tmpVal;
  }

  @Override
  public String convertBrIdToString(int localId) {
    String tmpVal = InnerBrLookup.sKeys.get(localId);
    return tmpVal;
  }

  @Override
  public List<DataBinderMapper> collectDependencies() {
    ArrayList<DataBinderMapper> result = new ArrayList<DataBinderMapper>(2);
    result.add(new androidx.databinding.library.baseAdapters.DataBinderMapperImpl());
    result.add(new com.joye.commonlibrary.DataBinderMapperImpl());
    return result;
  }

  private static class InnerBrLookup {
    static final SparseArray<String> sKeys = new SparseArray<String>(4);

    static {
      sKeys.put(0, "_all");
      sKeys.put(1, "user");
      sKeys.put(2, "user2");
      sKeys.put(3, "user3");
    }
  }

  private static class InnerLayoutIdLookup {
    static final HashMap<String, Integer> sKeys = new HashMap<String, Integer>(2);

    static {
      sKeys.put("layout/fragment_dashboard_0", com.joye.wapp.R.layout.fragment_dashboard);
      sKeys.put("layout/fragment_notifications_0", com.joye.wapp.R.layout.fragment_notifications);
    }
  }
}
复制代码

这类在静态代码块中把layout文件的id保存到SparseIntArray中去了。然后在getDataBinder()方法中根据布局文件id初始化对应binding对象,初始化参数是一个DataBindingComponent 和一个view。

我们来看一下第二个初始化的binding对象FragmentNotificationsBindingImpl

public class FragmentNotificationsBindingImpl extends FragmentNotificationsBinding  {

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = new android.util.SparseIntArray();
        sViewsWithIds.put(R.id.imageView, 2);
        sViewsWithIds.put(R.id.view_line, 3);
        sViewsWithIds.put(R.id.setting_changyong, 4);
        sViewsWithIds.put(R.id.setting_shoucang, 5);
        sViewsWithIds.put(R.id.setting_jifen, 6);
        sViewsWithIds.put(R.id.setting_about, 7);
    }
    // views
    @NonNull
    private final androidx.constraintlayout.widget.ConstraintLayout mboundView0;
    // variables
    // values
    // listeners
    // Inverse Binding Event Handlers

    public FragmentNotificationsBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 8, sIncludes, sViewsWithIds));
    }
    private FragmentNotificationsBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            , (android.widget.ImageView) bindings[2]
            , (com.joye.commonlibrary.view.SettingView) bindings[7]
            , (com.joye.commonlibrary.view.SettingView) bindings[4]
            , (com.joye.commonlibrary.view.SettingView) bindings[6]
            , (com.joye.commonlibrary.view.SettingView) bindings[5]
            , (android.widget.TextView) bindings[1]
            , (android.view.View) bindings[3]
            );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.tvImgText.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x8L;
        }
        requestRebind();
    }

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.user3 == variableId) {
            setUser3((com.joye.wapp.ui.me.User) variable);
        }
        else if (BR.user == variableId) {
            setUser((com.joye.wapp.ui.me.User) variable);
        }
        else if (BR.user2 == variableId) {
            setUser2((com.joye.wapp.ui.me.User) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }

    public void setUser3(@Nullable com.joye.wapp.ui.me.User User3) {
        this.mUser3 = User3;
    }
    public void setUser(@Nullable com.joye.wapp.ui.me.User User) {
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x2L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }
    public void setUser2(@Nullable com.joye.wapp.ui.me.User User2) {
        this.mUser2 = User2;
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
        }
        return false;
    }

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userFirstName = null;
        com.joye.wapp.ui.me.User user = mUser;

        if ((dirtyFlags & 0xaL) != 0) {



                if (user != null) {
                    // read user.firstName
                    userFirstName = user.getFirstName();
                }
        }
        // batch finished
        if ((dirtyFlags & 0xaL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvImgText, userFirstName);
        }
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
        flag 0 (0x1L): user3
        flag 1 (0x2L): user
        flag 2 (0x3L): user2
        flag 3 (0x4L): null
    flag mapping end*/
    //end
}
复制代码

在静态代码块中保存了xml中的R.id文件到数组中。然后在构造方法中把这些SparseArray转成对应的对象调用父类的构造方法进行初始化。这个地方是个黑科技,无法看到父类的实现。

可以看到这个类中为在xml中每一个引入的变量都生成了set方法,这里只列出一个。

 public void setUser3(@Nullable com.joye.wapp.ui.me.User User3) {
        this.mUser3 = User3;
    }
 //这个方法完成了对变量的初始化。
 protected void executeBindings(){}
复制代码

值得注意的一点,初始化完成触发了requestRebind() 方法。

触发了mRebindRunnable, 首先取消注册ViewDataBinding注册的观察者。触发从新绑定回调。

google用了黑科技地方

  • 编译时生成了一个包名时androidx.databinding的类—-DataBinderMapperImpl 。它在构造函数中用addMapper(new com.joye.wapp.DataBinderMapperImpl()); 把另外一个app生成mapper保存。
public class DataBindingUtil {
	private static DataBinderMapper sMapper = new DataBinderMapperImpl();
}
复制代码

DataBindingUtil这类是Androidx.databinding中的一个类,不是动态生成。所以这里的DataBinderMapperImpl这个类是提前写好的java代码,但是这个时候DataBinderMapperImpl还没有动态生成。一半不是会报错的么。应该是做了处理了。

  • 每一个bingding对象无法打开它的父类,父类的构造函数都看不了,点击父类都直接跳转到xml了。
  • 如何解析layout数据的,在几个依赖库中也没有找到。

这么多技法,不太常规,就不看具体细节了,说一下一些具体的类。

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);
}
复制代码

这个抽象类的作用主要是保存layout id和对应binding对象的关系,可以根据id生成binding对象。一半情况下会有三个类派生自这个类。

  • app编译生成DataBinderMapperImpl,它维护项目中layout id和对应binding对象之间的关系。
  • databinding包中自带MergedDataBinderMapper,它有一个CopyOnWriteArrayList数组保存DataBinderMapper对象。
  • 动态生成的DataBinderMapperImpl(继承自MergedDataBinderMapper)它的包名是androidx.databinding,它在构造函数包app的mapper添加进去。DataBindingUtil就是只用的这个类。

databinding.png

总结一下动态生成的重要的类。

  • 每个xml对应的XXBindingImpl对象,这个类的父类方法不可见,各个变量定义位置不可见,有xml中对象的set方法。用静态代码块的方式保存了xml的id,在构造方法中用这些id信息生成对象。
  • 以app包名作为包名的DataBinderMapperImpl(后面简称app的DataBinderMapperImpl)保存了每一个layout id和对应XXBindingImpl,对外提供访问方式。
  • 以androidx.databinding作为包名的DataBinderMapperImpl,在构造器中把app的DataBinderMapperImpl添加给自己。

这种这设计方式可能会成为一种统一标准。

app只做简单配置,根据配置信息把信息写入到静态变量中去。在构造函数中根据静态信息生成对象。

然后对这些对象和其他的业务建立关系。

库文件指定一个后期动态生成的类名。然后用这个未来的类做相关开发。

生成指定类,并且生成对象的时候就需要保存需要的信息。用这个约定的类就能使用未来编译之后才能生成的信息。

我暂时把这中方式称作是中间类法。

它可以无感知提供一些功能。可以说是一种高级的,封闭的动态生成代码方式,先动态生成代码,整理好相关信息,然后通过中间类,用合适的方式把这些功能或者信息暴漏。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享