jetpack系列之databinding

DataBinding是一个实现数据和UI绑定的框架,同时也是实现MVVM模式比较常用的一个依赖工具库.简单说把数据映射到 view 的 xml中,不用去findViewById 数据和UI建立映射关系,有点类似bind机制的内存映射,只是这里不是内存的映射而是数据和UI的映射
databinding 并不是MVVM特有的,MVP架构也可以使用,databinding是工具而已
viewModle 依赖库也和MVVM的 VM不是一个东西,VM是控制层 而viewModel是一个jetpack的组件库 mvvm更适合界面更新频繁 数据在多个页面服用的场景

使用

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状态等逻辑封装判断,不用我们手写代码。使用起来简单,高效。

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

推荐阅读更多精彩内容