【实战】Android Data Binding从抵触到爱不释手

移动开发 Android
目前Android Data Binding在运行类库只有632个方法数,算上每个layout.xml自动生成的ViewDataBinding子类(demo中每个类不超过20个方法数),方法数总和也非常有限。

1 引入

如何高效地实现以下界面?

 

登录/未登录

有好几年findViewById实战经验的我,感觉并不难啊。一般会

1.先定义一个User的Model类,数据来自JSON解析;

2.创建一个xml,随后在xml中布局完所有View,对头像、标题、积分、登录按钮一个id;

3.在Activity中通过findViewById获取到头像ImageView、标题TextView、积分TextView、登录Button,然后给Button设置监听器,再根据登陆状态展示对应数据;

实现如下:

  • User.java 

 

  • activity_detail.xml

 

  • DetailActivity

 

2 去掉烦人的findViewById(View注入)

可以看到,在Activity中View的定义、find、判空占据了大量篇幅,我们需要更优雅的实现。

2.1 ButterKnife

你可能听说过Jake Wharton的ButterKnife,这个库只需要在定义View变量的时候通过注解传入对应id,随后在onCreate时调用ButterKnife.bind(this)即可完成view的注入,示例如下:

 

2.2 Android Data Binding

如果使用了Android Data Binding,那么View的定义、find、判空这些都不用写了,如何做呢?

2.2.1 准备工作

