Jetpack 之 Databinding

501 阅读11分钟

MVVM 的结构 把原来 View 中所有的数据都单独抽离到一个 ViewModel 中,这个ViewModel可以从原来的Model层去取数据 model 层每次来了数据,都由 ViewModel 去通知 UI 修改数据,同时可以将视图 view 和 model进行双向绑定 MVP 的数据 要通过中间传到 view

下面以一个实例来分析下 Databinding 的实现原理

实例

例子很简单,就是在一个页面上显示一个用户名,每过1s钟在用户名后边加个1,使用Databinding来实现这个效果

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;//这个类在我们编译时生成
    User user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        //
        binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
        user=new User("jett","123");
        binding.setUser(user);

        new Thread(){
            @Override
            public void run() {
                super.run();

                for (int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    user.setName(user.getName()+"1");
                    // setUser 和 setVariable 是一样的操作
                    binding.setUser(user);
                    //当调用下面这句代码后,user 对象里面的数据就会一个一个填充到
                   //对应的 view 上去,数据的填充实现是在 ActivityMainBindingImpl的
                   // executeBindings() 方法上
                    binding.setVariable(BR.user,user);
                }
            }
        }.start();
    }
}

保存用户名和密码的实体类

public class User extends BaseObservable {
    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    @Bindable//apt 会读到这个注解,生成代码
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        //BR 相当于和我们的 R 文件是一样的
        //UI 的更新会调这个方法来完成
        notifyPropertyChanged(BR.name);
    }

    @Bindable
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

源码分析

下面的这个文件是AS编译后的文件 activity_main-layout.xml

该文件的路径如下图


activity_main-layout.xml

<!--?xml version="1.0" encoding="utf-8" standalone="yes"?--> 
  //modulepackage这个标签的包就是我们写的代码
  <layout layout="activity_main" modulepackage="com.example.databinding_demo_20200329"
 filepath="D:\AndroidFindingJobs\AndroidEnjoyLProjects\ViewModelProjects\DataBinding_
Demo_20200329\app\src\main\res\layout\activity_main.xml" directory="layout" ismerge="false">

   <variables declared="true" type="com.example.databinding_demo_20200329.User" name="user">
    <location startline="8" startoffset="8" endline="10" endoffset="63" />
   </variables>

   <targets>

    //这些 tag 中的 _0、  _1 、_3  之后分别对应于 bindings 数组的下标
// 所以 bindings[0] 放的是 LinearLayout对象,bindings[1] bindings[2]放的是TextView
//target标签:属性(view)+字符串(LinearLayout)    
<target tag="layout/activity_main_0" view="LinearLayout">
     <expressions />
     <location startline="14" startoffset="4" endline="42" endoffset="18" />
    </target>

    <target id="@+id/tv1" tag="binding_1" view="TextView"> //target标签,注意这里的tag标签
     <expressions>
      <expression text="user.name" attribute="android:text">
       <location startline="24" startoffset="12" endline="24" endoffset="38" />
       <twoway>
        false
       </twoway>
       <valuelocation startline="24" startoffset="28" endline="24" endoffset="36" />
      </expression>
     </expressions>
     <location startline="20" startoffset="8" endline="28" endoffset="55" />
    </target>

    <target id="@+id/tv2" tag="binding_2" view="TextView">
     <expressions>
      <expression text="user.password" attribute="android:text">
       <location startline="36" startoffset="12" endline="36" endoffset="42" />
       <twoway>
        false
       </twoway>
       <valuelocation startline="36" startoffset="28" endline="36" endoffset="40" />
      </expression>
     </expressions>
     <location startline="32" startoffset="8" endline="40" endoffset="55" />
    </target>

   </targets>

  </layout>

下面是另一份AS编译后生成的 activity_main.xml 文件

文件路径如下图


activity_main.xml

这个文件和我们手写的xml文件很像,只是中间空出了一部分

