DataBinding流程初探

1.使用方法

创建一个布局文件

<?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">
    <data class=".DataBindingTest">
        <variable
            name="user"
            type="com.example.databinding.TestUser" />
        <variable
            name="imageUrl"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >
        <Button
            android:id="@+id/btnChanged"
            android:text="changeData"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/tv_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.name,default=aaa}"
            />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:scaleType="centerCrop"
            app:imageUrl="@{imageUrl}"
            />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/tv_2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
        </RelativeLayout>

    </LinearLayout>
</layout>

这里我定义了5个子控件,其中3个有id,2个有binding信息,1个两者都有

这里有几个要点

1.布局最外层需要用layout标签包裹

2.layout标签内分为两层,data标签标示包裹绑定的数据信息,下面的按照正常布局排版,编译时会按照下面的布局生成真正的布局文件

3.data中class标签标示自定义生成的DataBinding名称,不定义会按照默认的命名方式生成

4.variable表示变量数据的信息,name标示引用的名称,下面布局中可以直接拿这个字段使用;type表示类型,基本类型直接写出,自定义的类型需要写出完整路径

5.在布局中引用需要用@{...}格式,这里定义了imageUrl属性;这个会根据定义的@BindingAdapter去查找指定的方法,比如我定义了标记

class CommonDataBinding {
    companion object {
        @BindingAdapter("imageUrl")
        @JvmStatic  //声明静态方法 ,这个方法在java里面需要是静态开放的方法,才能被databing引用
        fun loadImage(image: ImageView, url: String) {
            Glide.with(image.context).load(url)
                    .transition(withCrossFade())
                    .into(image)
        }
    }
}

这里的@BindingAdapter里的value值和布局中声明的保持一致;而且这个方法必须是静态方法!!!

然后在Activity中使用方法

lateinit var dataBinding: DataBindingTest
lateinit var user: TestUser

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding_test)
        dataBinding.btnChanged.setOnClickListener(this)
        dataBinding.imageUrl = "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2534506313,1688529724&fm=26&gp=0.jpg"
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.btnChanged -> {
                val random = (Math.random() * 10).toInt();
                if(!this::user.isInitialized){
                    user = TestUser("Name:$random", random)
                }else{
                    user!!.name= "Name $random"
                }
                  dataBinding.user = user
            }
        }
    }

其中TestUser类定义是

data class TestUser(var name: String, var age: Int)

 

2.流程分析

入口处是在DatabindingUtil.setContentView(),那这里做了啥

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

会发现这里会调用activity的setContentView,但显然我们这种布局是不可能解析成真正的布局文件的,同时我们注意到在LayoutInflater中有一行这个代码

 if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;

也就是说,解析子view的时候会根据有没有 '.' 这个符号分别处理,而这个分别处理则是

  protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

 public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;
        try {
            .....
            if (constructor == null) {
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                ......
            } else {
                ......
            }
              ......
            final View view = constructor.newInstance(args);
            ......
            return view;   
    }

就是说,没有点的会自动组装android.view前缀,然后二者都会根据类名反射得到实例并最终强转成View;这也就是为什么我们findViewById的最后还是需要做一次转换,因为解析的时候都会转成View;

回到上面的,那个布局显然不符合解析的要求,但也不必担心,编译过程中会帮我们处理掉这个问题

上面的布局在编译时候会把变量的部分移除,生成一个新的布局文件

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
         android:tag="layout/activity_databinding_test_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
        <Button
            android:id="@+id/btnChanged"
            android:text="changeData"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/tv_1"
            andoird:text="aaa"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:tag="binding_1"   
            />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:scaleType="centerCrop"
            android:tag="binding_2"   
            />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:id="@+id/tv_2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
        </RelativeLayout>

    </LinearLayout>
         

这个在build->intermediates->incremental->mergeDebugResources->stripped.dir->layout 目录下

可以看出这个布局给根容器添加了一个tag,tag名为原布局文件名加_0后缀,而后面的设置了@属性的控件都会打上binding标签,并以_1开始,依次往后递增;那些设置了id属性会保留

因为上面我指定了class属性,那么就会生成一个和属性名一致的ViewDataBinding文件,这个后面会提到;

接着进入bindToAddeddViews方法

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

这里传入的parent是上面的android.R.id.content那个容器,也就是一个FrameLayout,而这里的childCount则是我们的布局文件,最外层一般是一个根布局,所以这里会满足 childrenAdded == 1,并把这个取出来传到bind方法中