首先,你需要满足一个条件:你的Android Plugin for Gradle版本必须等于或高于1.5.0-alpha1版本,这个版本位于根目录build.gradle中,示例如下:

  1. buildscript { 
  2.     repositories { 
  3.         jcenter() 
  4.     } 
  5.     dependencies { 
  6.         classpath 'com.android.tools.build:gradle:2.1.0-rc1' 
  7.     } 
  8.  

接着,你必须告诉编译器开启Data Binding,一般位于app:build.gradle的android标签中,示例如下:

  1. android { 
  2.     compileSdkVersion 23 
  3.     buildToolsVersion "23.0.2" 
  4.  
  5.     dataBinding { 
  6.         enabled true 
  7.     } 
  8.     ... 
  9.  

2.2.2 修改layout.xml

以activity_detail.xml为例,原来的根节点为LinearLayout,如下所示:

 

2.2.3 开始享受乐趣吧!

在上述操作完成后,编译器会自动为我们生成

com.asha.demo.databinding.ActivityDetail2Binding.java类,这个类的命令方式为:包名 + databinding + activity_detail2驼峰命名方式 + Binding.java。随后,使用这个activity_detail2的DetailActivity2.java的代码可以简化为:

  1. public class DetailActivity2 extends AppCompatActivity { 
  2.  
  3.     ActivityDetail2Binding binding;    @Override 
  4.     protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState); 
  5.         binding = DataBindingUtil.setContentView(this,R.layout.activity_detail2); 
  6.  
  7.         login(); 
  8.     }    private void login(){ fill(User.newInstance()); }    private void logout(){ fill(null); }    private void fill(final User user){        final int visibility = user != null ? View.VISIBLE : View.GONE;        if (user != null){ 
  9.             binding.detailAvatar.setImageDrawable(ContextCompat.getDrawable(this,user.getAvatar())); 
  10.             binding.detailName.setText(user.getName()); 
  11.             binding.detailDesc.setText(String.format("积分:%d 等级:%d",user.getScore(),user.getLevel())); 
  12.         } 
  13.  
  14.         binding.detailAvatar.setVisibility(visibility); 
  15.         binding.detailName.setVisibility(visibility); 
  16.         binding.detailDesc.setVisibility(visibility); 
  17.         binding.detailActionButton.setOnClickListener(new View.OnClickListener() {                @Override 
  18.                 public void onClick(View v) {                if (user == null) login();                else logout(); 
  19.             } 
  20.         }); 
  21.         binding.detailActionButton.setText(user == null ? "登录":"退出登录"); 
  22.     } 

是的,所有View的定义、find、判空都不见了,所有的这些操作都在编译器为我们生成的ActivityDetail2Binding.java中完成,只需要在onCreate时调用如下代码进行setContentView即可实现,

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

我的天哪

2.2.4 ActivityDetail2Binding中注入View相关的代码分析

可以在as中方便的查看编译器自动生成的类,这个类位于/app/build/intermediates/classes/debug/com/asha/demo/databinding/ActivityDetail2Binding.class中,缩减掉Binding逻辑后的代码为:

 

其中全局静态SparseIntArray数组中存放了4个数字,这个四个数字为R.java中生成的对应View的id,

  1. public final class R { 
  2.     ...    public static final class id { 
  3.         ...        public static final int detail_action_button = 2131492951;        public static final int detail_avatar = 2131492948;        public static final int detail_desc = 2131492950;        public static final int detail_name = 2131492949; 
  4.         ... 
  5.     } 
  6.     ... 

 在ActvityDetail2Binding实例构造的时候调用了mapBindings,一次解决了所有View的查找,mapBindings函数在ActvityDetail2Binding父类ViewDataBinding中实现。

3 使用表达式在layout.xml中填充model数据

在ActivityDetail2.java中还存在大量的View控制、数据填充代码,如何把这些代码在交给layout.xml完成呢?

3.1 ModelAdapter类

第2节中已经定义了User.java类作为Model类,但是我们经常会遇到Model类和真正View展示不一致的情况,本例子中定义一个来ModelAdapter类来完整Model数据到展示数据的适配。示例代码为ActivityDetail3.java的内部类,可以调用ActivityDetail3.java中的函数,代码定义如下:

 

3.2 activity_detail3.xml中使用model

同样复制一份activity_detail2.xml为activity_detail3.xml,在<layout>节点加入<data>节点,并且在里面定义需要用的model类(比如ModelAdapter adapter),当然也可以是基础类型变量(比如int visibility);

随后,就可以在下面的view中使用表达式了,全部布局文件如下:

  1. <?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     <data> 
  3.         <variable name="adapter" type="com.asha.demo.DetailActivity3.ModelAdapter"/> 
  4.         <variable name="visibility" type="int"/> 
  5.     </data> 
  6.     <LinearLayout 
  7.         android:orientation="vertical" android:layout_width="match_parent" 
  8.         android:layout_height="match_parent"
  9.         <View 
  10.             android:background="@color/detail_background" 
  11.             android:layout_width="match_parent" 
  12.             android:layout_height="66dp"
  13.         </View
  14.         <ImageView 
  15.             android:src="@{adapter.avatar}" 
  16.             android:visibility="@{visibility}" 
  17.             android:id="@+id/detail_avatar" 
  18.             android:layout_gravity="center" 
  19.             android:layout_marginTop="-33dp" 
  20.             android:layout_width="66dp" 
  21.             android:layout_height="66dp" /> 
  22.         <TextView 
  23.             android:visibility="@{visibility}" 
  24.             android:text="@{adapter.name}" 
  25.             android:id="@+id/detail_name" 
  26.             android:textSize="17sp" 
  27.             android:textColor="@color/textColorPrimary" 
  28.             android:layout_marginTop="15dp" 
  29.             android:layout_gravity="center" 
  30.             android:layout_width="wrap_content" 
  31.             android:layout_height="wrap_content" /> 
  32.         <TextView 
  33.             android:visibility="@{visibility}" 
  34.             android:text="@{adapter.desc}" 
  35.             android:id="@+id/detail_desc" 
  36.             android:layout_marginTop="15dp" 
  37.             android:textSize="13sp" 
  38.             android:layout_gravity="center" 
  39.             android:layout_width="wrap_content" 
  40.             android:layout_height="wrap_content" /> 
  41.         <Button 
  42.             android:text="@{adapter.actionText}" 
  43.             android:onClick="@{adapter.clickHandler}" 
  44.             android:id="@+id/detail_action_button" 
  45.             android:layout_marginTop="15dp" 
  46.             android:layout_gravity="center" 
  47.             android:textColor="@color/white" 
  48.             android:background="@drawable/selector_g_button" 
  49.             android:layout_width="220dp" 
  50.             android:layout_height="wrap_content" /> 
  51.     </LinearLayout></layout>  

3.3 DetailActivity3.java中调用填充

如下代码所示,只需要在登录状态改变的时候,给viewDataBinding设置所需要的adatper、visibility值,即可完成数据的填充

 

3.4 ActivityDetail3Binding中填充相关的代码分析

同样,ActivityDetail3Binding中,编译器根据activity_detail3.xml中的<data>标签,自动生成了诸如setAdapter、setVisibility的代码,setAdapter相关代码如下:

 非常简单,自动生成了getter和setter,在完成set操作后,调用执行notifyPropertyChanged和super.requestRebind()

  • notifyPropertyChanged

ViewDataBinding本身就是一个BaseObservable, 在往ViewDataBinding注册观察某个属性的变化,如果注册了mAdapter的变化,对应的观察器就会接收到回调。相关逻辑与反向Binding相关,谷歌官方还没给出相关使用文档,不再深入分析;

  • super.requestRebind()

1.此函数为ViewDataBinding中的函数,具体实现为判断现在是否有Rebind请求,如果有则return;如果没有则根据运行时sdk版本交给handler或者choreographer插入到下一帧中执行mRebindRunnable。

2.在mRebindRunnable中会根据当前sdk版本,如果大于等于KITKAT,则需要在onAttachToWindow后执行executePendingBindings;否则直接执行executePendingBindings。 

 

3.在父类ViewDataBinding中经过一些的判断,调用到ActivityDetail3Binding中的executeBindings,在executeBindings中根据dirtyFlags执行不同的View属性赋值,以下所有ActivityDetail3Binding相关代码都是编译器自动生成的

  1. public class ActivityDetail3Binding extends ViewDataBinding{ 
  2.   ...  protected void executeBindings() {      long dirtyFlags = 0L;      synchronized(this) { 
  3.           dirtyFlags = this.mDirtyFlags;          this.mDirtyFlags = 0L; 
  4.       } 
  5.  
  6.       Drawable avatarAdapter = null
  7.       ModelAdapter adapter = this.mAdapter; 
  8.       String descAdapter = null
  9.       String nameAdapter = null
  10.       ActivityDetail3Binding.OnClickListenerImpl androidViewViewOnCli = null
  11.       String actionTextAdapter = null;      int visibility = this.mVisibility;      if((dirtyFlags & 5L) != 0L && adapter != null) { 
  12.           avatarAdapter = adapter.getAvatar(); 
  13.           descAdapter = adapter.getDesc(); 
  14.           nameAdapter = adapter.getName(); 
  15.           androidViewViewOnCli = (this.mAndroidViewViewOnCl == null?(this.mAndroidViewViewOnCl = new ActivityDetail3Binding.OnClickListenerImpl()):this.mAndroidViewViewOnCl).setValue(adapter); 
  16.           actionTextAdapter = adapter.actionText(); 
  17.       }      if((dirtyFlags & 6L) != 0L) { 
  18.           ; 
  19.       }      if((dirtyFlags & 5L) != 0L) { 
  20.           TextViewBindingAdapter.setText(this.detailActionButton, actionTextAdapter);          this.detailActionButton.setOnClickListener(androidViewViewOnCli); 
  21.           ImageViewBindingAdapter.setImageDrawable(this.detailAvatar, avatarAdapter); 
  22.           TextViewBindingAdapter.setText(this.detailDesc, descAdapter); 
  23.           TextViewBindingAdapter.setText(this.detailName, nameAdapter); 
  24.       }      if((dirtyFlags & 6L) != 0L) {          this.detailAvatar.setVisibility(visibility);          this.detailDesc.setVisibility(visibility);          this.detailName.setVisibility(visibility); 
  25.       } 
  26.  
  27.   } 
  28.   ... 

至此,完成了View数据的填充分析。

4 Binding

自动生成的ViewDataBinding类(例如ActivityDetail3Binding)内包含了Model + View,是MVVM中的MV的概念。

第2章的View注入,第3章的View赋值都是铺垫,他们最后都是为Binding操作进行服务。目前谷歌已经支持双向Binding,但上文已经提到,目前资料比较少。本文只关注单向的Binding,即:Model的变化,自动同步到View上。

4.1 使用ObservableField

目前所提供的ObservableField有:

Observable类型 对应原类型
ObservableArrayList ArrayList
ObservableArrayMap ArrayMap
ObservableBoolean boolean
ObservableByte byte
ObservableChar char
ObservableFloat float
ObservableDouble double
ObservableLong long
ObservableInt int
ObservableParcelable<T extends Parcelable> <T extends Parcelable>
ObservableField<T> <T>

本文使用简单的ObservableInt作为示例,解决visibility的单项绑定问题。

  • 改造activity_detail4.xml:定义类型为ObservableInt的variable,name为visibility,随后赋值给ImageView的android:visibility,示例如下: 
  1. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  2.   <data> 
  3.       <variable name="visibility" type="android.databinding.ObservableInt"/> 
  4.   </data> 
  5.   <LinearLayout 
  6.       android:orientation="vertical" android:layout_width="match_parent" 
  7.       android:layout_height="match_parent"
  8.      ...      <ImageView 
  9.           android:visibility="@{visibility.get()}" 
  10.           android:id="@+id/detail_avatar" 
  11.           android:layout_gravity="center" 
  12.           android:layout_marginTop="-33dp" 
  13.           android:layout_width="66dp" 
  14.           android:layout_height="66dp" /> 
  15.       ...  </LinearLayout></layout>  
  • 改造DetailActivity4.java,只需要在onCreate时把visibility赋值给binding(ActivityDetail4Binding)即可,后面对visibility的操作,就会更新到view上,示例代码如下: 
  1. public class DetailActivity4 extends AppCompatActivity { 
  2.   ActivityDetail4Binding binding; 
  3.   ObservableInt visibility = new ObservableInt();  @Override 
  4.   protected void onCreate(@Nullable Bundle savedInstanceState) {      super.onCreate(savedInstanceState); 
  5.       binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent()); 
  6.       binding.setVisibility(visibility); 
  7.       login(); 
  8.   }  private void login(){  fill(User.newInstance());  }  private void logout(){ fill(null); }  private void fill(final User user){ 
  9.       visibility.set(user != null ? View.VISIBLE : View.GONE); 
  10.       .... 
  11.   } 
  12.   .... 
  13.  

4.2 ActivityDetail4Binding中单向绑定相关的代码分析

与给ActivityDetail4Binding直接set纯Model不同,所有的ObservableField都实现了Observable接口,只要实现了Observable接口,都是单向Binding类型,所以ActivityDetail4Binding中的setVisibility多加了一行代码:this.updateRegistration(1, visibility),其中1为propertyId,目前一共自动生成了2个,0为adatper,1为visibility,代码如下:

  1. public class ActivityDetail4Binding extends ViewDataBinding { 
  2.     ...    public void setVisibility(ObservableInt visibility) {        this.updateRegistration(1, visibility);        this.mVisibility = visibility;        synchronized(this) {            this.mDirtyFlags |= 2L; 
  3.         }        this.notifyPropertyChanged(3);        super.requestRebind(); 
  4.     } 
  5.     ... 

updateRegistration函数为ViewDataBinding中的函数,会根据 Observable、ObservableList、ObservableMap三种类型,分别创建对应的Listener。ObservableInt为Observable,所以会使用CREATE_PROPERTY_LISTENER,在registerTo函数中创建WeakPropertyListener,

代码如下: 

  1. public abstract class ViewDataBinding extends BaseObservable { 
  2.     ...    private boolean updateRegistration(int localFieldId, Object observable, 
  3.             CreateWeakListener listenerCreator) {        if (observable == null) {            return unregisterFrom(localFieldId); 
  4.         } 
  5.         WeakListener listener = mLocalFieldObservers[localFieldId];        if (listener == null) { 
  6.             registerTo(localFieldId, observable, listenerCreator);            return true
  7.         }        if (listener.getTarget() == observable) {            return false;//nothing to do, same object 
  8.         } 
  9.         unregisterFrom(localFieldId); 
  10.         registerTo(localFieldId, observable, listenerCreator);        return true
  11.     } 
  12.     ... 
  13.  

在WeakPropertyListener的mListener有个setTarget函数,这个函数会向mObservable(即外面传进来的visibility)注册一个监听器,如果visibility值发生变化,这个listener就会得到通知,回调到WeakPropertyListener的onPropertyChanged,接着通知到binding(ActivityDetail4Binding)的handleFieldChange,在handleFieldChange中调用了ActivityDetail4Binding的onFieldChange函数,如果返回值为true,则在handleFieldChange中调用requestRebind(),通知View进行赋值更新界面,onFieldChange相关代码如下:

  1. public abstract class ViewDataBinding extends BaseObservable { 
  2.     ...   private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {        boolean result = onFieldChange(mLocalFieldId, object, fieldId);        if (result) { 
  3.             requestRebind(); 
  4.         } 
  5.     } 
  6.     ... 
  7.  
  1. public class ActivityDetail4Binding extends ViewDataBinding { 
  2.     ...    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {        switch(localFieldId) {        case 0:            return this.onChangeAdapter((ModelAdapter)object, fieldId);        case 1:            return this.onChangeVisibility((ObservableInt)object, fieldId);        default:            return false
  3.         } 
  4.     } 
  5.     ... 

4.3 Observable Objects

与4.1 ObservableField类似,可以改造一下ModelAdapter:为getter方法增加@Bindable注解,为setter方法增加notifyPropertyChanged(com.asha.demo.BR.name)通知。其中,BR是根据@Bindalbe自动生成的类,给getter方法增加@Bindable注解后,BR文件自动会生成一个整型的name。改造后代码如下:

  1. public class DetailActivity4 extends AppCompatActivity { 
  2.  
  3.     ActivityDetail4Binding binding; 
  4.     ObservableInt visibility = new ObservableInt();    public class ModelAdapter extends BaseObservable{        private User user;        public ModelAdapter(User user) {            this.user = user
  5.         } 
  6.         ...        @Bindable 
  7.         public String getName(){            return user != null ? user.getName() : null
  8.         }        public void setName(String name){            if (user != nulluser.setName(name); 
  9.             notifyPropertyChanged(com.asha.demo.BR.name); 
  10.         } 
  11.         ... 
  12.     } 
  13.     ... 

 随后,在DetailActivity4.java中调用测试代码,执行完会在1秒后改变adapter上的name值,并且同步到View上,测试代码如下:

  1. binding.detailActionButton.postDelayed(new Runnable() {    @Override 
  2.     public void run() { 
  3.         adapter.setName("haha"); 
  4.     } 
  5. },1000);  

具体原理与4.1类似,不再赘述。

5 layout.xml中View属性的setter

在下述示例中,detail_name这个TextView想把adapter.name赋值给自身的text属性,就需要调用textView.setText(String)方法,这个方法就是View属性的setter方法。

  1. <TextView 
  2.     android:text="@{adapter.name}" 
  3.     android:id="@+id/detail_name" 
  4.     android:layout_width="wrap_content" 
  5.     android:layout_height="wrap_content" />  

5.1 @BindingAdapter

上述的setter方法,Data Binding库帮我们实现了大部分默认方法,具体方法参见android.databinding.adapters包下的类,下图为ViewBindingAdatper具体实现,

 

ViewBindingAdatper

其中setter方法都为static方法,第一个参数都为自身的实例,后面为xml中传入的参数,只要加入@BindingAdapter注解,编译器就会全局搜索保存在一个temp文件中,并在生成类似ActivityDetail4Binding过程中去查找所需的setter方法的。如果需要自定义,只需要在任意app代码中定义@BindingAdapter即可,例如:

  1. public class DetailActivity4 extends AppCompatActivity {    @BindingAdapter("android:alpha")    public static void globalSetAlpha(View viewfloat alpha) { 
  2.         view.setAlpha(alpha); 
  3.     } 
  4.  

5.2 DataBindingComponent

很多情况下只是某个Binding文件(例如ActivityDetail4Binding)需要自定义setter方法,这个时候就需要使用DataBindingComponent,

  • 首先,定义一个MyComponent,
  1. public class MyComponent implements android.databinding.DataBindingComponent {  @BindingAdapter("android:alpha")  public void setAlpha(View viewfloat alpha) { 
  2.       view.setAlpha(0.5f); 
  3.   }  @Override 
  4.   public MyComponent getMyComponent() {      return new MyComponent(); 
  5.   } 
  6.  
  • 接着,在生成Binding对象时传入这个DataBindingComponent实例,代码如下:
  1. @Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {  super.onCreate(savedInstanceState); 
  2.   binding = DataBindingUtil.setContentView(this,R.layout.activity_detail4,new MyComponent()); 
  3.   ... 
  4.  

完成后,这个ActivityDetail4Binding范围内的所有android:alpha="@{foo}"的方式赋值alpha的setter函数都会使用MyComponent#setAlpha。

5.3 @BindingConversion

有时候会遇到类型不匹配的问题,比如R.color.white是int,但是通过Data Binding赋值给android:background属性后,需要把int转换为ColorDrawable,实现方式如下:

  • 1.定义一个静态函数,放在项目任意类中, 
  1. @BindingConversionpublic static Drawable convertColorToDrawable(int drawable) {  return new ColorDrawable(drawable); 
  2.  
  • 2.在layout.xml中使用Data Binding,如:
  1. <Viewandroid:background="@{adapter.avatar != null ? @color/detail_background : @color/colorAccent }"android:layout_width="match_parent"android:layout_height="66dp"

对应在ActivityDetail4Binding.java中生成的代码如下所示,其中AvatarAdapterObjectn1为int类型: 

  1. ViewBindingAdapter.setBackground(this.mboundView1, DetailActivity4.convertColorToDrawable(AvatarAdapterObjectn1)); 

5.4 @BindingMethod

例如layout.xml中android:onClick属性,在Binding中真正使用setter时,就对应到了setOnClickListener方法,

  1. @BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener"), 

6 Data Binding利用编译器在背后做的那些事儿

Data Binding相关的jar包由四部分组成,

  • 1.baseLibrary-2.1.0-rc1.jar

作为运行时类库被打进APK中;

  • 2.DataBinderPlugin(gradle plugin)

在编译期使用,利用gradle-api(之前叫transform-api,1.5生,2.0改名)处理xml文件,生成DataBindingInfo.java;

  • 3.compiler-2.1.0-rc1.jar

在编译器使用,入口类继承自AbstractProcessor,用于处理注解,并生成Binding类,DataBindingCompoent.java,DataBinderMapper.java类;

  • 4.compilerCommon-2.1.0-rc1.jar

被DataBinderPlugin和compiler-2.1.0-rc1.jar所依赖

为了提高运行时的效率,Data Binding在背后做了非常多的工作,下图是我整理的编译流程,如图所示:

 

Data Binding编译流程

6.1 相关对象介绍

  • 白色部分为输入,包括

1.res/layout;

2.源代码中的注解;

  • 黄色部分为编译器处理类,包括

1.aapt编译时处理,入口类名为MakeCopy.java;

2.gradle-api处理,入口类名为DataBinderPlugin.java;

3.AbstractProcessor处理,入口类名为ProcessDataBinding.java;

  • 蓝色部分为中间产物,包括

1.data-binding-info文件夹,包含了layout的基本信息,导入的变量,View标签中的表达式,标签的位置索引等等,如下所示为data-binding-info/activity_detail3-layout.xml: 

 

2.setter_store.bin,包含所有setter相关信息;

3.layoutinfo.bin,包含所有layout相关信息;

4.br.bin,包含所有BR相关信息;

以上bin文件都以Serializable方式序列化到磁盘上,需要的时候进行反序列化操作;

  • 绿色部分为最终产物,包括

1.data-binding-layout-out(最终输出到res/layout),即去掉根节点<layout>,去掉节点<data>,与不使用Data Binding时的layout相一致,例如data-binding-layout-out/activity_detail2.xml:

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout 
  3.   android:orientation="vertical" android:layout_width="match_parent" 
  4.   android:layout_height="match_parent" android:tag="layout/activity_detail2_0" xmlns:android="http://schemas.android.com/apk/res/android"
  5.   <View 
  6.       android:background="@color/detail_background" 
  7.       android:layout_width="match_parent" 
  8.       android:layout_height="66dp"
  9.   </View
  10.   ... 
  11. </LinearLayout>  

2.DataBindingInfo.class,一个看似空的类,但在SOURCE阶段包含了一个@BindingBuildInfo注解,包含了基本DataBinding的基本信息,代码如下:

  1. // DataBindingInfo.classpublic class DataBindingInfo {  public DataBindingInfo() { 
  2.   } 
  3. }// @BindingBuildInfo@Target({ElementType.TYPE})@Retention(RetentionPolicy.SOURCE)public @interface BindingBuildInfo {  String buildId();  String modulePackage();  String sdkRoot();  int minSdk();  String layoutInfoDir();  String exportClassListTo();  boolean isLibrary();  boolean enableDebugLogs() default false;  boolean printEncodedError() default false

 3.DataBindingComponent.class,会根据自定义的DataBindingComponent自动生成对应实例化方法,例如:

  1. public interface DataBindingComponent { MyComponent getMyComponent(); 
  2.  
  3.  

4.ViewDataBinding.class的子类(ActivityDetail2Binding.class等)

5.BR.class,Bindable属性索引表,例如:

  1. public class BR {  public static final int _all = 0;  public static final int adapter = 1;  public static final int name = 2;  public static final int visibility = 3;  public BR() { 
  2.   } 
  3.  

6.DataBindingMapper.class,Mapper,用于寻找某个layout.xml对应的ViewDataBinding类,例如:

  1. class DataBinderMapper {  static final int TARGET_MIN_SDK = 16;  public DataBinderMapper() { 
  2.   }  public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View viewint layoutId) {      switch(layoutId) {      case 2130968602:          return ActivityDetail2Binding.bind(view, bindingComponent);      case 2130968603:          return ActivityDetail3Binding.bind(view, bindingComponent); 
  3.      ....      default:          return null
  4.       } 
  5.   }  ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View[] views, int layoutId) {      return null
  6.   }  int getLayoutId(String tag) {      if(tag == null) {          return 0; 
  7.       } else {          int code = tag.hashCode();          switch(code) {          case -600937657:              if(tag.equals("layout/activity_detail2_0")) {                  return 2130968602; 
  8.               }              break;          case -600936696:              if(tag.equals("layout/activity_detail3_0")) {                  return 2130968603; 
  9.               }              break; 
  10.           ....          return 0; 
  11.       } 
  12.   }  String convertBrIdToString(int id) {      return id >= 0 && id < DataBinderMapper.InnerBrLookup.sKeys.length?DataBinderMapper.InnerBrLookup.sKeys[id]:null
  13.   }  private static class InnerBrLookup {      static String[] sKeys = new String[]{"_all""adapter""name""visibility"};      private InnerBrLookup() { 
  14.       } 
  15.   } 

6.2 相关编译流程

  • STEP1 资源处理

aapt或者gradle执行时,都会触发资源处理,在资源处理过程中,DataBinding都会扫描一遍现有的资源,生成不包含<layout>的data-binding-layout-out以及DataBinding所需要的data-binding-info;

  • STEP2 DataBindingInfo.class生成

在完成资源处理后,aapt或者gradle-api都会去执行DataBindingInfo.class生成操作,把相关的信息写入DataBindingInfo.class的@BindingBuildInfo注解中;

  • STEP3 监听到注解变化

生成@BindingBuildInfo注解,或者code中发现有新的注解写入,AbstractProcessor注解处理器就开始执行注解处理。DataBinding中有一个ProcessDataBinding.java类专门来处理DataBinding相关的注解;

  • STEP4 ProcessDataBinding处理注解,生成bin

ProcessDataBinding中处理注解永远会按顺执行3步,ProcessMethodAdapter,ProcessExpressions,ProcessBindable。每次执行都会从磁盘反序列化对应的bin文件,然后忘bin中写入新的,完成后再序列化到磁盘;

  • STEP5 生成最终产物

执行ProcessMethodAdapter生成DataBindingComponents.class;执行ProcessExpressions生成ViewDataBinding.class子类(ActivityDetail2Binding.class),并触发DataBindingMapper.class更新;执行ProcessBindable生成BR.class,并触发DataBindingMapper.class更新;

7 细节补充-View Tag的使用

第二章有讲到View是如何注入的,其实需要分两种情况:

  • 1.如果这个View标签属性中只有id,没有其他"@{表达式}"形式,则按照第2章提到的方式直接通过id查找;
  • 2.如果这个View标签属性中有"@{表达式}"形式的值,则编译器会自动给这个View加个android:tag="binding_{N}", 其中{N}按顺序从0开始递增,如android:tag="binding_0"。当执行ViewDataBinding#mapBindings去注入View时,会找tag为binding_开头的View,随后执行View注入;

另外,如果View标签原来就有android:tag值,则编译器会先保存原有值信息,写入android:tag="binding_{N}"。当执行完view注入后,再把原来的值赋值给android:tag。注意如果原来的android:tag值为"binding_0",那么在View注入时将会发生错乱。

在完成View注入后,ActivityDetail3Binding会执行this.setRootTag(root),代码如下:

 

这与ListView中的ViewHoloder实现方式相似,所以如果把DataBinding运用到ListView的ViewHolder中,就不需要多生成一个ViewHolder,直接使用这个ViewDataBinding类即可,例如ListAdapter实现:

 

8 总结

  • DataBinding 库非常小

目前Android Data Binding在运行类库只有632个方法数,算上每个layout.xml自动生成的ViewDataBinding子类(demo中每个类不超过20个方法数),方法数总和也非常有限。

 

Data Binding方法数

  • DataBinding 运行时没有多余性能损耗

DataBinding所有的View注入、View赋值、Binding都是编译器自动生成的代码,这些重复的体力劳动本身就需要去做,只是交给了编译器来完成,所以运行时没有多余的性能损耗。

  • DataBinding 可以减少错误率

既然View注入、View赋值、Binding都是编译器自动完成的,只要使用正确,100%无低级错误保证,可以提高代码质量,让开发者心情愉悦。

  • DataBinding 对编译时长的影响

还没实际运用到生产环境,肯定有所延长,具体量级还未知。 

责任编辑:庞桂玉 来源: Android技术之家
相关推荐

2011-09-16 09:06:20

Smalltalk

2011-03-31 14:22:28

Chrome插件

2016-05-05 10:54:53

Android开发应用

2011-05-12 09:12:16

Ubuntu 11.0

2021-02-02 21:42:30

VS Code编辑器开发

2021-01-18 09:55:46

Spring代码Java

2010-12-08 09:59:10

CSS

2021-04-13 10:07:08

Python软件包编程语言

2009-08-29 08:41:07

Windows 7新功能

2021-04-25 10:15:38

Python编程语言软件包

2021-01-24 11:55:40

spring升华代码的技巧开发

2023-07-04 08:19:25

IDEA插件

2020-11-03 15:10:55

Spring Batc框架Java

2013-05-06 22:52:10

智能手机交互方式用户体验

2011-04-19 09:08:09

web工具项目管理

2023-07-18 08:46:34

开发必备软件工具

2021-01-20 06:29:42

JS工具操作符

2021-12-09 13:30:17

微软

2024-12-17 15:00:00

Python代码

2011-02-17 09:29:45

WebjQueryJavascript
点赞
收藏

51CTO技术栈公众号