jetpack系列文章:Databinding源码分析(一):单双向数据绑定

Databinding:单双向数据绑定

Databinding:动态数据更新

Lifecycle讲解

livedata源码分析

ViewModel源码+最终组合使用

前言: 一般使用MVVM模式,大多基于databinding的,因此专门分析了一下源码,有助于开发。之前项目用到的是MVP框架,其实MVP也能实现V和M的解耦,只是MVVM在解耦的基础上实现了V和M的双向绑定,达到自动更新同步数据,使开发更加便。

话不多说,开始分析,使用网上很多了,这里不说了:

APT技术:

databinding我们用起来很简单,越简单表示框架帮我们完成了越多的东西,毫无疑问自动生成的代码是通过apt技术完成的,这个我已经在Arouter源码分析中讲过了,这里附上链接,不再赘述:APT技术
我们看看都生成了哪些文件就行了:

生成同名activity_db.xml布局

文件路径:build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_db.xml,给根布局和databinding表达式标签添加了tag属性,如下:

    <Button
        android:id="@+id/first_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tag="binding_1"/>

生成activity_db-layout.xml布局

文件路径:build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_db-layout.xml,这个根据xml文件生成的一个layout布局文件,主要是将原布局activity_db.xml的属性补充完整,属性,是否双向绑定等等。方便java文件中遍历提取标签。如下:

<Target id="@+id/edit_text" tag="binding_3" view="EditText">
   <Expressions>
       <Expression attribute="android:afterTextChanged" text="user.afterTextChange">
           <Location endLine="38" endOffset="61" startLine="38" startOffset="12" />
           <TwoWay>false</TwoWay>
           <ValueLocation endLine="38" endOffset="59" startLine="38" startOffset="40" />
       </Expression>
       <Expression attribute="android:text" text="user.editText">
           <Location endLine="40" endOffset="43" startLine="40" startOffset="12" />
           <TwoWay>true</TwoWay>
           <ValueLocation endLine="40" endOffset="41" startLine="40" startOffset="29" />
       </Expression>
   </Expressions>
   <location endLine="40" endOffset="45" startLine="34" startOffset="8" />
</Target>
这里不得不吐槽一句,一个布局文件,就给生成俩副本,这他妈,一个完成大项目,要都用这个框架得生成多少副本。。。不知道是否划算。

生成ActivityDbBinding.java 抽象类

文件路径:
build/generated/data_binding_base_class_source_out/debug/out/com/example/myapplication/databinding/ActivityDbBinding.java,因为xml文件名为activity_db.xml,所以java文件名为(ActivityDb+Binding),是一个抽象类,实体类是下面这个。

生成ActivityDbBindingImpl.java文件

文件路径:
build/generated/ap_generated_sources/debug/out/com/example/myapplication/databinding/ActivityDbBindingImpl.java,这个文件是核心类,主要负责文件装箱工作:就是获取布局文件,然后设置点击事件,发布观察者消息等等核心工作都是通过这个类完成,分析时候详解。

生成DataBinderMapperImpl.java文件

这个文件一共有两个,一个是系统的,一个是自己包名下的,通过系统mapper创建出自己的mapper,再由自己的mapper创建出上面那个XXXbindingImpl核心类。

生成BR.java文件

这个是一个静态常量文件,在Bean类中添加了@Bindable注释后的get方法,返回的字段都会在该文件中添加。

单双向数据绑定

首先来看一段简单的启动代码:

ActivityDbBinding dbBinding = DataBindingUtil.setContentView(this, R.layout.activity_db);
User user = new User("Jiang", "tao", "111");
dbBinding.setUser(user);
user.setFirstName("Sun");

DataBindingUtil的启动

DataBindingUtil是一个静态类,启动的时候有个重要的静态变量