private static DataBinderMapper sMapper = new DataBinderMapperImpl()

static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }

----------------------------------- 分割线  ------------------------------------------

public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.example.myviewdemolearn.DataBinderMapperImpl());
  }
}

bind方法最终会通过sMapper返回一个ViewDataBinding的子类,而sMappere里添加一个编译自动生成的DataBinderMapper实现类

private List<DataBinderMapper> mMappers = new CopyOnWriteArrayList<>();  
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);
            }
        }
    }
------------------------------------------------------------------------------------
  @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里会用一个mMappers的list保存添加进来的实现类,然后遍历这个集合,通过getDataBinder获取指定的ViewDataBinding,而这个方法最终会调用上面生成的实现类的getDataBinder的方法

实现类中的方法为

 @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_ACTIVITYDATABINDINGTEST: {
          if ("layout/activity_databinding_test_0".equals(tag)) {
            return new DataBindingTestImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_databinding_test is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

注意这里的view就是上面从content中取出来的根容器,其实就是我们布局文件中抛开data数据之后,布局内容的最外层;

并且在生成新的布局文件后给这个容器加一个 "布局文件名称_0"的tag,而在这里则取到了这个tag,如果满足就创建并返回一个DataBindingTestImp的对象,再回到上面,我在data中加了这么一句

 <data class=".DataBindingTest"> ... </data>

也就是说编译会生成两个类,一个DataBindingTest类和一个DatabindTestImp类,当然这个命名是随意的,不一定是要这种格式;然后先找一下DataBindingTest类

具体路径在build->generated->data_binding_base_class_source_out_debug_dataBindingGenBaseClassedDebug目录下

public abstract class DataBindingTest extends ViewDataBinding {
  @NonNull
  public final Button btnChanged;

  @NonNull
  public final TextView tv1;

  @NonNull
  public final TextView tv2;

  @Bindable
  protected TestUser mUser;

  @Bindable
  protected String mImageUrl;

  protected DataBindingTest(Object _bindingComponent, View _root, int _localFieldCount,
      Button btnChanged, TextView tv1, TextView tv2) {
    super(_bindingComponent, _root, _localFieldCount);
    this.btnChanged = btnChanged;
    this.tv1 = tv1;
    this.tv2 = tv2;
  }

  public abstract void setUser(@Nullable TestUser user);

  @Nullable
  public TestUser getUser() {
    return mUser;
  }

  public abstract void setImageUrl(@Nullable String imageUrl);

  @Nullable
  public String getImageUrl() {
    return mImageUrl;
  }

  @NonNull
  public static DataBindingTest inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root,
      boolean attachToRoot) {
    return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
  }

  @NonNull
  @Deprecated
  public static DataBindingTest inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root,
      boolean attachToRoot, @Nullable Object component) {
    return ViewDataBinding.<DataBindingTest>inflateInternal(inflater, R.layout.activity_databinding_test, root, attachToRoot, component);
  }

  @NonNull
  public static DataBindingTest inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, DataBindingUtil.getDefaultComponent());
  }
 
  @NonNull
  @Deprecated
  public static DataBindingTest inflate(@NonNull LayoutInflater inflater,
      @Nullable Object component) {
    return ViewDataBinding.<DataBindingTest>inflateInternal(inflater, R.layout.activity_databinding_test, null, false, component);
  }

  public static DataBindingTest bind(@NonNull View view) {
    return bind(view, DataBindingUtil.getDefaultComponent());
  }
 
  @Deprecated
  public static DataBindingTest bind(@NonNull View view, @Nullable Object component) {
    return (DataBindingTest)bind(component, view, R.layout.activity_databinding_test);
  }
}

可以看出,在上面布局中定义的三个有id的控件,以及定义的两个变量都会在这里进行声明,并提供了属性的set和get方法;注意构造方法里会从子类中把有id标示都传进来,整下的都是绑定和填充的方法,最终都会调用到父类的ViewDataBinding中方法,这里暂不做解释

因为这里把id属性的控件都生成了成员变量,所以databinding可以直接拿来使用

然后是具体实现类,因为我的是kotlin开发的,java的可能路径有所差异;我的路径是

build->generated->source->kapt->debug

