软件编程
位置:首页>> 软件编程>> Android编程>> Android DataBinding布局的加载深入探究

Android DataBinding布局的加载深入探究

作者:昉钰  发布时间:2023-02-26 08:45:52 

标签:Android,DataBinding,布局加载

上一章说明了DataBinding生存的类之间关系,现在这里来看看布局是如何加载的以及view是如何映射的。

一、布局加载

这里把之前的代码重新贴下方便说明,代码如下:

class MainActivity : AppCompatActivity() {
   private val viewModel: SimpleViewModel by viewModels()
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
       binding.lifecycleOwner = this
       binding.viewModel = viewModel
   }
}

其中布局加载就这一行:DataBindingUtil.setContentView(this, R.layout.activity_main),所以进入到DataBindingUtil中,代码如下:

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

activity.setContentView(layoutId),这和我们不用DataBinding写的一样啊,所以Databinding在这里就帮我们加载了布局。

接下来,看DataBinding是如何实现view映射的。

二、view映射

然后拿到decorView 并找到contentView ,最后调用bindToAddedViews,bindToAddedViews的函数如下:

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

在我们的场景里面,endChildren 应该为1,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);
}

调用了sMapper的getDataBinder函数,这里的sMapper类型为DataBinderMapperImpl,还记得上一章说过有两个DataBinderMapperImpl吗?为了便于说明,这里再把之前的类图贴下:

Android DataBinding布局的加载深入探究

额,这就尴尬了,所以这里的Mapper到底是哪个呢?之前说过左边的是android提供的,右边的是我们自己包下面的;其实这里的sMapper属于左边这个行列,也就是androidx这个包下面的。那他们有什么区别呢?你可以认为左边的提供了一个简单的代理功能,其实它就是简单对右边的Mapper类进行包装而已。

这里需要说明下sMapper对象的初始化过程,我们知道类加载会触发类变量(静态变量)的初始化,这个时候sMapper就会被初始化,这个时候会调用DataBinderMapperImpl(左边那个mapper)的构建函数,代码如下:

package androidx.databinding;//位于androidx包下面
public class DataBinderMapperImpl extends MergedDataBinderMapper {
 DataBinderMapperImpl() {
   //这个DataBinderMapperImpl就是我们自己包下面的了
   addMapper(new com.zfang.databindingstudy.DataBinderMapperImpl());
 }
}

正如前面所说,androidx下面的mapper类包装了项目中的mapper类,addMapper代码如下:

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

这里会把项目中的mapper(即DataBinderMapperImpl)加入到mMappers这个CopyOnWriteArrayList中,后面会用到。

此时可以继续看看getDataBinder的实现了(其实现位于MergedDataBinderMapper中),代码如下:

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

这里就是从mMappers中把mapper拿出来,再根据传递进来的参数view、layoutId找到相应的ViewDataBinding对象;这里的mMappers就是刚刚提到的那个CopyOnWriteArrayList,所以会调用到我们的DataBinderMapperImpl,其中的getDataBinder实现如下:

private static final int LAYOUT_ACTIVITYMAIN = 1;
 private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
 static {
   INTERNAL_LAYOUT_ID_LOOKUP.put(com.zfang.databindingstudy.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
 }
 @Override
 public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
   int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
   if(localizedLayoutId > 0) {
     final Object tag = view.getTag();
     if(tag == null) {
       throw new RuntimeException("view must have a tag");
     }
     switch(localizedLayoutId) {
       case  LAYOUT_ACTIVITYMAIN: {
         if ("layout/activity_main_0".equals(tag)) {
           return new ActivityMainBindingImpl(component, view);
         }
         throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
       }
     }
   }
   return null;
 }

这里有个SparseIntArray ,它定义了我们的布局与一个整数的映射关系,上面的代码首先拿到view的tag,这里返回的tag为layout/activity_main_0(回忆下:上一章说过DataBinding会生存两个xml,其中一个加了tag,那里说的tag正是和这里对应上了,其作用就体现在这里),所以会返回ActivityMainBindingImpl,这正是需要的ViewDataBinding类。

继续进入ActivityMainBindingImpl的构建函数中,代码如下:

public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
       this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
   }
   private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
       super(bindingComponent, root, 2
           , (android.widget.TextView) bindings[1]
           , (android.widget.TextView) bindings[2]
           );
       this.first.setTag(null);
       this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
       this.mboundView0.setTag(null);
       this.second.setTag(null);
       setRootTag(root);
       // listeners
       invalidateAll();
   }

先调用了第一个构造函数,然后进入第二个。第二个构造函数又调用了父类的相应构造函数,代码如下:

protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
     TextView first, TextView second) {
   super(_bindingComponent, _root, _localFieldCount);
   this.first = first;
   this.second = second;
 }

没错,上面的bindings数组中的bindings[1]、bindings[2]正是对应到了我们这个场景中的first和second两个view。现在的问题是bindings数组中的值是怎么来的呢?

我们继续看看ActivityMainBindingImpl类中第一个构建数据中调用的函数mapBindings,看来在mapBindings中会填充bindings数组,mapBindings代码如下:

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

这里 根据numBindings新建了一个数组,继续:

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;
       //第一次进来isRoot为true,tag为根据布局所以是以layout开头,因此这进入第一个if
       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;//放入bindings数组,这里的view代表根布局
               }
               //处理包含布局中有include标签的情况
               indexInIncludes = includes == null ? -1 : index;
               isBound = true;
           } else {
               indexInIncludes = -1;
           }
       } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
           //如何不是根布局,对应到我们的场景则会走到这里,我们的两个TextView的
           //tag刚是以binding开头的,其实只要写了绑定表达式就会到这里。
           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;
               }
           }
       }
       //如果是ViewGroup则递归处理找到相应的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;
               //处理include标签
               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;
                           }
                       }
                   }
               }
               //非include
               if (!isInclude) {
                   mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
               }
           }
       }
   }

这里就是实现view数组映射的关键,主要功能就是填充了bindings数组,思路就是找到包含绑定表达式的控件,然后把它们记录下来放到一个数组中,方便在相应控件的数据变化的时候能够通知到控件, 这里其实就是找到如下布局中的两个TextView然后加入到bindings中。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:tag="layout/activity_main_0"
   tools:context=".MainActivity">
   <TextView
       android:id="@+id/first"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginVertical="12dp"
       android:tag="binding_1"
       android:textColor="#333333"
       android:textSize="18sp"
       app:layout_constraintBottom_toTopOf="@id/second"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintVertical_chainStyle="packed" />
   <TextView
       android:id="@+id/second"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:tag="binding_2"
       android:textColor="#999"
       android:textSize="14sp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/first" />
</androidx.constraintlayout.widget.ConstraintLayout>

上面那段代码的逻辑就是找到ConstraintLayout(android:tag="layout/activity_main_0") 以及两个TextView(tag分别为binding_1和binding_2),总共三个控件。ConstraintLayout就是根布局,两个TextView就是我们需要操作的View。

来源:https://blog.csdn.net/www586089/article/details/127779260

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com