Data Binding(数据绑定库)是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
所谓声明式UI,就是你在代码中做出的任何改变,都会实时的在界面中展示出来。与之对应的是命令式UI,当你想要改变界面时,必须调用XX.setText()之类的代码,才能使界面做出改变。
声明式/命令式
用传统的命令式UI,当要改变数据时,要如下操作:
findViewById(R.id.sample_text).apply {
text = viewModel.userName
}
而用数据绑定后,只需在xml中声明如下,无需任何逻辑代码,当username变化时,组件自动发生变化。
Data Binding并不仅仅是为了取代
findViewById()
。如果你的主要目的是取代findViewById()
调用,请考虑改用视图绑定【View Binding】 或者 直接用Kotlin。
Data Binding 使用
当然,如果直接在界面上这样写是没用的,得配置下。app:build.gradle中开启:
dataBinding {
enabled = true
}
xml界面更改为如下样式即可:
在Activity界面中获取binding,直接操作即可:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
Thread{
var i = 0
while (true){
Thread.sleep(1000)
//听说异步线程不能更新UI?sorry,我可以。因为我只改变了值,没有调用`更新UI`代码~
binding.msg = i++.toString()
}
}.start()
}
}
原理
主要是用到了APT(Annotation Processing Tool/注解处理工具)技术。在编译时对注解进行解析,自动生成代码,并编译代码生成class文件。
在项目的 build 目录下,可以看到生成的代码文件:
build/generated/source | build/intermediates/data_binding_xx |
---|---|
|
|
先看我们自己写的xml文件。像这种layout标签,不开启dataBinding的情况下编译器是铁定不识别的。所以我们有理由相信dataBinding后面肯定是将这个xml文件处理成了传统的xml布局格式。在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目录下,可以找到一个activity_main.xml文件,这个文件就是处理完后的xml文件了。
activity_main.xml
这个文件和我们自己写的布局文件后半部分很像,就只有一点不一样:在有用到dataBinding的地方,多了个tag标签--android:tag="binding_1"
。那前半部分标签去哪了呢?在第二张截图所展示目录下:
false
通过和我们自己写的xml对比着来看,可以看到
DataBindingUtil
再看我们的Java/Kotlin代码。DataBindingUtil.setContentView():
private static DataBindingComponent sDefaultComponent = null;
public static T setContentView(@NonNull Activity activity, int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
public static 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 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.setContentView(layoutId);
;然后获取decorView中的contentView,将contentView的子view传入bind方法中去【如果多个子view就用数组】。这个contentView中包裹的就是我们自己写的布局,所以第一次进入childCount为1,走第一个方法。 不过后面我还是把传view和传view[]这两个方法代码都贴上来了,注意区分。
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
static T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
static T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
bind方法又转到了sMapper的getDataBinder方法中去。sMapper是个DataBinderMapper对象。
DataBinderMapper
public class DataBinderMapperImpl extends MergedDataBinderMapper {
DataBinderMapperImpl() {
addMapper(new com.cy.myapplication.DataBinderMapperImpl());
}
}
public class MergedDataBinderMapper extends DataBinderMapper {
public void addMapper(DataBinderMapper mapper) {
Class extends DataBinderMapper> mapperClass = mapper.getClass();
if (mExistingMappers.add(mapperClass)) {
mMappers.add(mapper);
final List dependencies = mapper.collectDependencies();
for(DataBinderMapper dependency : dependencies) {
addMapper(dependency);
}
}
}
}
DataBinderMapperImpl
继承MergedDataBinderMapper
继承DataBinderMapper
。DataBinderMapperImpl
就一个构造方法,getDataBinder方法的实现在MergedDataBinderMapper
中。
MergedDataBinderMapper
中又持有一个DataBinderMapper的数组:mMappers。该对象在new DataBinderMapperImpl的时候被填充进去数据,这个数据就是APT为我们生成的类。
public class MergedDataBinderMapper extends DataBinderMapper {
private List mMappers = new CopyOnWriteArrayList<>();
@Override
public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
int layoutId) {
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;
}
@Override
public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] view,
int layoutId) {
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;
}
}
所以不出所料的,MergedDataBinderMapper中的getDataBinder方法最终转交给了APT生成的Mapper中的getDataBinder。
APT-MergedDataBinderMapper
所以来到APT生成的MergedDataBinderMapper中:
//setContentView(activity, layoutId, sDefaultComponent);
//bindToAddedViews(bindingComponent, contentView, 0, layoutId);
//bind(component, contentView‘s children, layoutId);
//上面是之前一路走来的用到的方法,写这里免得大家忘了。
private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
static {
INTERNAL_LAYOUT_ID_LOOKUP.put(com.cy.myapplication.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 ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main 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;
}
到这里第一次进入传来的view就是带Tag的activity_main.xml,所以tag能对的上,返回的就是ActivityMainBindingImpl(component, view);
对了,到这的时候,component = null,view是Activity_main中的最外层view。
APT-ActivityMainBindingImpl
首先,这个类也是APT帮我们生成的。
@Nullable
private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
@Nullable
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = null;
sViewsWithIds = null;
}//上面是要用到的数据
//下面是构造方法
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);
this.text.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
mapBindings
这里用到了一个方法生成Object[]:由于mapBinding方法略长,所以没打算贴出来。
这个方法相当于是对我们传入的这个view进行一次解析,解析完成后返回的Object[]数组里面包含的就是这个view中所有的view对象。
然后在初始化方法中setTag中将数组中的对象打上标签tag并存起来。比如this.text就是我们布局文件中的textView,其id叫text
。所以我们能通过binding.text获得该控件。
APT-setMsg()
public void setMsg(@Nullable java.lang.String Msg) {
this.mMsg = Msg;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.msg);
super.requestRebind();
}
private long mDirtyFlags = 0xffffffffffffffffL;
/* flag mapping
flag 0 (0x1L): msg
flag 1 (0x2L): null
flag mapping end*/
这个mDirtyFlags值是后面用作判断修改哪个值用的。然后跟着notifyPropertyChanged()方法;
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
CallbackRegistry.notifyCallbacks()
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
mNotificationLevel++;
//--------------------------主要看这个方法----------
notifyRecurse(sender, arg, arg2);
mNotificationLevel--;
if (mNotificationLevel == 0) {
if (mRemainderRemoved != null) {
for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
final long removedBits = mRemainderRemoved[i];
if (removedBits != 0) {
removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
mRemainderRemoved[i] = 0;
}
}
}
if (mFirst64Removed != 0) {
removeRemovedCallbacks(0, mFirst64Removed);
mFirst64Removed = 0;
}
}
}
private void notifyRecurse(T sender, int arg, A arg2) {
final int callbackCount = mCallbacks.size();
final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
// Now we've got all callbakcs that have no mRemainderRemoved value, so notify the
// others.
//----------------------------看这个方法!!!-------------
notifyRemainder(sender, arg, arg2, remainderIndex);
// notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
// However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
// The remaining have no bit set
notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
}
private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
if (remainderIndex < 0) {
//-----------------------走这个!!!!!!!!!--------------------
notifyFirst64(sender, arg, arg2);
} else {
final long bits = mRemainderRemoved[remainderIndex];
final int startIndex = (remainderIndex + 1) * Long.SIZE;
final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
notifyRemainder(sender, arg, arg2, remainderIndex - 1);
notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits);
}
}
private void notifyFirst64(T sender, int arg, A arg2) {
final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
}
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
final int endIndex, final long bits) {
long bitMask = 1;
for (int i = startIndex; i < endIndex; i++) {
if ((bits & bitMask) == 0) {
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
}
bitMask <<= 1;
}
}
PropertyChangeRegistry.NotifierCallback.onNotifyCallback
private static final NotifierCallback NOTIFIER_CALLBACK = new NotifierCallback() {
public void onNotifyCallback(OnPropertyChangedCallback callback, Observable sender, int arg, Void notUsed) {
callback.onPropertyChanged(sender, arg);
}
};
ViewBinding.onPropertyChanged
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver) {
// We're in LiveData registration, which always results in a field change
// that we can ignore. The value will be read immediately after anyway, so
// there is no need to be dirty.
return;
}
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}
这里会调用到onFieldChange方法,这个方法实现在imp类中:
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
}
return false;
}
返回true则认为数据发生了改变,走requestRebind方法:
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;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
最后要么走postFrameCallback,要么走mUIThreadHandler.post方法。其实两个方法是一样的,最终都是走的post方法中来,所以这里直接看mRebindRunnable。
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();
}
};
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
跟着executePendingBindings()
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
if (!hasPendingBindings()) {
return;
}
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
//----------------看这里!!!!!!!!!
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}
其中的executeBindings是抽象方法,实现在ActivityMainbindingImpl中:
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
com.cy.myapplication.Girl girl = mGirl;
java.lang.String girlName = null;
java.lang.String msg = mMsg;
if ((dirtyFlags & 0x5L) != 0) {
if (girl != null) {
// read girl.name
girlName = girl.getName();
}
}
if ((dirtyFlags & 0x6L) != 0) {
}
// batch finished
if ((dirtyFlags & 0x6L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.text, msg);
}
if ((dirtyFlags & 0x5L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.text2, girlName);
}
}
看到这里便真相大白了。我们所苦苦寻找的更改界面的方法就在这里了。
最后附上一副整体的UML图: