Android DataBinding原理分析

作者: Joker_Wan | 来源:发表于2019-12-09 11:34 被阅读0次

    一、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时也会得心应手。

    相关文章

      网友评论

        本文标题:Android DataBinding原理分析

        本文链接:https://www.haomeiwen.com/subject/uwdbgctx.html