美文网首页
jetpack系列之databinding

jetpack系列之databinding

作者: gogoingmonkey | 来源:发表于2021-08-11 14:44 被阅读0次

    DataBinding是一个实现数据和UI绑定的框架,同时也是实现MVVM模式所依赖的工具.简单说把数据映射到 view 的 xml中,不用去findViewById

    使用

    1.开启 DataBinding 功能

    哪个module需要使用DataBinding就在对应的build.gradle编译配置文件中添加如下代码声明。开启后,在编译时就会执行Databinding相关的Task.

    android {
        ...
        dataBinding {
            enabled = true
        }
        ...
    }
    
    2.布局中的变化

    布局文件的写法变化,在之前的层级上增加了layout 和data标签,data标签不是必须的。
    其中myuser是一个临时命名。

    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context=".ThirdActivity">
        <data>
            <variable
                name="myuser"
                type="com.android.projects.firstkotlinapplication.databinding.User" />
        </data>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:text="@{myuser.name}"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:text="@{myuser.age}"/>
        </LinearLayout>
    </layout>
    

    必须要使用 <layout> 这个标签作为 xml 的根部局,所有 DataBinding 的内容都写在 <data >标签内
    <import > 是导入所使用的对象的类型
    <variable > 是具体声明一个数据对象,<name> 是这个对象的名字,<type> 是这个对象的类型,<variable > 可以有多个
    使用 @{ } / @{book.price} 的方式可以把这个声明出来的数据对象的莫个数据设置给我们的 view

    3.Activity中的实现

    替换之前的setContentView(布局),其实源码里面还是调用了。

       binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
            user=new User("测试","密码:999");
            binding.setUser(user);
            binding.setVariable(BR.name,"测试。。。。。");
    

    再看下User类的定义,我们让这个继承BaseObservable ,同时通过注解再对象的get XX方法上,完成定义。

    public class User extends BaseObservable {
        private String name;
        private String pwd;
    
        public User(String name, String pwd) {
            this.name = name;
            this.pwd = pwd;
        }
    
        @Bindable
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
            notifyPropertyChanged(BR.name);
        }
    
        @Bindable
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
            notifyPropertyChanged(BR.pwd);
        }
    }
    

    这样,整个简单实用,基本就完成了,当然我们实际使用时,数据更新,可能是从网络获取到数据,也就是viewModel更新数据,更新User对象,更新UI。

    源码分析

    1.布局文件分析

    我们可能会想到,布局文件这样写,怎么就能实现解析了。databinding框架在我们编译时候,会生成几个重要的文件如下图
    首先是两个布局文件,第一个里面文件中,部分代码如下,我们布局中的每个View都被标记为targets,定义了一个tag,同时view表明之前是一个什么View类型。第二个布局,则是和我们最原始的布局几乎一致,只是每个View 中多了一个tag android:tag="binding_1"

    <Targets>
            <Target tag="layout/activity_main_0" view="LinearLayout">
                <Expressions />
                <location endLine="35" endOffset="18" startLine="9" startOffset="4" />
            </Target>
    
    image.png

    除了上面生成的代码,还有一个比较重要的生成类如下:


    image.png
    2.继续看DataBindingUtil类

    我们在activity中使用这个工具类去设置布局,里面设置布局,里面可以看到还调用setContentView, 然后获取到布局的content再作为参数传到bindToAddedViews方法

    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);
        }
    

    里面调用bind 方法,同样会调用

    sMapper.getDataBinder(bindingComponent, roots, layoutId)
    
    3.APT生成的DataBinderMapperImpl类 的getDataBinder方法

    看下面代码,如果命中判断,标签是我们定义的最外层的activity_main_0 就创建ActivityMainBindingImpl ,注意activity_main_0 就是我们布局提到的第一个生成的布局文件中的 tag,而创建的实现类,就是APT生成的第三个类

    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;
      }
    
    4.创建ActivityMainBindingImpl类

    注意到构造方法里面传入了3,这个3,就是布局文件,原始布局文件中View的个数,比如我一个线性布局中嵌套放了5个TextView,那么这个地方就是6.里面主要是创建 Object[] bindings 数组,存放的是对XML文件信息读取数据,主要调用mapBindings方法,里面代码多,主要就是解析XML保存到数组中。

     public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
            this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
        }
    
    
     protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots,
                int numBindings, 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;
        }
    //mapBindings 方法太长了。不复制了
    

    >>>>>>>>>>>>> 上面是调用DataBindingUtil设置布局的逻辑>>>>>>>>>

    二、调用 binding.setVariable源码分析

    这个方法点进去是一个抽象方法,里面具体实现在APT生成的ActivityMainBindingImpl类中。

    1. setVariable方法

    我们在更改User对象时候,可能直接调用setUser("密码","123456");就如下的判断,BR 也是生成的一个类,代码中all是0 .如果命中的是user就调用setUser方法

     public boolean setVariable(int variableId, @Nullable Object variable)  {
            boolean variableSet = true;
            if (BR.user == variableId) {
                setUser((com.example.databindingdemo.User) variable);
            }
            else {
                variableSet = false;
            }
                return variableSet;
        }
    
    public class BR {
      public static final int _all = 0;
    
      public static final int name = 1;
    
      public static final int pwd = 2;
    
      public static final int user = 3;
    }
    

    核心还是调用下面的updateRegistration 方法

    public void setUser(@Nullable com.example.databindingdemo_20210117.User User) {
            updateRegistration(0, User);
            this.mUser = User;
            synchronized(this) {
                mDirtyFlags |= 0x1L;
            }
            notifyPropertyChanged(BR.user);
            super.requestRebind();
        }
    
    5.updateRegistration 方法

    CREATE_PROPERTY_LISTENER是一个回调接口,onCreate方法中会创建弱引用的监听,里面

    protected boolean updateRegistration(int localFieldId, Observable observable) {
            return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
        }
    

    下面方法中首先从饮用的监听的数组中取出弱引用的监听,如果不为空,调用registerTo方法注册

    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;
        }
    
    5.调用registerTo方法

    listenerCreator就是之前的那个CREATE_PROPERTY_LISTENER,
    调用create方法就是调用 return new WeakPropertyListener(viewDataBinding,

     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.setLifecycleOwner(mLifecycleOwner); 里面其实就是把被观察者 与listener绑定,里面就是前面说到的lifecycle的addObserver()

     public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
                mObservable.setLifecycleOwner(lifecycleOwner);
            }
    

    重点还是要看 listener.setTarget(observable);

    public void setTarget(T object) {
                unregister();
                mTarget = object;
                if (mTarget != null) {
                    mObservable.addListener(mTarget);
                }
            }
    

    再继续看进去 addListener 方法, 是一个接口的抽象方法,里面四个实现,重点看WeakPropertyListener的里面会调用到 target.addOnPropertyChangedCallback(this);
    会执行到 下面方法,里面的mCallbacks.add(callback);ViewDataBinding保存在一个List中

     public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
            synchronized (this) {
                if (mCallbacks == null) {
                    mCallbacks = new PropertyChangeRegistry();
                }
            }
            mCallbacks.add(callback);
        }
    
    5.

    上面只是完成updateRegistration(0, User);方法的逻辑实现更新监听,执行Dirty 再执行下面我们看
    notifyPropertyChanged(BR.user); 方法实现

      public void setUser(@Nullable com.example.databindingdemo.User User) {
            updateRegistration(0, User);
            this.mUser = User;
            synchronized(this) {
               mDirtyFlags |= 0x1L;
            }
            notifyPropertyChanged(BR.user);
            super.requestRebind();
        }
    
    
    public void notifyPropertyChanged(int fieldId) {
            synchronized (this) {
                if (mCallbacks == null) {
                    return;
                }
            }
            mCallbacks.notifyCallbacks(this, fieldId, null);
        }
    

    三、刷新流程

    1.notifyPropertyChanded 方法调用。

    重点看notifyRecurse(sender, arg, arg2); 方法,后面都是移除回调

    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;
                }
            }
    
    2.
    private void notifyRecurse(T sender, int arg, A arg2) {
         ...删除其他代码
            notifyRemainder(sender, arg, arg2, remainderIndex)
        }
    
    调用notifyFirst64方法
    notifyFirst64(sender, arg, arg2);
    

    再调用到

    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 类的onPropertyChanged方法

    执行到监听的属性改变方法
    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); //重点
            }
    

    再调用 binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);

    再调用requestRebind

    下面看编舞者类的另一个分支,通过一个handler执行runnable。

    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); //重点
                }
            }
        }
    
    Runnble中执行 executePendingBindings()
        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();
            }
        };
    

    上面代码会再次执行到ViewDataBinding类的executeBindings方法,

    会调用到APT 生成类的executeBindings方法
        long dirtyFlags = 0;
            synchronized(this) {
                dirtyFlags = mDirtyFlags;
                mDirtyFlags = 0;
            }
            java.lang.String userName = null;
            com.example.databindingdemo.User user = mUser;
            java.lang.String userPwd = null;
    
            if ((dirtyFlags & 0xfL) != 0) {
    
    
                if ((dirtyFlags & 0xbL) != 0) {
    
                        if (user != null) {
                            // read user.name
                            userName = user.getName();
                        }
                }
                if ((dirtyFlags & 0xdL) != 0) {
    
                        if (user != null) {
                            // read user.pwd
                            userPwd = user.getPwd();
                        }
                }
            }
            // batch finished
            if ((dirtyFlags & 0xbL) != 0) {
                // api target 1
    
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
            }
            if ((dirtyFlags & 0xdL) != 0) {
                // api target 1
    
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userPwd);
            }
        }
    

    可以看到上面的代码,TextViewBindingAdapter.setText ,感觉快到了!

     public static void setText(TextView view, CharSequence text) {
            final CharSequence oldText = view.getText();
            if (text == oldText || (text == null && oldText.length() == 0)) {
                return;
            }
            if (text instanceof Spanned) {
                if (text.equals(oldText)) {
                    return; // No change in the spans, so don't set anything.
                }
            } else if (!haveContentsChanged(text, oldText)) {
                return; // No content changes, so don't set anything.
            }
            view.setText(text);
        }
    
    

    上面就是当我们调用setUser这种或者setVariable方法,会调用到notifyPropertiChange方法,去更新binding,因为我们在使用DataBindingUtil 设置布局时,建立了监听关系,这个时候会通知发生变化,当然,中间还增加判断了,被观察者是否处于active状态,才能更新,最后会调用到我们在APT阶段生成的类的executeBindings方法, 里面还是调用了对应tag的更新UI的方法。最终其实就是跟我们使用MVP 加回调方式实现是一样的,只是框架对这套回调,封装在框架里面。防止开发者忘记注销,增加是否激活状态等封装,使用更方便。

    小结

    首先就是activity是一个被观察者,被databinding观察他的生命周期变化。只有当处于激活状态,才会回调更新UI;
    其实activity是观察者,每个activity 可能会用到多个Viewmodel 对象,那么activity会在APT生成的实现类中创建数组,存放对应的tag,当数据tag发生变化后,通过DataBinding框架的监听,来通知activity 刷新UI。
    最后我们会发现,这个框架其实思路还是比较简单,只是对绑定关系,回调,即是否处于active状态等逻辑封装判断,不用我们手写代码。使用起来简单,高效。

    相关文章

      网友评论

          本文标题:jetpack系列之databinding

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