美文网首页
Jetpack源码解析(四)之Data Binding

Jetpack源码解析(四)之Data Binding

作者: 慕尼黑凌晨四点 | 来源:发表于2021-02-17 21:12 被阅读0次

    Data Binding(数据绑定库)是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

    所谓声明式UI,就是你在代码中做出的任何改变,都会实时的在界面中展示出来。与之对应的是命令式UI,当你想要改变界面时,必须调用XX.setText()之类的代码,才能使界面做出改变。

    声明式/命令式

    用传统的命令式UI,当要改变数据时,要如下操作:

        findViewById<TextView>(R.id.sample_text).apply {
            text = viewModel.userName
        }
    

    而用数据绑定后,只需在xml中声明如下,无需任何逻辑代码,当username变化时,组件自动发生变化。

    <TextView
            android:text="@{viewmodel.userName}" />
    

    Data Binding并不仅仅是为了取代findViewById()。如果你的主要目的是取代 findViewById() 调用,请考虑改用视图绑定【View Binding】 或者 直接用Kotlin。

    Data Binding 使用

    当然,如果直接在界面上这样写是没用的,得配置下。app:build.gradle中开启:

    dataBinding {
        enabled = true
    }
    

    xml界面更改为如下样式即可:

    <?xml version="1.0" encoding="utf-8"?>
    <layout 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">
    
        <data>
            <variable
                name="msg"
                type="String" />
        </data>
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
            <TextView
                android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{msg,default = `Hello World`}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

    在Activity界面中获取binding,直接操作即可:

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val binding: ActivityMainBinding = DataBindingUtil.setContentView(
                this, R.layout.activity_main)
    
            Thread{
                var i = 0
                while (true){
                    Thread.sleep(1000)
                    //听说异步线程不能更新UI?sorry,我可以。因为我只改变了值,没有调用`更新UI`代码~
                    binding.msg = i++.toString()
                }
            }.start()
    
        }
    }
    

    原理

    主要是用到了APT(Annotation Processing Tool/注解处理工具)技术。在编译时对注解进行解析,自动生成代码,并编译代码生成class文件。

    在项目的 build 目录下,可以看到生成的代码文件:

    build/generated/source build/intermediates/data_binding_xx
    image-20210101133756494.png image-20210101134221268.png

    先看我们自己写的xml文件。像这种layout标签,不开启dataBinding的情况下编译器是铁定不识别的。所以我们有理由相信dataBinding后面肯定是将这个xml文件处理成了传统的xml布局格式。在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目录下,可以找到一个activity_main.xml文件,这个文件就是处理完后的xml文件了。

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity" 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:id="@+id/text"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="Hello World"                   
                  app:layout_constraintBottom_toBottomOf="parent"
                  app:layout_constraintLeft_toLeftOf="parent"
                  app:layout_constraintRight_toRightOf="parent"
                  app:layout_constraintTop_toTopOf="parent"  android:tag="binding_1"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
             
    

    这个文件和我们自己写的布局文件后半部分很像,就只有一点不一样:在有用到dataBinding的地方,多了个tag标签--android:tag="binding_1"。那前半部分<data>标签去哪了呢?在第二张截图所展示目录下:

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <Layout layout="activity_main" modulePackage="com.cy.myapplication" filePath="app\src\main\res\layout\activity_main.xml" directory="layout" isMerge="false" isBindingData="true" rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
        <Variables declared="true" type="String" name="msg">
            <location startLine="6" startOffset="8" endLine="8" endOffset="27"/>
        </Variables>
        <Targets>
            <Target tag="layout/activity_main_0" view="androidx.constraintlayout.widget.ConstraintLayout">
                <Expressions/>
                <location startLine="11" startOffset="4" endLine="26" endOffset="55"/>
            </Target>
            <Target id="@+id/text" tag="binding_1" view="TextView">
                <Expressions>
                    <Expression text="msg,default = `Hello World`" attribute="android:text">
                        <Location startLine="20" startOffset="12" endLine="20" endOffset="56"/>
                        <TwoWay>false</TwoWay>
                        <ValueLocation startLine="20" startOffset="28" endLine="20" endOffset="54"/>
                    </Expression>
                </Expressions>
                <location startLine="16" startOffset="8" endLine="24" endOffset="55"/>
            </Target>
        </Targets>
    </Layout>
    

    通过和我们自己写的xml对比着来看,可以看到<variable>中的信息是完全转移到了这个xml中的;原布局文件中用到了哪些布局,布局中又引入了哪些variable中的变量,也可以在xml中的Target--Expression下找得到。所以这两份文件可以包含我们所写的xml中的所有信息了。

    DataBindingUtil

    再看我们的Java/Kotlin代码。DataBindingUtil.setContentView():

    private static DataBindingComponent sDefaultComponent = null;
    
    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);
        }
    }
    

    先调用activity.setContentView(layoutId);;然后获取decorView中的contentView,将contentView的子view传入bind方法中去【如果多个子view就用数组】。这个contentView中包裹的就是我们自己写的布局,所以第一次进入childCount为1,走第一个方法。 不过后面我还是把传view和传view[]这两个方法代码都贴上来了,注意区分。

    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }
    
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
    

    bind方法又转到了sMapper的getDataBinder方法中去。sMapper是个DataBinderMapper对象。

    DataBinderMapper

    public class DataBinderMapperImpl extends MergedDataBinderMapper {
      DataBinderMapperImpl() {
        addMapper(new com.cy.myapplication.DataBinderMapperImpl());
      }
    }
    
    public class MergedDataBinderMapper extends DataBinderMapper {
        public void addMapper(DataBinderMapper mapper) {
            Class<? extends DataBinderMapper> mapperClass = mapper.getClass();
            if (mExistingMappers.add(mapperClass)) {
                mMappers.add(mapper);
                final List<DataBinderMapper> dependencies = mapper.collectDependencies();
                for(DataBinderMapper dependency : dependencies) {
                    addMapper(dependency);
                }
            }
        }
    }
    

    DataBinderMapperImpl继承MergedDataBinderMapper继承DataBinderMapperDataBinderMapperImpl就一个构造方法,getDataBinder方法的实现在MergedDataBinderMapper中。

    MergedDataBinderMapper中又持有一个DataBinderMapper的数组:mMappers。该对象在new DataBinderMapperImpl的时候被填充进去数据,这个数据就是APT为我们生成的类。

    public class MergedDataBinderMapper extends DataBinderMapper {
        private List<DataBinderMapper> mMappers = new CopyOnWriteArrayList<>();
        @Override
        public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
                int layoutId) {
            for(DataBinderMapper mapper : mMappers) {
                ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
                if (result != null) {
                    return result;
                }
            }
            if (loadFeatures()) {
                return getDataBinder(bindingComponent, view, layoutId);
            }
            return null;
        }
    
        @Override
        public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] view,
                int layoutId) {
            for(DataBinderMapper mapper : mMappers) {
                ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
                if (result != null) {
                    return result;
                }
            }
            if (loadFeatures()) {
                return getDataBinder(bindingComponent, view, layoutId);
            }
            return null;
        }   
    }
    

    所以不出所料的,MergedDataBinderMapper中的getDataBinder方法最终转交给了APT生成的Mapper中的getDataBinder。

    APT-MergedDataBinderMapper

    所以来到APT生成的MergedDataBinderMapper中:

    //setContentView(activity, layoutId, sDefaultComponent);
    //bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    //bind(component, contentView‘s children, layoutId);
    //上面是之前一路走来的用到的方法,写这里免得大家忘了。
    
    private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
    
    static {
        INTERNAL_LAYOUT_ID_LOOKUP.put(com.cy.myapplication.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
    }
    
    @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;
    }
    
    @Override
    public ViewDataBinding getDataBinder(DataBindingComponent component, View[] views, int layoutId) {
      if(views == null || views.length == 0) {
        return null;
      }
      int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
      if(localizedLayoutId > 0) {
        final Object tag = views[0].getTag();
        if(tag == null) {
          throw new RuntimeException("view must have a tag");
        }
        switch(localizedLayoutId) {
        }
      }
      return null;
    }
    

    到这里第一次进入传来的view就是带Tag的activity_main.xml,所以tag能对的上,返回的就是ActivityMainBindingImpl(component, view);

    对了,到这的时候,component = null,view是Activity_main中的最外层view。

    APT-ActivityMainBindingImpl

    首先,这个类也是APT帮我们生成的。

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = null;
    }//上面是要用到的数据
    //下面是构造方法
    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds));
    }
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
              , (android.widget.TextView) bindings[1]
             );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.text.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }
    

    mapBindings

    这里用到了一个方法生成Object[]:由于mapBinding方法略长,所以没打算贴出来。

    这个方法相当于是对我们传入的这个view进行一次解析,解析完成后返回的Object[]数组里面包含的就是这个view中所有的view对象。

    image-20210101210706060.png

    然后在初始化方法中setTag中将数组中的对象打上标签tag并存起来。比如this.text就是我们布局文件中的textView,其id叫text。所以我们能通过binding.text获得该控件。

    APT-setMsg()

    public void setMsg(@Nullable java.lang.String Msg) {
        this.mMsg = Msg;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.msg);
        super.requestRebind();
    }
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
       flag 0 (0x1L): msg
       flag 1 (0x2L): null
    flag mapping end*/
    

    这个mDirtyFlags值是后面用作判断修改哪个值用的。然后跟着notifyPropertyChanged()方法;

    public void notifyPropertyChanged(int fieldId) {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }
    

    CallbackRegistry.notifyCallbacks()

    public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
        mNotificationLevel++;
        //--------------------------主要看这个方法----------
        notifyRecurse(sender, arg, arg2);
        mNotificationLevel--;
        if (mNotificationLevel == 0) {
            if (mRemainderRemoved != null) {
                for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
                    final long removedBits = mRemainderRemoved[i];
                    if (removedBits != 0) {
                        removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
                        mRemainderRemoved[i] = 0;
                    }
                }
            }
            if (mFirst64Removed != 0) {
                removeRemovedCallbacks(0, mFirst64Removed);
                mFirst64Removed = 0;
            }
        }
    }
    
    private void notifyRecurse(T sender, int arg, A arg2) {
        final int callbackCount = mCallbacks.size();
        final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
    
        // Now we've got all callbakcs that have no mRemainderRemoved value, so notify the
        // others.
        //----------------------------看这个方法!!!-------------
        notifyRemainder(sender, arg, arg2, remainderIndex);
    
        // notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
        // However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
        final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
    
        // The remaining have no bit set
        notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
    }
    
    private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
        if (remainderIndex < 0) {
            //-----------------------走这个!!!!!!!!!--------------------
            notifyFirst64(sender, arg, arg2);
        } else {
            final long bits = mRemainderRemoved[remainderIndex];
            final int startIndex = (remainderIndex + 1) * Long.SIZE;
            final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
            notifyRemainder(sender, arg, arg2, remainderIndex - 1);
            notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits);
        }
    }
    private void notifyFirst64(T sender, int arg, A arg2) {
        final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
        notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
    }
    
    private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
                                 final int endIndex, final long bits) {
        long bitMask = 1;
        for (int i = startIndex; i < endIndex; i++) {
            if ((bits & bitMask) == 0) {
                mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
            }
            bitMask <<= 1;
        }
    }
    

    PropertyChangeRegistry.NotifierCallback.onNotifyCallback

    private static final NotifierCallback<OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new NotifierCallback<OnPropertyChangedCallback, Observable, Void>() {
        public void onNotifyCallback(OnPropertyChangedCallback callback, Observable sender, int arg, Void notUsed) {
            callback.onPropertyChanged(sender, arg);
        }
    };
    

    ViewBinding.onPropertyChanged

    @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);
    }
    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方法,这个方法实现在imp类中:

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
        }
        return false;
    }
    

    返回true则认为数据发生了改变,走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);
            }
        }
    }
    

    最后要么走postFrameCallback,要么走mUIThreadHandler.post方法。其实两个方法是一样的,最终都是走的post方法中来,所以这里直接看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();
        }
    };
    
    public void executePendingBindings() {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else {
            mContainingBinding.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;
    }
    

    其中的executeBindings是抽象方法,实现在ActivityMainbindingImpl中:

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        com.cy.myapplication.Girl girl = mGirl;
        java.lang.String girlName = null;
        java.lang.String msg = mMsg;
    
        if ((dirtyFlags & 0x5L) != 0) {
    
    
    
                if (girl != null) {
                    // read girl.name
                    girlName = girl.getName();
                }
        }
        if ((dirtyFlags & 0x6L) != 0) {
        }
        // batch finished
        if ((dirtyFlags & 0x6L) != 0) {
            // api target 1
    
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.text, msg);
        }
        if ((dirtyFlags & 0x5L) != 0) {
            // api target 1
    
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.text2, girlName);
        }
    }
    

    看到这里便真相大白了。我们所苦苦寻找的更改界面的方法就在这里了。

    最后附上一副整体的UML图:

    image-20210103190946285.png

    相关文章

      网友评论

          本文标题:Jetpack源码解析(四)之Data Binding

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