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就是只用的这个类。
总结一下动态生成的重要的类。
- 每个xml对应的XXBindingImpl对象,这个类的父类方法不可见,各个变量定义位置不可见,有xml中对象的set方法。用静态代码块的方式保存了xml的id,在构造方法中用这些id信息生成对象。
- 以app包名作为包名的DataBinderMapperImpl(后面简称app的DataBinderMapperImpl)保存了每一个layout id和对应XXBindingImpl,对外提供访问方式。
- 以androidx.databinding作为包名的DataBinderMapperImpl,在构造器中把app的DataBinderMapperImpl添加给自己。
这种这设计方式可能会成为一种统一标准。
app只做简单配置,根据配置信息把信息写入到静态变量中去。在构造函数中根据静态信息生成对象。
然后对这些对象和其他的业务建立关系。
库文件指定一个后期动态生成的类名。然后用这个未来的类做相关开发。
生成指定类,并且生成对象的时候就需要保存需要的信息。用这个约定的类就能使用未来编译之后才能生成的信息。
我暂时把这中方式称作是中间类法。
它可以无感知提供一些功能。可以说是一种高级的,封闭的动态生成代码方式,先动态生成代码,整理好相关信息,然后通过中间类,用合适的方式把这些功能或者信息暴漏。