一、DataBinding使用
本文着重讲解DataBinding原理,使用的例子比较简单,若读者想要了解更多的DataBinding的使用方法介绍,可以自寻相关资料,本文纯属个人理解,若有错误,还望指出(抱拳)
在app模块的build.gradle中加入如下配置
android {
...
dataBinding {
enabled = true
}
}
现在你就可以在代码中使用DataBinding了,这里我们举个简单例子,给一个TextView设置单向绑定一个ObservableField< String>类型的name,给一个EditText设置双向绑定一个类型为ObservableField< String>的nickName,点击一个Button可以获取nickName里面的值,nickName首先显示“美女”,在代码中延迟三秒后将nickName的值改为“延迟三秒”,三秒后然后观察到EditText上文本变为“延迟三秒”,然后再将EditText上文本删除,输入“beauty”,点击button获取nickName的值,发现也是“beauty”,这里的name是单项绑定到TextView上,nickName是双向绑定到EditText上。
image.pngimage.png
image.png
来看下布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="userInfo"
type="com.jokerwan.databinding.UserInfo" />
<variable
name="listener"
type="android.view.View.OnClickListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userInfo.name}"
android:textSize="16sp"
tools:text="姓名"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={userInfo.nickName}"
tools:text="昵称"
android:layout_marginTop="10dp"/>
<Button
android:id="@+id/btn_get_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取model里面的昵称"
android:layout_marginTop="20dp"
android:onClick="@{listener}"/>
</LinearLayout>
</layout>
注意这里给TextView和EditText绑定数据的区别:
给TextView是设置单项绑定
android:text="@{userInfo.name}"
,
给EditText是设置双向绑定
android:text="@={userInfo.nickName}"
可以看到单项绑定和双向绑定的区别就是“@”和“{}”之间多了个“=”。
xml绑定了一个UserInfo对象和一个listener,listener是Button的点击监听,我们来看下UserInfo的代码
public class UserInfo {
private ObservableField<String> name = new ObservableField<>();
private ObservableField<String> nickName = new ObservableField<>();
public ObservableField<String> getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public ObservableField<String> getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName.set(nickName);
}
}
再看下MainActivity中的代码,主要就是构造UserInfo并给binding的属性赋值
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private UserInfo userInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userInfo = new UserInfo();
userInfo.setName("王昭君");
userInfo.setNickName("美人");
binding.setUserInfo(userInfo);
binding.setListener(this);
binding.getRoot().postDelayed(new Runnable() {
@Override
public void run() {
userInfo.setNickName("延迟三秒");
}
}, 3000);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_get_nick) {
Toast.makeText(this, userInfo.getNickName().get(), Toast.LENGTH_SHORT).show();
}
}
}
可以看到,使用了数据绑定,我们的代码逻辑结构变得清晰,由数据绑定框架替我们生成findViewById和给View设置数据的代码,数据绑定框架帮我们做了控件的数据变化监听,并将数据同步更新到控件上。
二、DataBinding原理分析
数据绑定的运行机制是怎样的呢?,为什么我们改变nickName的值UI上可以直接更新,我们操作UI,对应的nickName的值也会更新呢,下面我们一探DataBinding的究竟。
首先我们要先找到一个切入点,就是MainActivity中的
DataBindingUtil.setContentView(this, R.layout.activity_main);
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);
}
}
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
从DataBindingUtil.setContentView()
一路方法调用跟下来,这里的parent是布局id为R.id.content
的跟布局,一般跟布局里面就是我们自己的布局,最外层是一个容器,所以childrenAdded == 1,并调用bind(component, childView, layoutId)
方法,跟进bind()方法发现调用sMapper.getDataBinder(bindingComponent, root, layoutId)
,
这里的sMapper是DataBinderMapper类,该类是抽象类,找到它的实现类DataBinderMapperImpl,路径是:
app/build/generated/ap_generated_sources/debug/out/com/jokerwan/databinding/DataBinderMapperImpl.java
DataBinderMapperImpl#getDataBinder(bindingComponent, root, layoutId)
@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;
}
判断view的tag是不是与layout/activity_main_0
相等,如果相等,就new ActivityMainBindingImpl(component, view)
,这个ActivityMainBindingImpl就是DataBinding框架根据我们的activity_main.xml通过APT在编译时生成的类,ActivityMainBindingImpl的路径为:
app/build/generated/ap_generated_sources/debug/out/com/jokerwan/databinding/databinding/ActivityMainBindingImpl.java
有的小伙伴就有疑问了,为什么是layout/activity_main_0
,view的tag又是在哪里set进去的呢?
原来,数据绑定在处理布局的时候生成了两个xml文件
- activity_main-layout.xml(DataBinding需要的布局控件信息)
- activity_main.xml(Android OS 渲染的布局文件)
activity_main-layout.xml
路径:
app/build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/activity_main-layout.xml
文件内容:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout"
filePath="/Users/jokerwan/AndroidStudioProjects/DataBinding/app/src/main/res/layout/activity_main.xml"
isMerge="false" layout="activity_main" modulePackage="com.jokerwan.databinding">
<Variables name="userInfo" declared="true" type="com.jokerwan.databinding.UserInfo">
<location endLine="8" endOffset="54" startLine="6" startOffset="8" />
</Variables>
<Variables name="listener" declared="true" type="android.view.View.OnClickListener">
<location endLine="12" endOffset="54" startLine="10" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="44" endOffset="18" startLine="15" startOffset="4" />
</Target>
<Target tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="userInfo.name">
<Location endLine="25" endOffset="42" startLine="25" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="25" endOffset="40" startLine="25" startOffset="28" />
</Expression>
</Expressions>
<location endLine="27" endOffset="28" startLine="22" startOffset="8" />
</Target>
<Target tag="binding_2" view="EditText">
<Expressions>
<Expression attribute="android:text" text="userInfo.nickName">
<Location endLine="32" endOffset="47" startLine="32" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="32" endOffset="45" startLine="32" startOffset="29" />
</Expression>
</Expressions>
<location endLine="34" endOffset="44" startLine="29" startOffset="8" />
</Target>
<Target id="@+id/btn_get_nick" tag="binding_3" view="Button">
<Expressions>
<Expression attribute="android:onClick" text="listener">
<Location endLine="42" endOffset="40" startLine="42" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="42" endOffset="38" startLine="42" startOffset="31" />
</Expression>
</Expressions>
<location endLine="42" endOffset="42" startLine="36" startOffset="8" />
</Target>
</Targets>
</Layout>
activity_main.xml
路径:
app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
文件内容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center" 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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_1"
android:textSize="16sp"
tools:text="姓名"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_2"
tools:text="昵称"
android:layout_marginTop="10dp"/>
<Button
android:id="@+id/btn_get_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取model里面的昵称"
android:layout_marginTop="20dp"
android:tag="binding_3" />
</LinearLayout>
可以看到在activity_main.xml生成了辅助信息tag,每个容器和view都对应一个tag,而布局的第一个容器LinearLayout的tag就为layout/activity_main_0
,这些tag在我们build工程时会随着activity_main.xml的生成而存在。
接着看生成的ActivityMainBindingImpl#构造器
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 4, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.Button) bindings[3]
);
this.btnGetNick.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.TextView) bindings[1];
this.mboundView1.setTag(null);
this.mboundView2 = (android.widget.EditText) bindings[2];
this.mboundView2.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
构造函数内首先调用mapBindings()
递归把root
中所有的view找出来,数字4是指布局中总共有4个View,然后还传入sIncludes
和sViewsWithIds
,前者是布局中include进来的布局的索引,后者是布局中包含id的索引。
再回到构造函数,mapBindings()
查找到的View都放置在bindings
这个数组中,并通过生成代码的方式,将它们一一取出来,转化为对应的数据类型,有设置id的控件,会以id作为变量名,没有设置id的控件,则以mboundView + 数字
的方式依次赋值。然后通过setRootTag(root)
方法通过setTag的方式将这个Binding和root关联起来
protected void setRootTag(View view) {
view.setTag(R.id.dataBinding, this);
}
ViewDataBinding#mapBindings()
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots, int numBindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds) {
Object[] bindings = new Object[numBindings];
for(int i = 0; i < roots.length; ++i) {
mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
}
return bindings;
}
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
ViewDataBinding existingBinding = getBinding(view);
if (existingBinding == null) {
Object objTag = view.getTag();
String tag = objTag instanceof String ? (String)objTag : null;
boolean isBound = false;
int indexInIncludes;
int id;
int count;
if (isRoot && tag != null && tag.startsWith("layout")) {
id = tag.lastIndexOf(95);
if (id > 0 && isNumeric(tag, id + 1)) {
count = parseTagInt(tag, id + 1);
if (bindings[count] == null) {
bindings[count] = view;
}
indexInIncludes = includes == null ? -1 : count;
isBound = true;
} else {
indexInIncludes = -1;
}
} else if (tag != null && tag.startsWith("binding_")) {
id = parseTagInt(tag, BINDING_NUMBER_START);
if (bindings[id] == null) {
bindings[id] = view;
}
isBound = true;
indexInIncludes = includes == null ? -1 : id;
} else {
indexInIncludes = -1;
}
if (!isBound) {
id = view.getId();
if (id > 0 && viewsWithIds != null && (count = viewsWithIds.get(id, -1)) >= 0 && bindings[count] == null) {
bindings[count] = view;
}
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)view;
count = viewGroup.getChildCount();
int minInclude = 0;
for(int i = 0; i < count; ++i) {
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(47) > 0) {
int includeIndex = findIncludeIndex(childTag, minInclude, includes, indexInIncludes);
if (includeIndex >= 0) {
isInclude = true;
minInclude = includeIndex + 1;
int index = includes.indexes[indexInIncludes][includeIndex];
int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
int lastMatchingIndex = findLastMatching(viewGroup, i);
if (lastMatchingIndex == i) {
bindings[index] = DataBindingUtil.bind(bindingComponent, child, layoutId);
} else {
int includeCount = lastMatchingIndex - i + 1;
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) {
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
}
}
mapBindings()
方法主要是把root内所有的view给查找出来,并放置到bindings
对应的索引内,这个索引如何确定呢?上面我们分析过,DataBinding在加载我们的布局activity_main.xml时会生成两个xml文件,一个用来关联布局控件信息,一个是布局文件并对每个ViewGroup和View都打了一个tag,通过解析这个tag,就能知道对应的索引了。所以,为了避免自己inflate布局文件后,不小心操作了view的tag对解析产生干扰,尽量使用数据绑定来得到inflate之后的view。
通过代码我们可以发现mapBindings()
通过自身递归调用把root
中所有的view找出来,这里虽然用到了递归,但实际上是通过这种方式实现对root下所有的控件的遍历,因此整个方法的时间复杂度是O(n),通过一次遍历,找到所有的控件,整体性能比使用findViewById还优秀,因为findViewById每次都要从DecorView开始循环遍历找到对应的View。
回到ActivityMainBindingImpl实现类的构造器,可以看到最后调用了invalidateAll()
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x8L;
}
requestRebind();
}
invalidateAll()
方法实现很简单,将脏标记位mDirtyFlags
标记为0x8L
,即在二进制表示上,第4位的值为1,这个脏标记位是一个long的值,也就是最多有64个位可供使用。由于mDirtyFlags
这个变量是成员变量,且多处会对其进行写操作,所以对它的写操作加了同步锁。接着调用了requestRebind()
,ViewDataBinding#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);
}
}
}
mContainingBinding
表示当前ViewDataBinding所包含的另外一个ViewDataBinding,如果当前ViewDataBinding包含另一个ViewDataBinding,则子ViewDataBinding会优先执行rebind操作,从if (mContainingBinding != null)
则执行mContainingBinding.requestRebind()
代码中可以看出,若当前ViewDataBinding内部没有包含另一个ViewDataBinding,则程序继续走else逻辑,获取到此ViewDataBinding绑定的LifecycleOwner,如果LifecycleOwner的生命周期状态不是Lifecycle.State.STARTED
,则直接return,不进行绑定数据的相关操作,否则继续执行下面的程序代码。
如果此前没请求执行rebind操作,那么会将mPendingRebind
置为true,API等级16及以上,会往mChoreographer
发一个mFrameCallback
,在系统刷新界面(doFrame)的时候执行rebind操作,API等级16以下,则是往UI线程post一个mRebindRunnable
任务。mFrameCallback
的内部实际上调用的是mRebindRunnable
的run方法,因此这两个任务仅仅是调用时机不同。
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
而如果此前请求过执行rebind操作,即已经post了一个任务到队列去,而且这个任务还未获得执行,此时mPendingRebind
的值为true,那么requestRebind将直接返回,避免重复、频繁执行rebind操作带来的性能损耗。接着我们来看下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();
}
};
当mRebindRunnable
的run()
方法执行时,先同步吧mPendingRebind
赋值为false,以便后续其他requestRebind能往主线程发起rebind的任务。在 API 19及以上的版本,检查下UI控件是否附加到了窗口上,如果没有附到窗口上,则设置监听器,以便在UI附加到窗口上的时候立即执行rebind操作,然后返回。当 API 19 以下或UI控件已经附加到窗口上,,则调用executePendingBindings()
执行binding逻辑。继续跟进ViewDataBinding#executePendingBindings()
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
在这里依旧先判断若当前ViewDataBinding内部有没有包含另一个ViewDataBinding,若有包含,则先执行子ViewDataBinding的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;
}
通过以上代码可以看出,此处进行了binding操作之前的一些判定,如果已经开始执行绑定操作了,即这段代码正在执行,那么调用一次requestRebind,然后返回。接着调用hasPendingBindings()
判断是否需要刷新UI,若返回true表示需要刷新,继续执行代码,否则直接return掉该方法。
Override
public boolean hasPendingBindings() {
synchronized(this) {
if (mDirtyFlags != 0) {
return true;
}
}
return false;
}
这里mDirtyFlags != 0
表示需要刷新UI,还记得之前invalidateAll()
中的mDirtyFlags = 0x8L
吗?
接下来在执行具体的executeBindings()
操作前,调用下mRebindCallbacks.notifyCallbacks
通知所有回调将开始rebind操作,回调可以在执行的过程中,将mRebindHalted
置为true,阻止executeBindings()
方法的执行,拦截成功同样通过回调进行通知。如果没有被拦截,executeBindings()
方法会被执行,运行结束后,同样通过回调进行通知。executeBindings()
是ViewDataBinding的一个抽象方法,具体实现在ActivityMainBindingImpl#executeBindings()
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
android.view.View.OnClickListener listener = mListener;
androidx.databinding.ObservableField<java.lang.String> userInfoNickName = null;
java.lang.String userInfoNickNameGet = null;
java.lang.String userInfoName = null;
com.jokerwan.databinding.UserInfo userInfo = mUserInfo;
if ((dirtyFlags & 0xaL) != 0) {
}
if ((dirtyFlags & 0xdL) != 0) {
if (userInfo != null) {
// read userInfo.nickName
userInfoNickName = userInfo.getNickName();
}
updateRegistration(0, userInfoNickName);
if (userInfoNickName != null) {
// read userInfo.nickName.get()
userInfoNickNameGet = userInfoNickName.get();
}
if ((dirtyFlags & 0xcL) != 0) {
if (userInfo != null) {
// read userInfo.name
userInfoName = userInfo.getName();
}
}
}
// batch finished
if ((dirtyFlags & 0xaL) != 0) {
// api target 1
this.btnGetNick.setOnClickListener(listener);
}
if ((dirtyFlags & 0xcL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userInfoName);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userInfoNickNameGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
}
}
首先将脏标记为存储到局部变量dirtyFlags
中,然后将脏数据标记位mDirtyFlags
置0,然后对绑定到布局文件的model对象进行判空,并取到对应的值赋值给对应的控件,这里又体现出了数据绑定的一个优势:在进行数据相关的操作前,会检查变量是否为空,倘若没有传入对应的变量,或者传入null,在布局上进行的操作并不会执行,因此,假如上述例子中,我们没有传入对应的userInfo对象也不会引发Crash。
接着根据脏标记位和相关的值进行位与运算来判断,上面我们已经知道,在ActivityMainBindingImpl构造函数调用了invalidateAll()
将mDirtyFlags
置0x8L
,转为二进制第四位为1,上述四个if条件经过位与运算后与0比较都为真,即这种情况下上述条件里面的代码都会执行。这里主要执行的操作:
-
获取
userInfoName
,将userInfoName
绑定到mboundView1
,获取userInfoNickNameGet
,并将其绑定到mboundView2
,通过代码可以看到,每一个被绑定到View上的通过variable
标签定义的变量都会有一个专属的标记位,当该变量的值被更新时,对应的脏标记位就会置为1,executeBindings()
执行的时候就会将变动的数据更新到对应的UI控件上。 -
在设置了双向绑定的控件上,为其添加对应的监听器,监听其变动,如:
EditText
上设置TextWatcher
,具体的设置逻辑放置到了TextViewBindingAdapter.setTextWatcher里。源码如下:@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false) public static void setTextWatcher(TextView view, final BeforeTextChanged before, final OnTextChanged on, final AfterTextChanged after, final InverseBindingListener textAttrChanged) { final TextWatcher newValue; if (before == null && after == null && on == null && textAttrChanged == null) { newValue = null; } else { newValue = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (before != null) { before.beforeTextChanged(s, start, count, after); } } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (on != null) { on.onTextChanged(s, start, before, count); } if (textAttrChanged != null) { textAttrChanged.onChange(); } } @Override public void afterTextChanged(Editable s) { if (after != null) { after.afterTextChanged(s); } } }; } final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher); if (oldValue != null) { view.removeTextChangedListener(oldValue); } if (newValue != null) { view.addTextChangedListener(newValue); } }
代码中创建了一个新的
TextWatcher
,并将传进来的mboundView2androidTextAttrChanged
监听器包裹在里面。
当数据发生变化的时候,TextWatcher
在回调onTextChanged()
的最后,会通过textAttrChanged.onChange()
回调到传入的mboundView2androidTextAttrChanged
的onChange()
。在这里我们也看到了熟悉的
@BindingAdapter
注解,这个注解实现了控件属性和代码内的方法调用的映射,编译期,数据绑定框架通过这种方式,为对应的控件生成对应的方法调用。接着我们来看下传进来的监听器
mboundView2androidTextAttrChanged
的代码private androidx.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new androidx.databinding.InverseBindingListener() { @Override public void onChange() { // Inverse of userInfo.nickName.get() // is userInfo.nickName.set((java.lang.String) callbackArg_0) java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2); // localize variables for thread safety // userInfo != null boolean userInfoJavaLangObjectNull = false; // userInfo.nickName androidx.databinding.ObservableField<java.lang.String> userInfoNickName = null; // userInfo.nickName.get() java.lang.String userInfoNickNameGet = null; // userInfo com.jokerwan.databinding.UserInfo userInfo = mUserInfo; // userInfo.nickName != null boolean userInfoNickNameJavaLangObjectNull = false; userInfoJavaLangObjectNull = (userInfo) != (null); if (userInfoJavaLangObjectNull) { userInfoNickName = userInfo.getNickName(); userInfoNickNameJavaLangObjectNull = (userInfoNickName) != (null); if (userInfoNickNameJavaLangObjectNull) { userInfoNickName.set(((java.lang.String) (callbackArg_0))); } } } };
这段代码会去对应的View中取得控件中最新的值,然后一系列地判断布局文件中绑定的userInfo以及对应的属性是否为null,不为null时将值更新为从控件中取出的最新值。
上面讲到的情况是当UI控件上
EditText
显示内容改变时通知绑定到布局文件的ObservableField更新,那当ObservableField更新后是如何通知UI更新的呢?我们继续回到ActivityMainBindingImpl#executeBindings()... if ((dirtyFlags & 0xdL) != 0) { if (userInfo != null) { // read userInfo.nickName userInfoNickName = userInfo.getNickName(); } updateRegistration(0, userInfoNickName); ... } ...
注意我们绑定到布局中的
userInfo
中的nickName
的类型是ObservableField<String>
,通过注释我们也可以看到此处的userInfoNickName
就是读取的userInfo.nickName
,接着调用updateRegistration(0, userInfoNickName)
,ObservableField最终是继承Observable,我们不妨猜想一下,updateRegistration()方法传入一个Observable对象应该是要观察这个对象的变化,当被观察者发生变化时去重新执行rebind操作,第一个参数0表示的是userInfoNickName的id,看代码ViewDataBinding#updateRegistration()protected boolean updateRegistration(int localFieldId, Observable observable) { return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER); }
这个方法中接着调用updateRegistration方法并传入一个
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(); } };
通过代码发现
CREATE_PROPERTY_LISTENER
是一个属性监听器创建类,其中new WeakPropertyListener(viewDataBinding, localFieldId)
并返回其监听器,我们先继续看updateRegistration()
方法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; }
当传进来的observable为null时,解注册该被观察者,后面的逻辑就是先判断传进来的observable的监听器在
mLocalFieldObservers
中是否存在,存在就根据localFieldId
拿出来先重新绑定监听器,不存在就新创建一个监听器并绑定该observable根据localFieldId
存进mLocalFieldObservers
中,mLocalFieldObservers
是一个数组,我们继续看registerTo(localFieldId, observable, listenerCreator)
protected void registerTo(int localFieldId, Object observable, CreateWeakListener listenerCreator) { if (observable == null) { return; } WeakListener listener = mLocalFieldObservers[localFieldId]; if (listener == null) { listener = listenerCreator.create(this, localFieldId); mLocalFieldObservers[localFieldId] = listener; if (mLifecycleOwner != null) { listener.setLifecycleOwner(mLifecycleOwner); } } listener.setTarget(observable); }
当
listener
为null时,调用传进来的监听器CreateWeakListener
的create(this, localFieldId)
方法创建listener,并且给listener设置当前ViewDataBinding的LifecycleOwner和对应的Target为observable
,这里的this
就是指当前的ViewDataBinding,CreateWeakListener是一个抽象类,我们之前new出来的WeakPropertyListener
是它的实现类,接下来我们看WeakPropertyListener
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback implements ObservableReference<Observable> { final WeakListener<Observable> mListener; public WeakPropertyListener(ViewDataBinding binder, int localFieldId) { mListener = new WeakListener<Observable>(binder, localFieldId, this); } @Override public WeakListener<Observable> getListener() { return mListener; } @Override public void addListener(Observable target) { target.addOnPropertyChangedCallback(this); } @Override public void removeListener(Observable target) { target.removeOnPropertyChangedCallback(this); } @Override public void setLifecycleOwner(LifecycleOwner lifecycleOwner) { } @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); } }
首先看其构造方法
mListener = new WeakListener<Observable>(binder, localFieldId, this);
这里new一个WeakListener并且将ViewDataBinding类型的binder对象和localFieldId传进去,好,继续往下看,注意看onPropertyChanged()
方法,见名知意,这个方法应该是当属性变化时会调用,从mListener
中取出binder,当binder为null时直接退出,从mListener取出之前存入的Observable对象,并校验下当前获取到的Observable对象是否是发出更新消息的Observable对象,如果不是,则不作处理,如果是,则调用binder的handleFieldChange()
方法,跟进ViewDataBinding#handleFieldChange()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()
方法来判断需要更新的数据是否有变化,这个方法是ViewDataBinding的抽象方法,具体实现在ActivityMainBindingImpl#onFieldChange()@Override protected boolean onFieldChange(int localFieldId, Object object, int fieldId) { switch (localFieldId) { case 0 : return onChangeUserInfoNickName((androidx.databinding.ObservableField<java.lang.String>) object, fieldId); } return false; } private boolean onChangeUserInfoNickName(androidx.databinding.ObservableField<java.lang.String> UserInfoNickName, int fieldId) { if (fieldId == BR._all) { synchronized(this) { mDirtyFlags |= 0x1L; } return true; } return false; }
还记得调用
updateRegistration(0, userInfoNickName)
方法吗,第一个参数就是nickName的localFieldId,传的值是0,匹配到switch语句的0,返回onChangeUserInfoNickName()
的结果,这里会重新将脏数据进行位或运算,将nickName对应的二进制标记位置为1,并返回true,handleFieldChange()
方法中会接受结果为true则调用requestRebind()
重新绑定更改过的数据,在requestRebind()
会重新获取nickName的值,并判断脏标记位是否满足更新UI控件条件,满足则更新UI,当我们给ObservableField对象set值的时候会调用notifyChange()
public void notifyChange() { synchronized (this) { if (mCallbacks == null) { return; } } mCallbacks.notifyCallbacks(this, 0, null); }
mCallbacks
的类型是PropertyChangeRegistry,通过addOnPropertyChangedCallback(OnPropertyChangedCallback callback)
方法创建,并且把callback添加到mCallbacks
中,继续回到
WeakPropertyListener
类@Override public void addListener(Observable target) { target.addOnPropertyChangedCallback(this); } @Override public void removeListener(Observable target) { target.removeOnPropertyChangedCallback(this); }
发现它集成自Observable.OnPropertyChangedCallback,并且在被调用
addListener()
方法是将自己添加到ObservableField对象的mCallbacks
中,并在removeListener()
被调用时移除回调,这里就是把属性监听器绑定在ObservableField对象中,当ObservableField对象值改变时会调用notifyChange()
,经过一系列的回调和通信,最终会调用WeakPropertyListener
类的onPropertyChanged()
方法来通知ViewDataBinding来执行rebind操作。
至此,ObservableField更新后是通知UI更新的流程就分析完毕。
三、总结
DataBingding用起来很方便,可以帮助我们简化使Activity/Fragment中部分操作View的代码,让我们更加关注业务逻辑的实现。但本文讲的DataBingding原理可能理解起来比较困难,你也没有必要去计较DataBingding库中的一些细枝末节的代码,我们只需要疏通整个链路,知道DataBingding框架如何实现双向绑定的大概思路即可,了解其中的观察者设计模式。通过这篇文章的学习,了解了DataBingding原理,在日后使用DataBingding和定位使用DataBinding产生的bug时也会得心应手。
网友评论