悲惨的起源
首先DataBinding
已经出现很久了,不过大部分人都不太待见,都选择其他的例如ButterKnife
等(不过这东西不更新了哦亲~~),或者原式的findview
也好,只能感叹一句生不逢时,而且大部分人也反感在xml
里面写逻辑,但是他的双向绑定让我们能视图和数据保持一致不需要去处理很麻烦的set啊等等。
PS:如果你只是想省点findView
当我没说,毕竟很多人对双向绑定很反感,不过我比较推荐使用新的ViewBinding
,他用起来比Databinding
简单而且能高效。
原理
不讲用法了,满大街都是自己去看,还不懂建议百度。
首先了解一件事情,Databinding
在使用后会生产以下几个,几个XML,binding.java,BR文件类似R,1个DataBinderMapper
XML
首先讲2个XML
一个XML是去掉了<layout>
,<data>
的生成的XML,这个就不展示了自己去找,就和我们普通的XML一样,还有一个在/build/intermediates/data_binding_info_type/../../../out/..xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout bindingClass="MainActivityDataBinding" directory="layout"
filePath="。。。此处手动修改马赛克\app\src\main\res\layout\activity_main.xml"
isMerge="false" layout="activity_main" modulePackage="com.example.mydatabindinglearning">
<ClassNameLocation endLine="5" endOffset="39" startLine="5" startOffset="17" />
<Variables name="dataBean" declared="true" type="com.example.mydatabindinglearning.DataBean">
<location endLine="9" endOffset="63" startLine="7" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0"
view="androidx.constraintlayout.widget.ConstraintLayout">
<Expressions />
<location endLine="25" endOffset="55" startLine="12" startOffset="4" />
</Target>
<Target tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="dataBean.firstName">
<Location endLine="20" endOffset="47" startLine="20" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="20" endOffset="45" startLine="20" startOffset="28" />
</Expression>
</Expressions>
<location endLine="24" endOffset="55" startLine="17" startOffset="8" />
</Target>
</Targets>
</Layout>
Variables
你input的东西,targets就是你之前的view啊什么的,还告知了绑定关系不,Expressions 里面有个text可以看到我绑定了个。
binding
之后是一个binding.java
和一个bindingImpl.java
举个例子
MainActivityDataBinding
我的这个在/build/generated/data_binding_base_source_out/下面很多层
public abstract class MainActivityDataBinding extends ViewDataBinding {
@NonNull
public final TextView tvText;
@Bindable
protected DataBean mDataBean;
protected MainActivityDataBinding(Object _bindingComponent, View _root, int _localFieldCount,
TextView tvText) {
super(_bindingComponent, _root, _localFieldCount);
this.tvText = tvText;
}
public abstract void setDataBean(@Nullable DataBean dataBean);
@Nullable
public DataBean getDataBean() {
return mDataBean;
}
@NonNull
public static MainActivityDataBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot) {
return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
}
//此处有很多的省略 ヾ( ̄ー ̄)X(^▽^)ゞ
}
可从上面看到,有绑定的参,有绑定的ID的VIEW等等,VIEWID的是public
的可以直接在外面调用,Binding
内部主要负责双向绑定的功能。
还有1个impl.java
在androidX
下面
BR
BR文件如果你用了androidX会有2个BR,androidX的databinding单独生成了一个,目录/build/generated/ap_generated_sources/自己点下去
。
public class BR {
public static final int _all = 0;
public static final int dataBean = 1;
}
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 {
INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.mydatabindinglearning.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
}
@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 MainActivityDataBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
//很多的省略
}
以上就是我们build会自动帮我们生成的文件那马下面来看看绑定的过程。
init 初始化
DataBindingUtil.setContentView(this,R.layout....);
这个是最基础的绑定方法我们可以点进去看发现是这样的
// @Nullable don't annotate with Nullable. It is unlikely to be null and makes using it from
// kotlin really ugly. We cannot make it NonNull w/o breaking backward compatibility.
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
这个方法,其次他有个bindToAddedViews
里面有个bind
方法,会看到个
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);
}
sMapper
???这就是值钱我们说的自动生成的类,你可以在里面看到绑定的具体方法。
下面列出代码
@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 MainActivityDataBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
进入到MainActivityDataBindingImpl
里面有个mapBindings
,里面代码很多简单来说就是把bindings
数组强转为我们的控件类型。
PS:也就是说所有的布局控件都会放到一个数组对象中,那么这个数组对象大小是不定的,如果你有多个activity就会存在多个数组对象,这是比较占用内存的。
回到MainActivityDataBindingImpl
的构造函数,之后是invalidateAll
>> requestRebind
/**
* @hide
*/
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
final LifecycleOwner owner = this.mLifecycleOwner;
if (owner != null) {
Lifecycle.State state = owner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
//USE_CHOREOGRAPHER判断版本的 小于16的走另外一套 不看了,因为。。现在没有小于19的不需反驳
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
/**
* Runnable executed on animation heartbeat to rebind the dirty Views.
*/
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
mUIThreadHandler
emmmmmmmm貌似知道了什么,更新都是通过handler
通知view
进行更新的,handler
内部拥有一个Looper
对象(这里摆出他的创建语句mUIThreadHandler = new Handler(Looper.myLooper());
),是不断的在执行消息循环。
processReferenceQueue
是删除监听器的,重点关注executePendingBindings
他最后会执行会impl
里面的executeBindings
,很长反正你也不会看不举例了,直接executeBindings
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
androidx.databinding.ObservableField<java.lang.String> dataBeanFirstName = null;
com.example.mydatabindinglearning.DataBean dataBean = mDataBean;
java.lang.String dataBeanFirstNameGet = null;
if ((dirtyFlags & 0x7L) != 0) {
if (dataBean != null) {
// read dataBean.firstName
dataBeanFirstName = dataBean.firstName;
}
updateRegistration(0, dataBeanFirstName);
if (dataBeanFirstName != null) {
// read dataBean.firstName.get()
dataBeanFirstNameGet = dataBeanFirstName.get();
}
}
// batch finished
if ((dirtyFlags & 0x7L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvText, dataBeanFirstNameGet);
}
}
emmmmmmmmmmm dataBeanFirstName.get
获取Model的字段值,然后setText
显示出来,破费
网友评论