Android DataBinding原理分析

一、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.png

image.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,然后还传入sIncludessViewsWithIds,前者是布局中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();
        }
    };

mRebindRunnablerun()方法执行时,先同步吧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()mDirtyFlags0x8L,转为二进制第四位为1,上述四个if条件经过位与运算后与0比较都为真,即这种情况下上述条件里面的代码都会执行。这里主要执行的操作:

  1. 获取userInfoName,将userInfoName绑定到mboundView1,获取userInfoNickNameGet,并将其绑定到mboundView2,通过代码可以看到,每一个被绑定到View上的通过variable标签定义的变量都会有一个专属的标记位,当该变量的值被更新时,对应的脏标记位就会置为1,executeBindings()执行的时候就会将变动的数据更新到对应的UI控件上。

  2. 在设置了双向绑定的控件上,为其添加对应的监听器,监听其变动,如: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()回调到传入的mboundView2androidTextAttrChangedonChange()

    在这里我们也看到了熟悉的@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时,调用传进来的监听器CreateWeakListenercreate(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时也会得心应手。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容