Google JetPack 之 DataBinding

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二进制
0x3L011
0x2L010

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 的,不会执行设置监听的操作,只会执行数据绑定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值