<?xml version="1.0" encoding="utf-8"?>
这中间空出的一部分就是上面的activity_main-layout.xml对data进行的描述,as编译后把它们抽取分离了
                                                       
                                                   

    
                                                  
    
                 
                       
                                                                

           

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        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/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
//注意这里的binding_1 的标签,后面还要类似的android:tag="binding_2",
//在上面第一个文件里也有这个标签,这样就把这两个文件分离出来了,通过tag值进行绑定
            android:tag="binding_1"    
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
<!-- android:text="@{user.name}" 这种写法是和 User类中的数据进行了绑定-->
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_2"        
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </LinearLayout>

最后看一下我们手动写的 activity_main.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">
<!-- 在布局文件和数据绑定在一起,这里导入了 User类 (其中 name=“user”随便取) -->
//使用 DataBinding 会将下面的 <data></data>的部分抽走
    <data>
        <variable
            name="user"
            type="com.example.databinding_demo_20200329.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
//凡是有填这种信息的地方,这个属性就会抽取到另外一份新生成的文件中,作为一个标签来用
//只有标记了这种 @{user.name} 代码的地方才会有 tag 标签,user.name 这个字符串会生成代码,
//里面是可以添加java代码的
            android:text="@{user.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
<!-- android:text="@{user.name}" 这种写法是和 User类中的数据进行了绑定-->
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.password}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </LinearLayout>
</layout>

下面是AS 自动生成的 BR.java

public class BR {
  public static final int _all = 0;

  public static final int password = 1;

  public static final int name = 2;

  public static final int user = 3;
}

//代码中标记了@Bindable注解的,都会在BR.java 中生成相关属性

    @Bindable//apt 会读到这个注解,生成代码
    public String getName() {
        return name;
    }

    @Bindable
    public String getPassword() {
        return password;
    }

下面来分析下实现原理

首先来看一个重要的抽象类,从注释就可以看出这个类是编译后生成的绑定类的基类

/**
 * Base class for generated data binding classes. If possible, the generated binding should
 * be instantiated using one of its generated static bind or inflate methods. If the specific
 * binding is unknown, {@link DataBindingUtil#bind(View)} or
 * {@link DataBindingUtil#inflate(LayoutInflater, int, ViewGroup, boolean)} should be used.
 */
public abstract class ViewDataBinding extends BaseObservable {
  public abstract boolean setVariable(int variableId, @Nullable Object value);

Databinding的代码写好后,build时会自动生成以下代码

//这个类是抽象类 ViewDataBinding.java 的实现类
public class ActivityMainBindingImpl extends ActivityMainBinding  {
//variableId 对应 BR.java 中的 user属性,也就是BR.user,
//从上面BR.java的代码看BR.user = 3
@Override 
public boolean setVariable(int variableId, @Nullable Object variable)  {
    boolean variableSet = true;
    if (BR.user == variableId) {
        setUser((com.example.databinding_demo_20200329.User) variable);
    }
    else {
        variableSet = false;
    }
        return variableSet;
}
......}

进入 setUser的方法

public void setUser(@Nullable com.example.databinding_demo_20200329.User User) {
    updateRegistration(0, User);//更新注册
    this.mUser = User;
    synchronized(this) {
        mDirtyFlags |= 0x1L;
    }
    notifyPropertyChanged(BR.user);
    super.requestRebind();
}
//observable 就是我们的 User类
protected boolean updateRegistration(int localFieldId, Observable observable) {
    // CREATE_PROPERTY_LISTENER 常量是一个监听器
    return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
//注册的目的:使layout文件、MainActivity、ViewModel(单独写的一个类)、ViewDataBinding
//(又是一个文件)建立相互关联的引用这些文件之间,如果将来要通信,一定要把相互关联的引用建立起来
//如果有了联系,ViewDataBinding就可以操作我们写的 User 类
private boolean updateRegistration(int localFieldId, Object observable,
        CreateWeakListener listenerCreator) {
    if (observable == null) {
        return unregisterFrom(localFieldId);
    }
 //WeakListener 数组的下标对应了刚刚在BR文件中注册的三个整数 0、1、2、3
//每一个 WeakListener 对应 BR文件里的一个属性
    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;
}
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);
}

接下来继续看DataBinding的实现,在MainActivity中有以下代码

ActivityMainBinding binding;//这个类在我们编译时生成

binding= DataBindingUtil.setContentView(this,R.layout.activity_main);

