Jetpack源码解析四之Data Binding

Data Binding(数据绑定库)是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

所谓声明式UI,就是你在代码中做出的任何改变,都会实时的在界面中展示出来。与之对应的是命令式UI,当你想要改变界面时,必须调用XX.setText()之类的代码,才能使界面做出改变。

声明式/命令式

用传统的命令式UI,当要改变数据时,要如下操作:

1
2
3
findViewById<TextView>(R.id.sample_text).apply {
text = viewModel.userName
}

而用数据绑定后,只需在xml中声明如下,无需任何逻辑代码,当username变化时,组件自动发生变化。

1
2
<TextView
android:text="@{viewmodel.userName}" />

Data Binding并不仅仅是为了取代findViewById()。如果你的主要目的是取代 findViewById() 调用,请考虑改用视图绑定【View Binding】 或者 直接用Kotlin。

Data Binding 使用

当然,如果直接在界面上这样写是没用的,得配置下。app:build.gradle中开启:

1
2
3
dataBinding {
enabled = true
}

xml界面更改为如下样式即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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="msg"
type="String" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{msg,default = `Hello World`}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

在Activity界面中获取binding,直接操作即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
image-20210101133756494 image-20210101134221268

先看我们自己写的xml文件。像这种layout标签,不开启dataBinding的情况下编译器是铁定不识别的。所以我们有理由相信dataBinding后面肯定是将这个xml文件处理成了传统的xml布局格式。在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目录下,可以找到一个activity_main.xml文件,这个文件就是处理完后的xml文件了。

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" android:tag="layout/activity_main_0" 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">

<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" android:tag="binding_1"/>

</androidx.constraintlayout.widget.ConstraintLayout>

这个文件和我们自己写的布局文件后半部分很像,就只有一点不一样:在有用到dataBinding的地方,多了个tag标签android:tag="binding_1"。那前半部分标签去哪了呢?在第二张截图所展示目录下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout layout="activity_main" modulePackage="com.cy.myapplication" filePath="app\src\main\res\layout\activity_main.xml" directory="layout" isMerge="false" isBindingData="true" rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
<Variables declared="true" type="String" name="msg">
<location startLine="6" startOffset="8" endLine="8" endOffset="27"/>
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="androidx.constraintlayout.widget.ConstraintLayout">
<Expressions/>
<location startLine="11" startOffset="4" endLine="26" endOffset="55"/>
</Target>
<Target id="@+id/text" tag="binding_1" view="TextView">
<Expressions>
<Expression text="msg,default = `Hello World`" attribute="android:text">
<Location startLine="20" startOffset="12" endLine="20" endOffset="56"/>
<TwoWay>false</TwoWay>
<ValueLocation startLine="20" startOffset="28" endLine="20" endOffset="54"/>
</Expression>
</Expressions>
<location startLine="16" startOffset="8" endLine="24" endOffset="55"/>
</Target>
</Targets>
</Layout>

通过和我们自己写的xml对比着来看,可以看到中的信息是完全转移到了这个xml中的;原布局文件中用到了哪些布局,布局中又引入了哪些variable中的变量,也可以在xml中的Target–Expression下找得到。所以这两份文件可以包含我们所写的xml中的所有信息了。

DataBindingUtil

再看我们的Java/Kotlin代码。DataBindingUtil.setContentView():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static DataBindingComponent sDefaultComponent = null;

public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity, int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
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);
}

private static <T extends ViewDataBinding> 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[]这两个方法代码都贴上来了,注意区分。

1
2
3
4
5
6
7
8
9
10
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
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);
}

bind方法又转到了sMapper的getDataBinder方法中去。sMapper是个DataBinderMapper对象。

DataBinderMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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<DataBinderMapper> dependencies = mapper.collectDependencies();
for(DataBinderMapper dependency : dependencies) {
addMapper(dependency);
}
}
}
}

DataBinderMapperImpl继承MergedDataBinderMapper继承DataBinderMapperDataBinderMapperImpl就一个构造方法,getDataBinder方法的实现在MergedDataBinderMapper中。

MergedDataBinderMapper中又持有一个DataBinderMapper的数组:mMappers。该对象在new DataBinderMapperImpl的时候被填充进去数据,这个数据就是APT为我们生成的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MergedDataBinderMapper extends DataBinderMapper {
private List<DataBinderMapper> 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中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//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帮我们生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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对象。

image-20210101210706060

然后在初始化方法中setTag中将数组中的对象打上标签tag并存起来。比如this.text就是我们布局文件中的textView,其id叫text。所以我们能通过binding.text获得该控件。

APT-setMsg()

1
2
3
4
5
6
7
8
9
10
11
12
13
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()方法;

1
2
3
4
5
6
7
8
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}

CallbackRegistry.notifyCallbacks()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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

1
2
3
4
5
private static final NotifierCallback<OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new NotifierCallback<OnPropertyChangedCallback, Observable, Void>() {
public void onNotifyCallback(OnPropertyChangedCallback callback, Observable sender, int arg, Void notUsed) {
callback.onPropertyChanged(sender, arg);
}
};

ViewBinding.onPropertyChanged

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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类中:

1
2
3
4
5
6
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
}
return false;
}

返回true则认为数据发生了改变,走requestRebind方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@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图:

image-20210103190946285