public class DataBindingTestImpl extends DataBindingTest  {

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = new android.util.SparseIntArray();
        sViewsWithIds.put(R.id.btnChanged, 3);
        sViewsWithIds.put(R.id.tv_2, 4);
    }
    // views
    @NonNull
    private final android.widget.LinearLayout mboundView0;
    @NonNull
    private final android.widget.ImageView mboundView2;

    public DataBindingTestImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
    }
    private DataBindingTestImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            , (android.widget.Button) bindings[3]
            , (android.widget.TextView) bindings[1]
            , (android.widget.TextView) bindings[4]
            );
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView2 = (android.widget.ImageView) bindings[2];
        this.mboundView2.setTag(null);
        this.tv1.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x4L;
        }
        requestRebind();
    }

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.user == variableId) {
            setUser((com.example.databinding.TestUser) variable);
        }
        else if (BR.imageUrl == variableId) {
            setImageUrl((java.lang.String) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }

    public void setUser(@Nullable com.example.databinding.TestUser User) {
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }
    public void setImageUrl(@Nullable java.lang.String ImageUrl) {
        this.mImageUrl = ImageUrl;
        synchronized(this) {
            mDirtyFlags |= 0x2L;
        }
        notifyPropertyChanged(BR.imageUrl);
        super.requestRebind();
    }

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

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userName = null;
        com.example.databinding.TestUser user = mUser;
        java.lang.String imageUrl = mImageUrl;

        if ((dirtyFlags & 0x5L) != 0) {



                if (user != null) {
                    // read user.name
                    userName = user.getName();
                }
        }
        if ((dirtyFlags & 0x6L) != 0) {
        }
        // batch finished
        if ((dirtyFlags & 0x6L) != 0) {
            // api target 1

            com.example.databinding.CommonDataBinding.loadImage(this.mboundView2, imageUrl);
        }
        if ((dirtyFlags & 0x5L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
        }
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
        flag 0 (0x1L): user
        flag 1 (0x2L): imageUrl
        flag 2 (0x3L): null
    flag mapping end*/
    //end
}

这里有个BR属性,是根据binding变量生成的索引值,也就是可以指定variableId去更新数据;这个后面会说

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

  public static final int imageUrl = 1;

  public static final int user = 2;
}

然后看下上面的类,首先注意到静态代码块,里面有一个sIncludes是空的,还有一个sViewsWithIds,这里添加了两个id,也就是上面抛去有tag的View后,拥有id的view数,而且索引位置正好排在tag的views之后

这里相当于把所有的有效的view进行了排序;比如上面有tag的View有3个,会按照后缀的大小值进行排序,剩下的没tag的会按照遍历顺序依次排在tag的后面;为什么说有效呢,没有id也没有tag的view会被过滤掉

具体可以在这上面构造方法的mapBindings中看到一些痕迹,为什么里面有个5,因为有效的view总数只有5个

 protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
            int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
        Object[] bindings = new Object[numBindings];
        mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
        return bindings;
    }

public static final String BINDING_TAG_PREFIX = "binding_";
private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();

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;
        }
        Object objTag = view.getTag();
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
        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);
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                indexInIncludes = includes == null ? -1 : index;
                isBound = true;
            } else {
                indexInIncludes = -1;
            }
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
            if (bindings[tagIndex] == null) {
                bindings[tagIndex] = view;
            }
            isBound = true;
            indexInIncludes = includes == null ? -1 : tagIndex;
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
            final int id = view.getId();
            if (id > 0) {
                int index;
                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                        bindings[index] == null) {
                    bindings[index] = view;
                }
            }
        }

        if (view instanceof  ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) view;
            final int count = viewGroup.getChildCount();
            int minInclude = 0;
            for (int i = 0; i < count; i++) {
                final 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('/') > 0) {
                        // This *could* be an include. Test against the expected includes.
                        int includeIndex = findIncludeIndex(childTag, minInclude,
                                includes, indexInIncludes);
                        if (includeIndex >= 0) {
                            isInclude = true;
                            minInclude = includeIndex + 1;
                            final int index = includes.indexes[indexInIncludes][includeIndex];
                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                            int lastMatchingIndex = findLastMatching(viewGroup, i);
                            if (lastMatchingIndex == i) {
                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                        layoutId);
                            } else {
                                final int includeCount =  lastMatchingIndex - i + 1;
                                final 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);
                }
            }
        }
    }

简单说一下步骤

1.创建一个大小为有效view数目大小的数组bings[],用来存后面查找出的view

2.获取当前view的Tag属性,如果有的话会进行一下判断