进入 setContentView 一步步深入

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

最终会进入 bind 方法

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

getDataBinder的实现在 DataBinderMapperImpl

@Override
public ViewDataBinding getDataBinder(DataBindingComponent component
, View view, int layoutId) {
  int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
  if(localizedLayoutId > 0) {
    //获取activity_main-layout.xml中的 tag 标签:<target tag="layout/activity_main_0"
    final Object tag = view.getTag();
    if(tag == null) {
      throw new RuntimeException("view must have a tag");
    }
    switch(localizedLayoutId) {
      case  LAYOUT_ACTIVITYMAIN: {
        //如果 tag="layout/activity_main_0" 直接new一个 ActivityMainBindingImpl
        if ("layout/activity_main_0".equals(tag)) {
          return new ActivityMainBindingImpl(component, view);
        }
        ......
      }
    }
  }
  return null;
}
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent 
bindingComponent, @NonNull View root) {
 //下面的 mapBindings 方法一旦跑完,xml中定义的 view (一个LinearLayout 和两个 TextView)
//就会存到数组 bindings 中
    this(bindingComponent, root, 
             mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}

下面看一下 mapBindings 的实现,在 ViewDataBinding中

protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
        int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {

//读取xml文件时,有几个tag标签我们是可以知道的
   Object[] bindings = new Object[numBindings];

//mapBindings 方法一旦跑完,xml中定义的 view (一个LinearLayout 和两个 TextView),
//就会存到数组 bindings 中    
mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);    
return bindings;
}

方法 mapBindings 主要是解析之前AS编译后生成的两个xml文件,读取到xml进行一些列的字符拼接、判断,读取标签时,是 View 的就会往数组里面存,如果是 ViewGroup的就会继续找里面是 View 的 这样一步一步深入

private static void mapBindings(DataBindingComponent bindingComponent, View view,
        Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
        boolean isRoot) {
    final int indexInIncludes;
    final ViewDataBinding existingBinding = getBinding(view);
    if (existingBinding != null) {
        return;
    }
 //从标签中读取信息,然后创建根据信息View解析的就是 <data></data> 标签生成的那个
// activity_main-layout.xml文件 和 有空白的那个activity_main.xml 文件
    Object objTag = view.getTag();//获取标签
    final String tag = (objTag instanceof String) ? (String) objTag : null;
    boolean isBound = false;
//如果标签是 layout 开头的,对应的 view 会放到数组 bindings 里面
    if (isRoot && tag != null && tag.startsWith("layout")) {
        final int underscoreIndex = tag.lastIndexOf('_');
        if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
            final int index = parseTagInt(tag, underscoreIndex + 1);
//将 view 存到 对应id 的 bindings 数组里,这里的view 就是layout_xml 文件里我们定义的两个TextView
            if (bindings[index] == null) {
                bindings[index] = view;
            }
          ......
            if (!isInclude) {//最后调用自己 进行递归 
                mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
            }
        }
    }
}

mapBindings方法执行完成后,返回一个存有 activity_main.xml中的View的数组

之后通过 this调用 ActivityMainBindingImpl 的构造方法,此时xml的解析已经完成

private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent 
bindingComponent, View root, Object[] bindings) {
    super(bindingComponent, root, 1
        , (android.widget.TextView) bindings[1]
        , (android.widget.TextView) bindings[2]
        );
    this.mboundView0 = (android.widget.LinearLayout) bindings[0];
    //原来的标签不需要了,直接设置为空 null 
    this.mboundView0.setTag(null);
    //this 就是 Activity tv1、tv2就是 我们在 xml文件中定义的TextView
    this.tv1.setTag(null);
    this.tv2.setTag(null);
    setRootTag(root);
    // listeners
    invalidateAll();
}

上面我们走完了DataBinding中解析xml的源码,继续往下走,看一下数据是怎么绑定到 UI 上以及当我们的数据发生改变时是怎么来通知 UI 更新的

看一下 ViewDatBinding 的静态初始化块(类一用马上就开始执行)

