Android-Jetpack筆記-DataBinding

DataBinding即數據綁定,能夠實現數據和UI的雙向綁定。數據改變時,驅動UI刷新;操做UI時,也能夠同步給數據。一般在開發界面時,總有findViewById的重複工做,DataBinding能夠免去這些操做。同時,DataBinding還能夠直接在xml中綁定數據,免去相似setText的操做,讓數據來驅動UI刷新。java

Jetpack筆記代碼android

使用

app/build.gradle中開啓:git

android {
    dataBinding {
        enabled = true
    }
}
複製代碼

在佈局文件中,將光標定位在根佈局,alt+enter,而後convert to data binding layoutgithub

佈局外層會多出一層layout標籤:markdown

<layout>
    <!--數據描述-->
    <data>
    </data>
    <!--佈局描述-->
    <ScrollView>
    </ScrollView>
</layout>
複製代碼

在數據描述內,能夠導入類和聲明變量:app

<data>
    <import type="com.holiday.jetpackstudy.model.User" />
    <variable name="user" type="User" />
</data>
複製代碼

在佈局描述內,定義一個TextView並綁定數據:ide

<TextView android:id="@+id/tv_name" android:text="@{user.name}" />
複製代碼

在activity中,經過DataBindingUtil獲得binding對象:oop

void onCreate(Bundle savedInstanceState) {
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
複製代碼

其中xml文件名決定了生成的binding的類名,xml文件名+Binding,如activity_main.xml生成ActivityMainBinding.java,而後就能夠經過binding對象直接訪問到view:佈局

mBinding.tvName.setTextColor(xxx);
複製代碼

經過binding對象設置數據,驅動UI刷新:post

mBinding.setUser(user);
複製代碼

原理

DataBindingUtil.setContentView做爲入口跟進去,

//DataBindingUtil.java
public static <T extends ViewDataBinding> T setContentView(Activity activity,int layoutId,DataBindingComponent bindingComponent) {
    //這裏設置了佈局文件
    activity.setContentView(layoutId);
    return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
 //省略調用鏈:bindToAddedViews -> bind  static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); } 複製代碼

來到MergedDataBinderMapper.java

//MergedDataBinderMapper.java
@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;
        }
    }
    return null;
}
 複製代碼

那麼mMappers的值是在何時設置的呢?發現只有一處進行add,

//MergedDataBinderMapper.java
public void addMapper(DataBinderMapper mapper) {
    Class<? extends DataBinderMapper> mapperClass = mapper.getClass();
    //若是不在mExistingMappers中,才添加進mMappers
    if (mExistingMappers.add(mapperClass)) {
        mMappers.add(mapper);
        final List<DataBinderMapper> dependencies = mapper.collectDependencies();
        for(DataBinderMapper dependency : dependencies) {
            addMapper(dependency);
        }
    }
}
 複製代碼

再來看看誰調了addMapper,發現有一個生成類DataBinderMapperImpl(data binding經過apt建立了一些類),

//DataBinderMapperImpl.java
package androidx.databinding;//注意包名
public class DataBinderMapperImpl extends MergedDataBinderMapper {
    DataBinderMapperImpl() {
        //構造的時候把另外一個包下的生成類DataBinderMapperImpl添加進去
        addMapper(new com.holiday.jetpackstudy.DataBinderMapperImpl());
    }
}
 複製代碼

接着看業務包名下的生成類DataBinderMapperImpl

//DataBinderMapperImpl.java
package com.holiday.jetpackstudy;//注意包名
public class DataBinderMapperImpl extends DataBinderMapper {
  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
      int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
      switch(localizedLayoutId) {
          case  LAYOUT_ACTIVITYMAIN: {
              if ("layout/activity_main_0".equals(tag)) {
                  //返回了binding的具體實現類
                  return new ActivityMainBindingImpl(component, view);
              }
          }
      }
  }
}
 複製代碼

這裏出現了tag,須要知道的是,DataBinding將佈局文件拆成了兩個文件,activity_main.xml描述佈局,activity_main-layout.xml描述數據,activity_main.xmlapp/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/這個目錄下,可見其被剔除了layout外殼和數據描述,同時根佈局被加上了android:tag="layout/activity_main_0"

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:tag="layout/activity_main_0" >
 複製代碼

activity_main-layout.xmlapp/build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/目錄下,裏面能夠看到TextView被設置了一個tag="binding_1"

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" isMerge="false" layout="activity_main" modulePackage="com.holiday.jetpackstudy">
    <Variables name="user" declared="true" type="User">
    </Variables>
    <Imports name="User" type="com.holiday.jetpackstudy.model.User">
    </Imports>
    <Targets>
        <Target tag="layout/activity_main_0" view="ScrollView">
        </Target>
        <Target id="@+id/tv_name" tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.name">
                    <TwoWay>false</TwoWay>
                </Expression>
            </Expressions>
        </Target>
    </Targets>
</Layout>
 複製代碼

接下來跟進具體實現類ActivityMainBindingImpl

//ActivityMainBindingImpl.java
public ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent,View root) {
    this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds));
    //mapBindings會解析xml裏data binding相關的tag,返回Object[]
    //如:if (isRoot && tag != null && tag.startsWith("layout"))
    //如:if (tag != null && tag.startsWith("binding_"))
}
 private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { //bindings存儲了佈局文件裏含tag的view,如bindings[0]是根佈局,bindings[1]是TextView //調用父類ActivityMainBinding的構造方法,爲TextView賦值 super(bindingComponent, root, 0, (android.widget.TextView) bindings[1]); this.mboundView0 = (android.widget.ScrollView) bindings[0]; //這裏把tag置空,就不會影響到開發者本身寫的tag this.mboundView0.setTag(null); this.tvName.setTag(null); setRootTag(root); invalidateAll(); }  //省略調用鏈:invalidateAll -> requestRebind -> mUIThreadHandler.post(mRebindRunnable); // -> executePendingBindings -> executeBindingsInternal -> executeBindings  @Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } java.lang.String userName = null; com.holiday.jetpackstudy.model.User user = mUser; if ((dirtyFlags & 0x3L) != 0) { //這裏對數據進行了判空,避免了空指針 if (user != null) { userName = user.getName(); } } if ((dirtyFlags & 0x3L) != 0) { //這裏把數據設置給了TextView androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userName); } }  複製代碼

最後補充一點,ActivityMainBinding這個類的位置在app/build/generated/data_binding_base_class_source_out/debug/dataBindingGenBaseClassesDebug/out/$業務包名/databinding/路徑下,從這裏能夠找到binding能直接引用view的緣由:

//ActivityMainBinding.java
public abstract class ActivityMainBinding extends ViewDataBinding {
    public final TextView tvName;
    protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,TextView tvName) {
        super(_bindingComponent, _root, _localFieldCount);
        this.tvName = tvName;
    }
}
複製代碼

優缺點

  • 優勢:
    • DataBinding會對綁定的數據進行判空,減小判空代碼和空指針異常
    • 省去了找id操做,不會再出現id找不着的狀況
  • 缺點:
    • apt建立了不少類,增大包體積和編譯時長

參考文章

本文使用 mdnice 排版

相關文章
相關標籤/搜索