private static DataBinderMapper sMapper = new DataBinderMapperImpl();
   -----------------------DataBinderMapperImpl类-----------------------
  DataBinderMapperImpl() {
  //之前讲过,这个类有两个,一个系统的,一个包名下的,这个就是系统的,然后new一个包名下的
    addMapper(new com.example.myapplication.DataBinderMapperImpl());
  }
  -------------------------addMapper方法--------------------------------
  //这是个递归方法,目的就是为了将DataBinderMapperImpl填充到mMappers这个集合中。
  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);
      }
  }

XXXXBindingImpl核心类的创建

首先通过DataBindingUtil.setContentView开始设置布局:

public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
        int layoutId) {
    return setContentView(activity, layoutId, sDefaultComponent);
   ------------setContentView方法-------------------------------
   //本质上还是setContent方法,所以通过findViewById来获取布局控件也是可以的。
        activity.setContentView(layoutId);
        //这个是用来获取phoneWindow的根布局DecorView,id是android.R.id.content
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
   ------------bindToAddedViews方法----------------------------
   //获取xml布局中layouet标签中的根布局,继续绑定工作
   		final View childView = parent.getChildAt(endChildren - 1);
        return bind(component, childView, layoutId);
   ------------bind方法--------------------------------------
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
   ------------sMapper.getDataBinder方法------------------------
   //这个就是从之前得到的mapper集合中获取
        ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
   ------------mapper.getDataBinder方法------------------------
   //关键的来了,这里进来new了一个ActivityDbBindingImpl核心类。
        switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYDB: {
          if ("layout/activity_db_0".equals(tag)) {
            return new ActivityDbBindingImpl(component, view);
          }
        }
      }
}

XXXXBindingImpl核心类的初始化

类初始化工作主要做了两个工作,获取布局、注册

public ActivityDbBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
//这里有个mapBindings方法
    this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
}

获取布局view

这个方法很长,其实目的只有一个,就是将xml布局中带有databinding表达式的标签填充到bindings数组中。

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;
   }
   //第一次进来肯定都是没绑过的,这个tag就是编译时帮忙添加的tag。
   Object objTag = view.getTag();
   final String tag = (objTag instanceof String) ? (String) objTag : null;
   boolean isBound = false;
   if (isRoot && tag != null && tag.startsWith("layout")) {
   //这里添加根布局,根布局的tag是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)) {
   //这里添加有databinding表达式的标签,都是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;
           }
       }
   }
   //这里主要目的是继续处理include标签中的布局文件,如果没有include,递归处理当前布局
   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);
           }
       }
   }
}

代替findviewById赋值

这个方法结束就获取到了所有带databinding表达式的bindings布局数组集合。然后继续初始化工作。

private ActivityDbBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
//这里将XML中带有ID的标签都进行赋值操作,传给父类ActivityDbBinding,这里就是代替了findviewById方法。
    super(bindingComponent, root, 1
        , (android.widget.EditText) bindings[3]
        , (android.widget.Button) bindings[2]
        , (android.widget.TextView) bindings[4]
        );
//这里将XML中没有ID的,但是有databinding表达式的在当前类中赋值。
    this.editText.setTag(null);
    this.lastName.setTag(null);
    this.mboundView0 = (android.widget.LinearLayout) bindings[0];
    this.mboundView0.setTag(null);
    this.mboundView1 = (android.widget.Button) bindings[1];
    this.mboundView1.setTag(null);
    setRootTag(root);
    // listeners
    mCallback1 = new com.example.myapplication.generated.callback.OnClickListener(this, 1);
    invalidateAll();
}

单向数据绑定

在xml布局中通过@{}其实就是单向数据绑定,@={}就是双向数据绑定,使用起来非常方便,
怎么实现的呢,我们分析一下:

接着上面部分代码继续跳转
invalidateAll()----------->requestRebind();
//这个方法是关键,调用requestRebind方法将会启动run任务
----------->mRebindRunnable.run();
----------->executePendingBindings();
----------->executeBindingsInternal();
----------->executeBindings(); //最终调用这个方法,实现界面刷新。