2.1 如果tag是以 "layout" 开头的,取最后一个"_"之后的字符串,如果是十进制数字,比如是0,那么就把当前view放在上面创建的数组的第0位

2.2 如果tag是以 "binding_" 开头的 ,取这个字符串后的数字,和上面一样,把当前view放在上面数组的数字位

2.3 如果都不满足,那就获取一下id,如果id大于0,那么根据id拿到上面sViewsWithIds的索引位置,存放view

3.如果当前view是ViewGroup,则去遍历判断子View,递归重复流程

因为我们上面传入的sIncludes是空的,所以这里findIncludeIndex一直返回的是-1,isInclude也就一直是false

然后第二个构造方法会把有id的属性的view设置给父类,也就是DataBindingTest中的那三个view,其他的会以 mBoundViewN的形式生成局部变量,这里的N就算在bindings中的索引位置

做了这么多操作只是为了放一个集合中,方便给父类中声明的view属性赋值。。。

 private DataBindingTestImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 1
            , (android.widget.Button) bindings[3]
            , (android.widget.TextView) bindings[1]
            , (android.widget.TextView) bindings[4]
            );
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView2 = (android.widget.ImageView) bindings[2];
        this.mboundView2.setTag(null);
        this.tv1.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

这里会把有tag属性的view的tag全部清空初始化,并调用invalidateAll方法去刷新数据,这个后面会讲

初始化方面就分析完了,接下来看下设置值

上面我调用了setUser方法更新数据

private  long mDirtyFlags = 0xffffffffffffffffL;
 public void setUser(@Nullable com.example.databinding.TestUser User) {
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }

会先把这个新的属性值的赋值给刷新,然后更新mDirtyFlags的标记;

notifyPropertyChanged方法是一个刷新的回调通知

 private transient PropertyChangeRegistry mCallbacks;

    public BaseObservable() {
    }

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

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

可以看出我们可以通过addOnPropertyChangedCallBack方法去监听属性的变化,我们没注册当然也不会回调

private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16 
 
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);
            }
        }
    }

requestRebind是最终的通知更新的方法,这里的mContainingBinding我们并没有赋值,而下面的owner也是空的,可以通过setLifecycleOwner方法设置周期的监听,但是我们没有注册也就没有这个功能;现在的手机基本上SDK>16,那么就会最终走到

mChoreographer.postFrameCallback(mFrameCallback),这是个什么玩意

在ViewDataBinding的构造方法中

 protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
        mBindingComponent = bindingComponent;
        mLocalFieldObservers = new WeakListener[localFieldCount];
        this.mRoot = root;
        if (Looper.myLooper() == null) {
            throw new IllegalStateException("DataBinding must be created in view's UI Thread");
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer = Choreographer.getInstance();
            mFrameCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    mRebindRunnable.run();
                }
            };
        } else {
            mFrameCallback = null;
            mUIThreadHandler = new Handler(Looper.myLooper());
        }
    }

这里会初始化一个mChoreographer和一个FrameCallback,FrameCallBack什么时候回调呢,这个我没有研究过,但从注释上

   public interface FrameCallback {
        /**
         * Called when a new display frame is being rendered.
         * <p>
         * This method provides the time in nanoseconds when the frame started being rendered.
         ......
         ......         
         * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
         * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
         * to convert it to the {@link SystemClock#uptimeMillis()} time base.
         */
        public void doFrame(long frameTimeNanos);
}

可以看出这个是一个渲染的回调,是最新的渲染帧发生时的回调

也就是说上面的在最新一帧渲染时候会进入mRebindRunnable.run()方法中

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

private static void processReferenceQueue() {
        Reference<? extends ViewDataBinding> ref;
        while ((ref = sReferenceQueue.poll()) != null) {
            if (ref instanceof WeakListener) {
                WeakListener listener = (WeakListener) ref;
                listener.unregister();
            }
        }
    }

 public boolean unregister() {
            boolean unregistered = false;
            if (mTarget != null) {
                mObservable.removeListener(mTarget);
                unregistered = true;
            }
            mTarget = null;
            return unregistered;
        }

这里最后会走到executePendingBinds()方法中,对应于上面的DataBindingTestImpl中;