static {
    if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
        ROOT_REATTACHED_LISTENER = null;
    } else {
                                      //添加绑定状态改变的监听
        ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
            @TargetApi(VERSION_CODES.KITKAT)
            @Override
            public void onViewAttachedToWindow(View v) {
                // execute the pending bindings.
                final ViewDataBinding binding = getBinding(v);
                //通过一个线程实现,数据填充到 UI
                binding.mRebindRunnable.run();
                v.removeOnAttachStateChangeListener(this);
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
            }
        };
    }
}

看一下 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();
    }
}
private void executeBindingsInternal() {
   ......
    if (!mRebindHalted) {
        executeBindings();//执行绑定
        if (mRebindCallbacks != null) {
            mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
        }
    }
    mIsExecutingPendingBindings = false;
}

在ViewDataBinding中 executeBindings() 是一个抽象方法

protected abstract void executeBindings();

该方法的实现在 ActivityMainBindingImpl 

@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    java.lang.String userName = null;
    com.example.databinding_demo_20200329.User user = mUser;
    java.lang.String userPassword = null;
    if ((dirtyFlags & 0xfL) != 0) {
        if ((dirtyFlags & 0xbL) != 0) {
                if (user != null) {
                    // read user.name
                   //调用我们自己写的User对象的方法来获取数据
                    userName = user.getName();
                }
        }
        if ((dirtyFlags & 0xdL) != 0) {
                if (user != null) {
                    // read user.password
                    userPassword = user.getPassword();
                }
        }
    }
    // batch finished
    if ((dirtyFlags & 0xbL) != 0) {
        // api target 1
       //把值绑定到 View 上,这里最终会调用TextView的setText方法
        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
    }
    if ((dirtyFlags & 0xdL) != 0) {
        // api target 1
        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userPassword);
    }
}

以上是将数据绑定到UI的过程,下面看一下数据更新流程

我们在调用 user的setName方法更改数据时,会去执行一个notify方法   notifyPropertyChanged(BR.name);

public void notifyPropertyChanged(int fieldId) {
  ......
    mCallbacks.notifyCallbacks(this, fieldId, null);
}
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
    mNotificationLevel++;
    notifyRecurse(sender, arg, arg2);
    ......
}
private void notifyRecurse(T sender, int arg, A arg2) {
    final int callbackCount = mCallbacks.size();
    final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
    ......
    notifyRemainder(sender, arg, arg2, remainderIndex);
    ......
}
private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
    if (remainderIndex < 0) {
        notifyFirst64(sender, arg, arg2);
    } else {
      ......
    }
}
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;
    }
}

onNotifyCallback的实现在   PropertyChangeRegistry 

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

onPropertyChanged 的实现在 ViewDataBinding的内部类 WeakPropertyListener

@Override
public void onPropertyChanged(Observable sender, int propertyId) {
    ViewDataBinding binder = mListener.getBinder();
   ......
    binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
   ......
    boolean result = onFieldChange(mLocalFieldId, object, fieldId);
    if (result) {
        requestRebind();
    }
}

最终走到  onFieldChange 它的实现在 ActivityMainBindingImpl 

@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
    switch (localFieldId) {
        case 0 :
          return onChangeUser((com.example.databinding_demo_20200329.User) object, fieldId);
    }
    return false;
}
private boolean onChangeUser(com.example.databinding_demo_20200329.User User,
 int fieldId) {
//这里我们在User实体类中有几个变量,就会有几个判断,变量多会改为switch 语句
    if (fieldId == BR._all) {
        synchronized(this) {
       //通过对 mDirtyFlags 变量进行或运算,我们会知道哪一个变量需要修改数据
       //最后又会调用之前讲到的用来绑定数据到UI的executeBindings()方法更新数据
                mDirtyFlags |= 0x1L;
        }
        return true;
    }
    else if (fieldId == BR.name) {
        synchronized(this) {
                mDirtyFlags |= 0x2L;
        }
        return true;
    }
    else if (fieldId == BR.password) {
        synchronized(this) {
                mDirtyFlags |= 0x4L;
        }
        return true;
    }
    return false;
}

时序图

最后来两张时序图理清思路

一、解析xml并绑定数据到 UI的时序图


二、更新注册及数据更新流程