run任务的触发机制:

1、当ViewDataBinding进行类加载的时候,当sdk>=19的时候,将会创建attachStateChange监听器,看过系统绘制源码的都知道,这个监听在界面绘制的时候,也就是ViewRootImpl中的performTraversales()方法中开始执行。
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);
                binding.mRebindRunnable.run();
                v.removeOnAttachStateChangeListener(this);
            }
            @Override
            public void onViewDetachedFromWindow(View v) {
            }
        };
    }
}
2、当ViewDataBinding类执行构造函数时,当api>16的时候将会创建Choreographer监听器,当一下帧执行的时候,讲执行run方法。
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount){
...............
    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());
    }
}

通过以上两点,使用databinding框架应该会存在界面多次渲染的问题。

继续分析executeBindings()方法,这个也就是实现数据绑定的方法,我们在XML中直接使用user.firstName获取数据,这里其实帮我们实现了get方法,我们修改M层数据后,调用这个方法,其实就是执行了setText方法。实现其实还是这么实现,就是不用自己写了。

protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    java.lang.String userFirstName = null;
    com.example.myapplication.bean.User user = mUser;
    com.example.myapplication.bean.UserAction action = mAction;
    java.lang.String userEditText = null;
    if ((dirtyFlags & 0x3dL) != 0) {
        if ((dirtyFlags & 0x25L) != 0) {
                if (user != null) {
                    userFirstName = user.getFirstName();
                }
        }
        if ((dirtyFlags & 0x31L) != 0) {
                if (user != null) {
                    userEditText = user.getEditText();
                }
        }
    }
    if ((dirtyFlags & 0x31L) != 0) {androidx.databinding.adapters.TextViewBindingAdapter.setText(this.editText, userEditText);
    }
    if ((dirtyFlags & 0x25L) != 0)              TextViewBindingAdapter.setTextWatcher(this.editText, (BeforeTextChanged)null, (OnTextChanged)null, (AfterTextChanged)uAfterTextChanged, TextAttrChanged);
    }
    if ((dirtyFlags & 0x26L) != 0) {androidx.databinding.adapters.TextViewBindingAdapter.setText(this.firstName, userFirstName);
    }
}

双向数据绑定

当我们在XML布局中使用@={}时,系统偷偷做了这么几件事:
1、当我们在xml中使用了AfterTextChanged时,这里添加了一个实现类AfterTextChangedImpl;其中value.afterTextChange(arg0)方法是自定义value类中的自定义afterTextChange方法,arg0是参数。

public static class AfterTextChangedImpl implements androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged{
    private com.example.myapplication.bean.User value;
    public AfterTextChangedImpl setValue(com.example.myapplication.bean.User value) {
        this.value = value;
        return value == null ? null : this;
    }
    @Override
    public void afterTextChanged(android.text.Editable arg0) {
        this.value.afterTextChange(arg0); //
    }
}

2、然后在get方法中创建了这个实例,傻逼系统的命名很长,我这里缩减了一下,没有就创建,有的话就是调用serValue方法,间接调用了我们在自定义类中的自定义方法。

mAfterTextChanged = (((mAfterTextChanged== null) ? (mAfterTextChanged = new AfterTextChangedImpl()) : mAfterTextChanged).setValue(user));

3、最后,通过TextViewBindingAdapte的setTextWatcher方法,其实就是调用了TextWatcher方法中对应的方法,中转了一下,最终调用了步骤1中的afterTextChanged方法,将值抛了出去。

TextViewBindingAdapter.setTextWatcher(this.editText, (TextViewBindingAdapter.BeforeTextChanged)null, (TextViewBindingAdapter.OnTextChanged)null, (TextViewBindingAdapter.AfterTextChanged)mAfterTextChanged , editTextandroidTextAttrChanged);
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值