而执行这个方法前都会先调用一下processRefrencQueue方法,这个主要是针对变更局部属性使用的引用队列,如果被回收了那就把变量的监听注册移除,并且里面的mTarget也会清空

 protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userName = null;
        com.example.databinding.TestUser user = mUser;
        java.lang.String imageUrl = mImageUrl;
        if ((dirtyFlags & 0x5L) != 0) {
                if (user != null) {
                    userName = user.getName();
                }
        }
        if ((dirtyFlags & 0x6L) != 0) {
        }
        if ((dirtyFlags & 0x6L) != 0) {

            com.example.databinding.CommonDataBinding.loadImage(this.mboundView2, imageUrl);
        }
        if ((dirtyFlags & 0x5L) != 0) {

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
        }
    }

这里会进行标记判断,并把标记置成0

这里得讲一下这个运算方式,这里运算是按十六进制的或与运算设计的,同时每个属性都会定义一个十六进制的数字标记;

为了所有属性的互不干扰,所以设计的时候,按照顺序每下个属性的值会是上一个的2倍,因为转成二进制后这些值就变成

001,010,100 ... 这种,始终保持只有一位是1,其他位都是0,这样再做与运算时候,可以方便判断

注意到我们总共定义了两个变量,上面生成的标记是 0x1L和0x2L ,对应成二进制的  01和10

而下面更新UI的判定值是 0x5L和0x6L ,对应成二进制的101和110 ,那么这个值是怎么得出的

其实上面还有一个方法

 public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x4L;
        }
        requestRebind();
    }

是刷新所有的标记,而这个值是上面生成的最大标记的两倍0x4L也就是二进制的100

并且 0x5L = 0x4L&0X1L  , 0x6L = 0x4L&0x2L;

也就是说会进行双重判断,当前的标记和全部的标记都满足就会去执行对应页面属性的重新设置

而上面也说了,在构造方法执行后会去调用这个invalidateAll方法,也就是说这里一进来就会把标记设置成0x4L,然后去执行一遍刷新,因为0x4L对所有方法判定都是满足的,所以下面所有的方法都会走一遍,因为TextViewBindingAdapter内部有判空处理,我们不必担心空指针问题

因为我上面只定义了两个,分别是0x1L和0x2L;剩下的会继续按照递增排序0x4L,0x8L,0x10L,0x20L ,后面的标记也会按照规则重新生成

上面的流程是通过setUser方法进行的分析,通过设置对象来更新数据;但如果我不想通过设置对象的方法,只通过对象的设置方法就去更新UI数据该怎么做呢

其实只需要修改对象属性就可以了,比如上面的对象,我新定义一个属性,可以通过设置属性更新UI

data class TestUser(var name: String, var age: Int) : BaseObservable() {
    @Bindable
    var job = ""
        set(value) {
            field = value;
            notifyPropertyChanged(BR.job)
        }
}

需要继承BaseObservable,并把属性添加@Bindable注解,然后在set的方中调用notifyProperChanged方法,并指定编译后生成的BR.job索引即可;然后在布局中选择一个未标记的加上这个属性

编译后注意,原先的setUser方法中会多一行代码

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

 而且构造方法中的参数也会从0变成1

 private DataBindingTestImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 1
            , (android.widget.Button) bindings[3]
            , (android.widget.TextView) bindings[1]
            , (android.widget.TextView) bindings[4]
            );
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView2 = (android.widget.ImageView) bindings[2];
        this.mboundView2.setTag(null);
        this.tv1.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

而这个则是生成weakListener集合的代码

private WeakListener[] mLocalFieldObservers;
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
        ...
        mLocalFieldObservers = new WeakListener[localFieldCount];
}

回到updateRegistration方法

 protected boolean updateRegistration(int localFieldId, Observable observable) {
        return updateRegistration(localFieldId, observable, 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();
        }
    };

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

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

这个方法是更新监听器,Observable是TestUser对象,localFieldId是0,如果之前未注册,那么就创建一个WeakPropertyListener并获取其中的Listener的放到集合缓存中,并调用该对象的setTarget方法把当前的TestUser传入,注册相应的数据变更监听;

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

这里会在内部创建一个WeakListener,并把ViewDataBinding和把自身作为ObservableReference传给这个listener

而addListener和removeListener都是针对本身的操作,实际上也就是针对于本身继承的OnPropertyChangedCallback接口,这两个方法的target都是从WeakListener中回传过来的

在onPropertyChange的回调中会拿到当前的ViewDataBinding对象,并调用handleFieldChange方法,这个方法最终会回到之前的requestRebind方法,去刷新数据

而WeakListener结构是

private static final ReferenceQueue<ViewDataBinding> sReferenceQueue = new ReferenceQueue<>(); 
private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
        private final ObservableReference<T> mObservable;
        protected final int mLocalFieldId;
        private T mTarget;

        public WeakListener(ViewDataBinding binder, int localFieldId,
                ObservableReference<T> observable) {
            super(binder, sReferenceQueue);
            mLocalFieldId = localFieldId;
            mObservable = observable;
        }

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

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

        public boolean unregister() {
            boolean unregistered = false;
            if (mTarget != null) {
                mObservable.removeListener(mTarget);
                unregistered = true;
            }
            mTarget = null;
            return unregistered;
        }

        public T getTarget() {
            return mTarget;
        }

        protected ViewDataBinding getBinder() {
            ViewDataBinding binder = get();
            if (binder == null) {
                unregister(); // The binder is dead
            }
            return binder;
        }
    }

这里的setTaget方法最终会到上面的addListener方法,给上面的TestUser自己设置监听,并在当前类中onPropertyChange方法回调变更结果

这是个弱引用,会把ViewDataBinding作为缓存的内容,当调用期间发现已经被回收了,那么就会去判断内部的存储的target属性,这个对应于TestUser对象,如果不为空,那么移除TestUser设置的监听器,也就是WeakPropertyListener 

上面也提到sReferenceQueue是个静态引用队列,每次更新属性前,都会检测这个队列中的引用属性,对于弱引用,能取到的都是已经被回收的,循环取出已经被回收掉的,依次解除监听注册,上面也提到,里面的属性target,也就是观测的对象也会清空

 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方法,这里的mLocalField就是之前方法updateRegistration传入的第一个参数的值,上面传入的是0

另外因为我新增了一个可动态监测的属性,上面的判断标记都往后多推算了一位

  @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeUser((com.example.databinding.TestUser) object, fieldId);
        }
        return false;
    }
    private boolean onChangeUser(com.example.databinding.TestUser User, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x1L;
            }
            return true;
        }
        else if (fieldId == BR.job) {
            synchronized(this) {
                    mDirtyFlags |= 0x4L;
            }
            return true;
        }
        return false;
    }

 @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x8L;
        }
        requestRebind();
    }

新增的属性占据了0x4L的标记,而之前的全部刷新的标记变更为0x8L,原先User属性还是保留为0x1L;而原先的判断逻辑也发生了变更

if ((dirtyFlags & 0xaL) != 0) {
            com.example.databinding.CommonDataBinding.loadImage(this.mboundView2, imageUrl);
        }
        if ((dirtyFlags & 0x9L) != 0) {
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
        }
        if ((dirtyFlags & 0xdL) != 0) {
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userJob);
        }

那么这里就由之前的两重判断变成三层,因为user多了一个可变属性

User标记是  0x1L , name未做单独定义所以没有生成标记和User保持一致

User中属性Job的标记是 0x4L  

全部的标记是  0x8L

那么User的原属性的标记判断就变更为  0x1L & 0x8L = 0x9L

User额外定义的Job属性标记判断为  0x4L & 0x8L & 0x1L = 0xdL

也就说setUser方法会刷新页面上User的所有属性,而setJob方法只会刷新Job相关的属性

可以认为是全部刷新和单个刷新

在具体更新数据前,如果这两个同时发生了修改,会怎么样?

那么当前的标记就会变成 0x1L | 0x4L ,然后上面的两个判断都是满足的,也就是都会执行;而在更新的时候,会取到当前的标记,保存为局部变量,同时把当前的标记改成0,并且整个过程是同步锁中之行的,也就是说虽然调用了两次更新方法,但因为第二次已经变更为0了,后续的判断都不满足,也就不会执行,只有第一次的会执行

这样一个流程便走完毕了

 

单属性变更层级比较乱,总体来说归属关系可以定为

1. ViewDataBinding持有WeakListener的集合,新建的单属性监听都会放到这个集合里

2.WeakListener是通过CreateWeakListener的create方法生成, WeakListener是个弱引用

3.WeakPropertyListener构造方法中会创建一个WeakListener对象,并会传入一个ViewDataBinding对象,并提供getListener接口返回这个对象,就是说WeakPropertyListener持有WeakListener,同时WeakListener持有ViewDataBinding的弱引用

4.上一步方法返回的WeakListener对象会放到步骤1的集合中,那么ViewDatabinding和WeakPropertyListener都持有WeakListener

