其实说到DataBinding, 不管你使用MVP,MVVM,估计都有用到,数据和View的双向绑定是很爽的,相信很多小伙伴是用的很6,但是原理以及流程不太清楚。今天分享一起学习下DataBinding的相关流程。
举个例子
public class User extends BaseObservable {
private String name;
private String age;
public User(String name, String age) {
this.name = name;
this.age = age;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
notifyPropertyChanged(BR.age);
}
}
然后修改XML:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.example.jetpackproject.databinding.User" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.DatabindingActivity">
<TextView
android:id="@+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.age}"/>
</LinearLayout>
</layout>
最后将两者绑定起来:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding);
user = new User("hello", 10 + "");
viewDataBinding.setUser(user);
new Thread() {
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
user.setName(user.getName() + "");
//方法2:
// viewDataBinding.setVariable(BR.user, user);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
上述示例是一个简单的DataBinding的使用,需要注意一点:
通过调用ViewDataBinding的setVariable方法和notifyPropertyChanged效果是一样的,都可以实现数据修改View接着修改的效果。
现在有几个问题,需要考虑下:
- View和Data是怎样绑定的?
- Data修改,View是怎样修改的?
- setVariable和notifyPropertyChanged有什么关系?
- DataBinding是怎样避免使用findViewById的?
带着这些问题,开始源码分析流程。 在开始分析之前,看三个东西:
APT自动生成的代码:
当在XML中写完代码之后,编译一下,会自动生成两个XML文件:
注意:这里自动生成的XML文件名,是和自己在项目中使用DataBinding的XML的文件名称关联的。
其中第一个XML文件内容格式化之后,就是下面的东西:
自己使用了DataBinding的XML文件生成了两个XML文件,这两个文件的关联关系如上图所示。
根据XML以及注解,同样生成了相应的Java代码:
DataBindingMapperImpl,BR, 以及XXXBindingImpl 三个文件是自动生成的,XXXBindingImpl对应XML:activity_databinding.xml。其中Activity DatabindingBinding类是ViewDataBinding的实现类。
BR类里面有很多静态常量,这些常量都是添加了Bindable注解的方法:
@Bindable
public String getName() {
return name;
}
知道了这些自动生成的代码,开始带着上述的那四个问题分析流程。
入口setContentView方法
在Activity的onCreate方法内,将XML扔给了DataBindingUtil,并且传入了Activity本身:
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
// 内部还是调用了Activity的setContentView
activity.setContentView(layoutId);
......
//
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
其实在DataBindingUtil内部还是调用了Activity的setContentView方法,然后又拿着R.id.content的View给了bindToAddedViews作为参数,最终会进入:bind
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}
这里的sMapper就是抽象类DataBinderMapper,具体实现类是:DataBinderMapperImpl类,因此执行的是实现类DataBinderMapperImpl的getDataBinder 方法:
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
.....
return new ActivityDatabindingBindingImpl(component, view);
......
}
创建了ActivityDatabindingBindingImpl类实例, 然后我们进入构造方法看看初始化了什么东西。
public ActivityDatabindingBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
//通过mapBindings方法获取到XML中所有的View,并且存储到一个数组中
this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}
private ActivityDatabindingBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.TextView) bindings[1]
);
// 给成员变量View赋值
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView2 = (android.widget.TextView) bindings[2];
this.mboundView2.setTag(null);
//将标记tag置为null
this.tv1.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
在构造方法中,首先通过调用mapBindings方法获取到所有的子View对象。那么他是怎么获取子View对象的呢?
private static void mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) {
final int indexInIncludes;
final ViewDataBinding existingBinding = getBinding(view);
if (existingBinding != null) {
return;
}
Object objTag = view.getTag();
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
if (isRoot && tag != null && tag.startsWith("layout")) { // XML中的tag是否是layout开头的
final int underscoreIndex = tag.lastIndexOf('_');
if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
final int index = parseTagInt(tag, underscoreIndex + 1);
if (bindings[index] == null) {
bindings[index] = view;
}
indexInIncludes = includes == null ? -1 : index;
isBound = true;
} else {
indexInIncludes = -1;
}
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
if (bindings[tagIndex] == null) {
bindings[tagIndex] = view;
}
isBound = true;
indexInIncludes = includes == null ? -1 : tagIndex;
} else {
// Not a bound view
indexInIncludes = -1;
}
if (!isBound) { // view是否设置了id
final int id = view.getId();
if (id > 0) {
int index;
if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
bindings[index] == null) {
bindings[index] = view;
}
}
}
if (view instanceof ViewGroup) { //是否是ViewGroup
final ViewGroup viewGroup = (ViewGroup) view;
final int count = viewGroup.getChildCount();
int minInclude = 0;
for (int i = 0; i < count; i++) { //循环每个子View
final View child = viewGroup.getChildAt(i);
boolean isInclude = false;
if (indexInIncludes >= 0 && child.getTag() instanceof String) {
String childTag = (String) child.getTag();
if (childTag.endsWith("_0") &&
childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
// This *could* be an include. Test against the expected includes.
int includeIndex = findIncludeIndex(childTag, minInclude,
includes, indexInIncludes);
if (includeIndex >= 0) {
isInclude = true;
minInclude = includeIndex + 1;
final int index = includes.indexes[indexInIncludes][includeIndex];
final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
int lastMatchingIndex = findLastMatching(viewGroup, i);
if (lastMatchingIndex == i) {
//包含include标签,重新执行交给DataBindingUtil类处理
bindings[index] = DataBindingUtil.bind(bindingComponent, child,
layoutId);
} else {
final int includeCount = lastMatchingIndex - i + 1;
final View[] included = new View[includeCount];
for (int j = 0; j < includeCount; j++) {
included[j] = viewGroup.getChildAt(i + j);
}
bindings[index] = DataBindingUtil.bind(bindingComponent, included,
layoutId);
i += includeCount - 1;
}
}
}
}
if (!isInclude) { //是否包含 标签include。不包含的话,就递归获取view
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
}
上述代码很长,实际上就是通过tag获取对应的View,然后存入到数组bindings中。方法其实就是个递归,如果XML中定义的是ViewGroup类型的,那么就交给DataBindingUtil重新执行之前的操作;如果不包含,那么到最后就递归通过tag继续获取View。
这里的tag,实际上就是之前开头说的那个XML文件,通过tag文件可以对应的获取第二个XML的View的相关信息。
总体来说:这里就做了一件事情,通过tag获取View,并且将View存入到数组中并返回该数组。
返回该数组之后,将数组中的所有子View赋值给成员变量(XML中View有ID的就直接使用ID设置;没有设置ID的就创建成员变量)。
然后执行了invalidateAll方法,该方法可以看作是初始化View上的数据。最终会执行requestRebind方法:
protected void requestRebind() {
//这里判断View是否包含include标签的布局
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 { // 我们分析<16的
mUIThreadHandler.post(mRebindRunnable);
}
}
}
该方法最后开启了一个线程mRebindRunnable:
/**
* Runnable executed on animation heartbeat to rebind the dirty Views.
*/
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
.....
executePendingBindings();
}
};
public void executePendingBindings() {
if (mContainingBinding == null) { // 由于我们的XML没有包含include标签,因此进入此判断
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
private void executeBindingsInternal() {
.......
executeBindings();
.......
}
最终进入executeBindings方法,该方法是个抽象方法,其实现是在:ViewDataBinding的实现类ActivityDatabindingBindingImpl。
注意:这里是ActivityDatabindingBindingImpl, 这个自动生成的类是根据你的XML变化的。比如:如果你定义的是abc.xml,那么生成的实现类就是:AbcBindingImpl。
@Override
protected void executeBindings() {
......
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xbL) != 0) {
if (user != null) {
// read user.name
userName = user.getName(); // 通过调用注解Bindable的方法,获取值
}
}
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.age
userAge = user.getAge();
}
}
}
// batch finished
if ((dirtyFlags & 0xdL) != 0) {
// api target 1 TextView设置值
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userAge);
}
if ((dirtyFlags & 0xbL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
}
}
最终调用了TextViewBindingAdapter的setText方法:
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
.....
//给TextView设置value
view.setText(text);
}
至此setContentView流程就分析完了,重新捋一遍:
首先创建了XXXBindingImpl类,然后在构造方法中,通过tag获取到了所有的子view,并且添加到了一个数组中,并且将数组中的每一个值都赋值给了成员变量,或者直接通过ID设置了值。 接下来就是给View初始化数据, 通过通过开启一个线程,最后通过XXXBindingAdapter成功初始化了View。
通过上述流程分析,就解决了问题1 和 问题4
当我们有数据变化时,会直接调用:
viewDataBinding.setVariable(BR.user, user);
已setVariable为入口看下内部是怎么实现View的修改的。
setVariable方法
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.user == variableId) { // 如果 BR.user == 传入的ID
// 调用了setUser方法
setUser((com.example.jetpackproject.databinding.User) variable);
}
else { // 否则直接返回
variableSet = false;
}
return variableSet;
}
通过上述代码我们可以得出两点:
- 如果传入的是:BR.user(也就是继承BaseObservable),那么就调用方法;
- 如果不是就直接返回,那么也就是说:setVariable(BR.name, “hello”);是无效的!!!
继续深入:
public void setUser(@Nullable com.example.jetpackproject.databinding.User User) {
//更新注册
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
//这里调用了notifyPropertyChanged方法,(先不要管参数)
notifyPropertyChanged(BR.user);
super.requestRebind();
}
首先调用了updateRegistration方法,这方法就是给每一个BR下的静态常量添加一个监听器:
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
}
};
private boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) { // 解绑
return unregisterFrom(localFieldId);
}
//先从数组中获取
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {// 如果数组中没取到,那么就直接创建
registerTo(localFieldId, observable, listenerCreator);
return true;
}
if (listener.getTarget() == observable) {
return false;//nothing to do, same object
}
unregisterFrom(localFieldId);
registerTo(localFieldId, observable, listenerCreator);
return true;
}
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
.....
//这里的listener是WeakListener类的实例对象, observable就是User的实例对象
listener.setTarget(observable);
}
首先看下数组mLocalFieldObservers, 这个东西存储的就是监听器(WeakListener),BR文件中的数组一次从0开始,也就是说为每一个BR的常量都存储了个监听器,并且下标索引就是BR中常量的值。
最后调用了监听器的setTarget方法:
public void setTarget(T object) {
unregister();
mTarget = object;
if (mTarget != null) {
mObservable.addListener(mTarget);
}
}
这里的mObservable就是WeakPropertyListener类的实例对象,
@Override
public void addListener(Observable target) {
//target就是我们的User类的实例对象(注意:该方法在父类中)
target.addOnPropertyChangedCallback(this);
}
addOnPropertyChangedCallback的实现是在BaseObservable类中:
@Override
public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
}
mCallbacks.add(callback);
}
由于callback参数也就是WeakPropertyListener本身,因此也就是说User类间接持有了WeakPropertyListener的引用。
回过头在看下,updateRegistration方法之后调用了notifyPropertyChange方法,如果不管参数的前提下,是不是可以理解为:setVariable内部就是调用了notifyPropertyChanged方法呢? 答案是肯定的。
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
这里的mCallbacks就是PropertyChangeRegistry实例对象,从类BaseObservable的addOnPropertyChangedCallback方法可以看出来:
public void notifyChange(@NonNull Observable observable, int propertyId) {
notifyCallbacks(observable, propertyId, null);
}
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
......
notifyRecurse(sender, arg, arg2);
.......
}
private void notifyRecurse(T sender, int arg, A arg2) {
........
notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
}
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
final int endIndex, final long bits) {
....
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
.....
}
通过层层调用最终,进入到了notifyCallbacks方法,这里mNotifier就是PropertyChangeRegistry类中的NOTIFIER_CALLBACK常量。
而mCallbacks是一个数组,其内部存储的就是:WeakPropertyListener。
这里mCallbacks存储的数据类型,可以在ViewDataBinding类中找到。在上文分析的类BaseObservable的addOnPropertyChangedCallback方法中。
因此就进入到了WeakPropertyListener的onPropertyChanged方法中:
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
......
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
......
//修改二进制的标记值
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {// 是否修改了值
//最后赋值
requestRebind();
}
}
看到requestRebind方法,就和setContentView流程分析的代码重合了。
实际上总体流程就是Activity,ViewDataBinding, WeakListener, WeakPropertyListener和ViewModel(也就是User), 这四个类是是互相关联的,ViewModel修改了值,是通过WeakPropertyListener ——》 WeakListener——》ViewDataBinding——》Activity 的:
最后附上一张流程图, 以setVariable为例: