DataBinding
DataBinding 是以声明的方式,将布局中组件与应用程序源数据绑定在一起的框架库。
作用:
1.将布局组件与源数据绑定,使源数据变化的同时布局组件及时同步更新。
2.减少Activity中View的定义(private View view)与初始化(findViewById),让Activity代码更专注于界面的逻辑更新。
3.可自定义适配器,实现扩展组件的属性功能。
4.可自定义事件,实现各种组件的事件触发功能。
特点:
1.使用简单,主要以声明的方式实现。
2.功能强大,可自定义适配器 & 事件 ,兼容各种界面逻辑需求。
简单使用
只为研究下原理,然后写了一个简单的 demo
android {
//......
dataBinding {
enabled = true
}
layout 代码 activity_qinxue.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="userInput"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userInput}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入"
android:text="@={userInput}" />
</LinearLayout>
</layout>
Activity 代码
public class QinXueActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityQinxueBinding binding = ActivityQinxueBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
}
实现效果
源码分析
生成的中间产物
1、activity_qinxue.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout absoluteFilePath="/Users/qinxue/Downloads/GoogleArchitectureDemo/app_specific/src/main/res/layout/activity_qinxue.xml" directory="layout"
isMerge="false"
layout="activity_qinxue" modulePackage="google.architecture.specific">
<Variables name="userInput" declared="true" type="String">
<location endLine="5" endOffset="27" startLine="3" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_qinxue_0" view="LinearLayout">
<Expressions />
<location endLine="26" endOffset="18" startLine="8" startOffset="4" />
</Target>
//......
</Targets>
</Layout>
/build/intermediates/data-binding-info/debug/activity_qinxue-layout.xml
编译出的中间产物,文件会给每一个View设置上tag,这个很重要。但是这个这么重要,View的tag岂不是不能自定义了,感觉不太好。
2、ActivityQinxueBinding.java
build/generated/source/apt/debug/google/architecture/specific/databinding/ActivityQinxueBinding.java
编译出的中间产物,做数据绑定的。
流程分析
1、inflate 填充View,看到这里调用了bind
public static ActivityQinxueBinding inflate(@NonNull android.view.LayoutInflater inflater, @Nullable android.databinding.DataBindingComponent bindingComponent) {
return bind(inflater.inflate(google.architecture.specific.R.layout.activity_qinxue, null, false), bindingComponent);
}
2、bind检查了tag,上面编译的中间产物中已经标明了tag。返回了ActivityQinxueBinding 对象。
public static ActivityQinxueBinding bind(@NonNull android.view.View view, @Nullable android.databinding.DataBindingComponent bindingComponent) {
if (!"layout/activity_qinxue_0".equals(view.getTag())) {
throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
}
return new ActivityQinxueBinding(bindingComponent, view);
}
3、ActivityQinxueBinding 初始化
public ActivityQinxueBinding(@NonNull android.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
super(bindingComponent, root, 0);
final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds); //遍历view,并保存
this.mboundView0 = (android.widget.LinearLayout) bindings[0]; //本地变量指向 view
this.mboundView0.setTag(null);
this.mboundView2 = (android.widget.EditText) bindings[2];
this.mboundView2.setTag(null);
this.textView = (android.widget.TextView) bindings[1];
this.textView.setTag(null);
setRootTag(root); // rootView的tag设置为this,建立两者关系。
// listeners
invalidateAll(); //刷新绑定,所有的view都绑定
}
4、数据绑定
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x2L;
}
requestRebind(); //关键方法,无论哪个View绑定数据都会调用这个
}
5、ViewDataBinding.java 里的方法
/**
* @hide
*/
protected void requestRebind() {
if (mContainingBinding != null) { //没有设置的话就是null
mContainingBinding.requestRebind();
} else {
synchronized (this) {
if (mPendingRebind) { //在mRebindRunnable执行之前都是true,防止多次重新刷新
return;
}
mPendingRebind = true;
}
//执行刷新,线面是个版本适配,做的事情是一致的,看下面的mUIThreadHandler就行了,切到主线程
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
4、Runnable
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false; //置标记位允许后面的rebind执行
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
//if 代码块里是个判断View是否Attach到Window,监听到window,最终还是执行executePendingBindings();
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();
}
};
5、ViewDataBinding 的executePendingBindings
/**
* Evaluates the pending bindings, updating any Views that have expressions bound to
* modified variables. This <b>must</b> be run on the UI thread.
*/
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
if (!hasPendingBindings()) {
return;
}
mIsExecutingPendingBindings = true;//标记位防止重复进入
mRebindHalted = false;
if (mRebindCallbacks != null) { //这个回调没设置就是null,分析流程可不不管他
mRebindCallbacks.notifyCallbacks(this, REBIND, null); //绑定前回调,这里可能重置mRebindHalted,这是设置拦截的逻辑
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);//通知被拦截
}
}
if (!mRebindHalted) {
executeBindings();// 关键代码,在ViewDataBinding这个类中是个空方法,需查看子类
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}
6、ActivityQinxueBinding类中,实际绑定代码
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userInput = mUserInput;
if ((dirtyFlags & 0x3L) != 0) {
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userInput);
android.databinding.adapters.TextViewBindingAdapter.setText(this.textView, userInput);//设置数据
}
if ((dirtyFlags & 0x2L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);//设置监听
}
}
dirtyFlags的解释
flag | 二进制 |
---|---|
0x3L | 011 |
0x2L | 010 |
a、参考 mDirtyFlags = 0x2L; 所有(dirtyFlags & flag) != 0 都会执行绑定
b、先看设置数据代码
public void setUserInput(@Nullable java.lang.String UserInput) {
this.mUserInput = UserInput;
synchronized(this) {
mDirtyFlags |= 0x1L; //这里是1
}
notifyPropertyChanged(BR.userInput);
super.requestRebind();
}
(dirtyFlags & 0x2L) == 0 的,不会执行设置监听的操作,只会执行数据绑定。