5.然后会调用weakListener的setTarget把需要观测的对象传入;就是说WeakListener持有被观测的对象,然后观测的接口是Observable.OnPropertyChangedCallback,而WeakPropertyListener继承了这个接口,那么实际观测者就是WeakPropertyListener,实际回调监听也在这个类 (被观测的对象需要实现BaseObservable接口)

6.当发生更新时,会先回调WeakPropertyListener中的更新方法,这个方法中会回传两个参数,一个是被观测的对象,一个是被观测的属性id ; 然后这个方法内会通过getListener去WeakListener中查找存储的ViewDataBinding和观测的对象,如果对象匹配同时ViewDataBinding不为空,则会去调用ViewDataBinding相关的通知方法

7.每次更新都会检测WeakListener队列中的缓存情况,这里是通过sReferenceQueue缓存队列判断的,这个是静态的,就是所有的ViewDataBinding被回收都会集中放到这里,然后已经被回收的会被全部解除注册移除,并且里面的model也会被置空

单属性变更大概的关系是这样,可能会有些出入

 

 

总结

1.Databinding编译后会自动识别并生成能被代码识别的新的布局文件,并会生成和布局文件内数据匹配的ViewDataBinding具体实现类

2.对于对象属性变更,可以分为整体变更和单属性变更;调用设置对象的方法会刷新和对象所有属性相关的页面属性,而设置单属性变更只会刷新和该对象指定属性相关的页面属性

3.整体刷新无需额外定义,而单属性刷新需要指定对象继承BaseObservable接口,并将属性用@Bindable标记,然后在方法中调用notifyPropertyChanged方法

4.ViewDataBinding构造方法执行完毕会执行一遍全部刷新,虽然没啥卵用,因为这时候值基本上都是空的;操作后会把当前的标记置为0

5.调用刷新方法后,数据的刷新是在页面的最新渲染帧发生的时候

6.每个可变属性,包括单属性变更都会生成唯一的标记,按照16进制2倍递增,保持转换为二进制后始终只有1位是1,其他为都是0,每次递增2倍,1的位置就会往前移动1位;额外会有一个全部刷新的标记,具体值为当前属性标记最大值的2倍,也满足上面的条件;同时有一个保存当前标记状态的变量,用于修改和判断

7. 每次更新都会那当前的标记和要变更的属性进行或运算,比如上面的 mDirtyFlags |= 0x1L ,因为每个标记的1的位置都是不同的,而其他位都是0,并发修改的时候也会互不干扰,相当于每次或运算一次,就会把当前状态指定的标记位置值改成1,当然,如果并发全部修改,同时刷新方法还没进入,那么就变成一个所有位置都是1的标记了

8.每次进行一次操作后,都会把当前的标记重新置为0,同时这个操作会在锁中执行,即便并发修改发生并且刷新方法滞后,那么最终也只有第一次会实际更改,后续的因为都是0不会进入任何判断逻辑

9.属性变更判断是拿当前的标记和属性指定的标记判断值进行与运算,比如上面的dirtyFlags & 0x5L,而这个0x5L这个值是按照所有和当前属性相关的所有标记进行的与运算得出的,因为要同时满足;比如这个就是当期属性标记0x1L和全部更新的标记0x4L进行计算0x1L & 0x4L 得出的;注意如果定义了单属性变更, 那么这个单属性的计算会额外算上这个属性所在的对象,毕竟调用对象变更方法,这个属性也是要刷新的

10.每次更新前都会调用processReferenceQueue方法,从弱引用队列sReferenceQueue中检测已经被回收的ViewDataBing,这个队列是静态的,可以统一管理所有的回收的ViewDataBinding,如果发现了被回收,那么就找到这个弱引用对应的WeakListener, 对当前的被观测的Model解除监听,并清空次Model, 减少了内存泄露的风险

11.流程按照变更属性细分

11.1 整体变更  : 直接调用requestRebind方法,在最新渲染帧中回调

11.2 局部变更  : 观测对象本身,用一个WeakListener存储当前对象和视图工具ViewDataBinding弱引用,观察者是WeakPropertyListener;对象属性变更时,在观察者中的变更方法中onPropertyChanged,会有视图工具是否被回收以及对象一致性判断,然后在onFieldChange判断有没有和变更id匹配的属性,如果有就会调用requestRebind刷新数据;

第一次写这么长,写完我都不知道写了啥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值