Android-MVVM设计思想

简介

MVVM:MVVM是Model-View-ViewModel的简写,双向数据绑定,数据驱动UI;通过databinding的组件,负责将view和Model进行绑定;
MVVM:内存消耗比较大,每一个对象在内存中产生一个副本,而且没刷新一次UI都会开一个线程,也比较耗电,但是开发的速度是各种模式里面最快的一种;

图解

在这里插入图片描述

基本使用

先设置dataBinding为可用

apply plugin: 'com.android.application'
android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    dataBinding{    //配置dataBinding可用
        enabled true
    }
    ......
}

来个简单的布局-Activity中使用

1、整体布局的最外层标签用layout包裹起来;
2、设置我们的数据源,data标签中使用variable标签,name是别名,这个随便取;type是全类名;
3、布局中使用:使用这样的表达式@{user.userName}去取值;
注意
1、注意到app:header="@{user.header}“这个属性了吗?这里是用在ImageView标签上面的,也就是用来显示图片;
2、 app:自定义属性名=”@{url}";具体这个自定义属性名在哪定义,和它里面的值怎么做到显示图片的,我们接着往下看;

<?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>
        <!--  此处定义该布局要用的数据的名字和类型   -->
        <!--  name 等于是个这个类取个别名   -->
        <!--  type 就是使用的类   -->
        <variable
            name="user"
            type="com.lk.mymvvm.entity.User" />

    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            app:header="@{user.header}"
            android:onClick="goToListActivity"></ImageView>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`姓名:`+user.userName}"
           />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`密码:`+user.password}"
            />
    </LinearLayout>
</layout>

再来看看这个Activity中的代码

这里不适用原来的setContentView来做布局设置了;
这里需要用到DataBindingUtil.setContentView(this,layoutId);返回了一个ActivityMainBinding对象;
给布局设置数据源: binding.setUser(user);
我这里做了个延时数据更改,想要做到数据更改,页面布局中也会跟着变化;

package com.lk.mymvvm;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;

import com.lk.mymvvm.databinding.ActivityMainBinding;
import com.lk.mymvvm.entity.User;

public class MainActivity extends AppCompatActivity {
    User user;
    Handler handler= new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        //绑定布局
        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        user = new User("lk","123456","http://b-ssl.duitang.com/uploads/item/201704/10/20170410095843_SEvMy.thumb.700_0.jpeg");
        //设置数据
        binding.setUser(user);
        //我们来做个延时修改数据,用于模拟数据双向绑定的修改
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                user.setUserName("kai");
                user.setPassword("654321");
                user.setHeader("http://img5.imgtn.bdimg.com/it/u=3465022816,2610872513&fm=11&gp=0.jpg");
            }
        },3000);

    }
    public void goToListActivity(View view) {
        Intent intent = new Intent(this,ListActivity.class);
        startActivity(intent);
    }
}

最后我们来看看这个User实体类

BaseObservable:继承这个类,看名字被观察者;
@Bindable 这个注解标注在我们需要改变的属性的get方法上面,这样自动生成BR类中,才会有这个属性id;
接下来需要在值更改的时候,通知界面刷新; notifyPropertyChanged(BR.userName); 这个方法中的id就是BR给我们生成的属性id;这样在外面调用set方法,这个值赋值之后,会去通知界面刷新;从而做到双向数据绑定的效果;
注意看这个注解 @BindingAdapter(“bind:header”),是否还记得我们在布局中的ImageView标签中的app:header="@{user.header}"这个属性?app:自定义属性=“数据”,这个类中的bind:header,就是自定义的属性名;
最后我们自定义了getImage这个方法,在方法中做了图片加载;
一定要记住继承、注解、通知界面刷新,界面才会做到数据更改进而更改布局中的值

package com.lk.mymvvm.entity;

import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import com.squareup.picasso.Picasso;

import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import androidx.databinding.library.baseAdapters.BR;

/**
 * 这个类就相当于一个ViewModel
 * 继承BaseObservable  用于通知界面更新
 */
public class User extends BaseObservable {
    private String userName;
    private String password;

    private String header;

    public User(String userName, String password,String header) {
        this.userName = userName;
        this.password = password;
        this.header = header;
    }
    //注解@Bindable在编译期间生成一个BR类
    @Bindable
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
        //通知界面更新
        notifyPropertyChanged(BR.userName);
    }
    //注解@Bindable在编译期间生成一个BR类
    @Bindable
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
        //通知界面更新
        notifyPropertyChanged(BR.password);
    }

    /**
     * 自定义属性:提供一个静态方法来加载image   必须使用静态
     * bind:header   这个header是和数据xml中的自定义属性app:header=""  这个需要一致
     * @param imageView
     * @param url  url就是pp:header="@{user.header}"中的图片url
     */
    @BindingAdapter("bind:header")
    public static void getImage(ImageView imageView,String url){
        //这里我们用了个Picasso框架来加载图片
        Picasso.with(imageView.getContext()).load(url).into(imageView);
    }
    @Bindable
    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
        //通知界面更新
        notifyPropertyChanged(BR.header);
    }
}

我们接下来在列表中去使用
ListView中的每一个item去做数据双向绑定

Activity中引用的布局文件;
因为这里不需要做数据绑定,而是要去给listView的每一项去做绑定,所以这里没有用到上面那布局的layout标签;
就一个简单的ListView;

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

   >
    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></ListView>
</LinearLayout>

1、我们来看看这个item的布局

基本写法和上面那个布局差不多;
但是我们这里是用在ListView的每一个item中去的;
需要注意的是,给最后那个TextView标签加了点击事件;

<?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>
        <!--  此处定义该布局要用的数据的名字和类型   -->
        <!--  name 等于是个这个类取个别名   -->
        <!--  type 就是使用的类   -->
        <variable
            name="user1"
            type="com.lk.mymvvm.entity.User"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ImageView
            app:header="@{user1.header}"
            android:layout_width="50dp"
            android:layout_height="50dp" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@{user1.userName}"/>
        <TextView
            android:onClick="@{user1.click}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="@{user1.password}"/>

    </LinearLayout>

    </layout>

2、ListView最核心的地方是适配器啦,我们来看看这个适配器

在MVVM中,列表的适配器,基本可以做一个通用的适配器;
这里我们写了个泛型的适配器;
接收外部传入进来的上下文、布局解析器、布局id、节点id、以及数据源
除了getView方法重写起来不一样,其他三个方法还是原来的写法;
getView方法中:
先判断布局是否存在,我想大家都不陌生,这里是放置内存多余的消耗,做布局复用;
如果视图不存在,我们就创建了ViewDataBinding对象,有的话直接拿出来重用;
绑定variableId,并将值设置进视图中;
并通过dataBinding.getRoot().getRootView()获取到视图,return出去了;
这个通用的适配器,就可以直接拿着实用了,不用多次新建适配器了

package com.lk.mymvvm.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import java.util.List;

import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;

/**
 * 使用MVVM的时候可以写个这样的通用适配器
 * @param <T>
 */
public class CommAdapter<T> extends BaseAdapter {

    private Context context;
    //布局解析器
    private LayoutInflater inflater;
    //布局id
    private int layoutId;
    //这个id会自动生成   等于是跟节点(对应布局中的data标签中的variable节点)对应的id
    private int variableId;
    //数据源
    private List<T> list;

    public CommAdapter(Context context, LayoutInflater inflater, int layoutId, int variableId, List<T> list) {
        this.context = context;
        this.inflater = inflater;
        this.layoutId = layoutId;
        this.variableId = variableId;
        this.list = list;
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewDataBinding dataBinding;
        if(convertView==null){
            dataBinding = DataBindingUtil.inflate(inflater,layoutId,parent,false);
        }else{
            //重用之前的
            dataBinding = DataBindingUtil.getBinding(convertView);
        }
        //绑定variableId
        dataBinding.setVariable(variableId,list.get(position));
        //视图返回出去
        return dataBinding.getRoot().getRootView();
    }
}

3、接下里我们来看看调用

这个实用的地方,值得注意的地方,就是第四个参数;
也就是适配器中让我们传入的variableId;注意看这里用到的是BR.user1,有没有注意到这个user1,在上面那个布局代码中使用了?别名用在哪了?
嘿嘿,去找找吧~~

package com.lk.mymvvm;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

import com.lk.mymvvm.adapter.CommAdapter;
import com.lk.mymvvm.entity.User;

import java.util.ArrayList;
import java.util.List;

import androidx.annotation.Nullable;

public class ListActivity extends Activity {

    ListView listView;
    List<User> data = new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);
        initData();
        listView = findViewById(R.id.listView);
        //设置适配器,设置的是写的通用的适配器
        listView.setAdapter(new CommAdapter<User>(this,getLayoutInflater(),R.layout.item,BR.user1,data));
    }

    private void initData() {
        data.add(new User("lk1","123456","http://img5.imgtn.bdimg.com/it/u=3465022816,2610872513&fm=11&gp=0.jpg"));
        data.add(new User("lk1","123456","http://b-ssl.duitang.com/uploads/item/201901/09/20190109072726_aNNZd.thumb.700_0.jpeg"));
        data.add(new User("lk1","123456","http://img5.imgtn.bdimg.com/it/u=3465022816,2610872513&fm=11&gp=0.jpg"));
        data.add(new User("lk1","123456","http://b-ssl.duitang.com/uploads/item/201901/09/20190109072726_aNNZd.thumb.700_0.jpeg"));
    }
}

以上就是我们在简单布局中使用数据双向绑定,以及在ListView中去使用;

ps:

如果在写代码的过程中,发现找不到BR类,或者找不到这些id,记得ReBuild一下,既然了解到需要ReBuild下才能找到这个类,那么大致都应该知道这里肯定是使用了注解处理器编译时生成类咯~~~

浅析源码

生成的布局文件

在这里插入图片描述
我们来看看这两个路径下的文件
build下面,那么我们就知道这里是编译时给我们生成的文件了吧?
app\build\intermediates\data_binding_layout_info_type_merge\debug\mergeDebugResources\out\activity_main-layout.xml

<?xml version="1.0" encoding="utf-8"?>

<Layout layout="activity_main" modulePackage="com.lk.mymvvm" filePath="E:\AndroidTools\AndroidObj\MyMVVM\app\src\main\res\layout\activity_main.xml" directory="layout" isMerge="false">
  <Variables declared="true" type="com.lk.mymvvm.entity.User" name="user">
    <location startLine="8" startOffset="8" endLine="10" endOffset="46"/>
  </Variables>
  <Targets>
    <Target tag="layout/activity_main_0" view="LinearLayout">
      <Expressions/>
      <location startLine="13" startOffset="4" endLine="32" endOffset="18"/>
    </Target>
    <Target tag="binding_1" view="ImageView">
      <Expressions>
        <Expression text="user.header" attribute="app:header">
          <Location startLine="20" startOffset="12" endLine="20" endOffset="38"/>
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="20" startOffset="26" endLine="20" endOffset="36"/>
        </Expression>
      </Expressions>
      <location startLine="17" startOffset="8" endLine="21" endOffset="58"/>
    </Target>
    <Target tag="binding_2" view="TextView">
      <Expressions>
        <Expression text="`姓名:`+user.userName" attribute="android:text">
          <Location startLine="25" startOffset="12" endLine="25" endOffset="48"/>
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="25" startOffset="28" endLine="25" endOffset="46"/>
        </Expression>
      </Expressions>
      <location startLine="22" startOffset="8" endLine="26" endOffset="12"/>
    </Target>
    <Target tag="binding_3" view="TextView">
      <Expressions>
        <Expression text="`密码:`+user.password" attribute="android:text">
          <Location startLine="30" startOffset="12" endLine="30" endOffset="48"/>
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="30" startOffset="28" endLine="30" endOffset="46"/>
        </Expression>
      </Expressions>
      <location startLine="27" startOffset="8" endLine="31" endOffset="13"/>
    </Target>
  </Targets>
</Layout>

app\build\intermediates\incremental\mergeDebugResources\stripped.dir\layout\activity_main.xml

这个文件中,他把我们的data标签整个都去掉了吧?
并在每个标签上添加了tag属性,等于是做了标记

<?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_main_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:tag="binding_1"    
            android:onClick="goToListActivity"></ImageView>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_2"              
           />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_3"              
            />
    </LinearLayout>
         

我们两个文件结合起来看

注意
我们结合上面的这个生成的xml文件来看;
登记了实体类信息,并标注了代码在我们自己写的布局中的起始位置、结束位置;

  <Variables declared="true" type="com.lk.mymvvm.entity.User" name="user">
    <location startLine="8" startOffset="8" endLine="10" endOffset="46"/>
  </Variables>

第一个Target,里面的tab标签对应的是生成的下面按个xml中的LinearLayout标签里面的tag吧?
然后标记了标签的起始位置、结束位置

<Targets>
    <Target tag="layout/activity_main_0" view="LinearLayout">
      <Expressions/>
      <location startLine="13" startOffset="4" endLine="32" endOffset="18"/>
    </Target>
  ......
</Targets>

第二个Target,里面的tag也是对应了下面按个xml中的ImageView标签的tag吧?
标记了起始位置、结束位置

  <Target tag="binding_1" view="ImageView">
      <Expressions>
        <Expression text="user.header" attribute="app:header">
          <Location startLine="20" startOffset="12" endLine="20" endOffset="38"/>
          <TwoWay>false</TwoWay>
          <ValueLocation startLine="20" startOffset="26" endLine="20" endOffset="36"/>
        </Expression>
      </Expressions>
      <location startLine="17" startOffset="8" endLine="21" endOffset="58"/>
    </Target>

下面那几个标签,这里就不贴代码了,这两个文件相互依赖;
既然生成两个这样的文件,肯定就会去做做些这两个文件吧?

接下来看看生成的java类

既然是编译时生成的类,那么我们就在这个下面找到它们
在这里插入图片描述
我们来看看
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);这个方法调用;点进去,我们来上伪代码

activity.setContentView(layoutId); 这类调用的是Activity的setContentView来做绑定了布局吧?
接下来我们看到bindToAddedViews这个方法;
判断下是否是一个或者多个控件吧?
不管是一个还是多个最终调用了bind方法;
在bind方法中调用了这个DataBinderMapperImpl实现类的getDataBinder方法

public class DataBindingUtil {
    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    ......

    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);
    }
    ......
     static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }
    ......
    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);
        }
    }
}

来看看这个DataBinderMapperImpl实现类里面的实现
获取标签tag;做了个非空判断
然后根据布局去读取标签;如果找到这个布局中的顶层标签,标识这是我们的布局文件,然后去创建了一个ActivityMainBindingImpl对象并返回出去,反之抛异常;

package com.lk.mymvvm;

import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.View;
import androidx.databinding.DataBinderMapper;
import androidx.databinding.DataBindingComponent;
import androidx.databinding.ViewDataBinding;
import com.lk.mymvvm.databinding.ActivityMainBindingImpl;
import com.lk.mymvvm.databinding.ItemBindingImpl;
import java.lang.IllegalArgumentException;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.RuntimeException;
import java.lang.String;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class DataBinderMapperImpl extends DataBinderMapper {
  private static final int LAYOUT_ACTIVITYMAIN = 1;

  private static final int LAYOUT_ITEM = 2;

  private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(2);

  static {
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.lk.mymvvm.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.lk.mymvvm.R.layout.item, LAYOUT_ITEM);
  }

  @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) {
      //我们的activity_main布局
        case  LAYOUT_ACTIVITYMAIN: {
        //匹配到了顶层layout/activity_main_0这个tag
          if ("layout/activity_main_0".equals(tag)) {
            return new ActivityMainBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
        }
        //我们的item布局
        case  LAYOUT_ITEM: {
        //匹配到了顶层layout/item_0这个tag
          if ("layout/item_0".equals(tag)) {
            return new ItemBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for item is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View[] views, int layoutId) {
    if(views == null || views.length == 0) {
      return null;
    }
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = views[0].getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
      }
    }
    return null;
  }
 ......
}

接下来看看创建的ActivityMainBindingImpl这个类

在构造方法找我那个,将对应的控件赋值,注意这里的控件是我们布局中使用的控件,在这里所有的控件都产生了副本,这就是MVVM为什么非常消耗内存的原因,Android加载了一套,这里还给生产一套,在内存中多了一套;

package com.lk.mymvvm.databinding;
import com.lk.mymvvm.R;
import com.lk.mymvvm.BR;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
@SuppressWarnings("unchecked")
public class ActivityMainBindingImpl extends ActivityMainBinding  {

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = null;
    }
    // views
    @NonNull
    private final android.widget.LinearLayout mboundView0;
    @NonNull
    private final android.widget.ImageView mboundView1;
    @NonNull
    private final android.widget.TextView mboundView2;
    @NonNull
    private final android.widget.TextView mboundView3;
    // variables
    // values
    // listeners
    // Inverse Binding Event Handlers

    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 4, sIncludes, sViewsWithIds));
    }
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 1
            );
            //给控件赋值
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView1 = (android.widget.ImageView) bindings[1];
        this.mboundView1.setTag(null);
        this.mboundView2 = (android.widget.TextView) bindings[2];
        this.mboundView2.setTag(null);
        this.mboundView3 = (android.widget.TextView) bindings[3];
        this.mboundView3.setTag(null);
        setRootTag(root);
        // 调用更新的方法
        invalidateAll();
    }

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

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }
	//设置布局中data->Variable标签中的我们的对象
    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.user == variableId) {
            setUser((com.lk.mymvvm.entity.User) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }
	
    public void setUser(@Nullable com.lk.mymvvm.entity.User User) {
        updateRegistration(0, User);
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }
}

我们回到刚刚的DataBinderMapperImpl实现类中的getDataBinder这个方法的返回这里,这个返回了一个ViewDataBinding对象,我们大致看看ViewDataBinding这个类里面

这个静态块是会被执行的吧?
里面比对下了使用的SDK的版本,版本低于19的话,这里就不支持自动更新的功能;
19版本以上,会帮我们创建一个监听器,这个监听器在什么情况下会触发了?
在我们实体类中的set方法调用notifyPropertyChanged(BR.id);的时候触发;
这个监听器一旦触发以后,这里执行了一个线程;
在县城里面执行了绑定的功能,这个绑定的方法中,做了一下判断回调,我们去看看executeBindings这个方法,这里是个抽象方法,我们去实现类;
是否还记得在DataBinderMapperImpl类的getDataBinder方法是返回ViewDataBinding 这个对象吧?最终是new了一个ActivityMainBindingImpl对象吧?也就是executeBindings这个方法的实现是在ActivityMainBindingImpl这个类里面吧?我们返回去看看这个类

public abstract class ViewDataBinding extends BaseObservable {
	......
	 private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
	 static {
	 //SDK版本低于19,就不会给我们创建监听器
        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) {
                }
            };
        }
    }
  	//监听器触发的时候,开启的线程
    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();
        }
    };
      /**
     * Evaluates the pending bindings, updating any Views that have expressions bound to
     * modified variables. This <b>must</b> be run on the UI thread.
     */
    public void executePendingBindings() {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else {
            mContainingBinding.executePendingBindings();
        }
    }
     private void executeBindingsInternal() {
        if (mIsExecutingPendingBindings) {
            requestRebind();
            return;
        }
        if (!hasPendingBindings()) {
            return;
        }
        mIsExecutingPendingBindings = true;
        mRebindHalted = false;
        if (mRebindCallbacks != null) {
            mRebindCallbacks.notifyCallbacks(this, REBIND, null);

            // The onRebindListeners will change mPendingHalted
            if (mRebindHalted) {
                mRebindCallbacks.notifyCallbacks(this, HALTED, null);
            }
        }
        if (!mRebindHalted) {
        //上面做了一些判断,回调,我们去看看这个executeBindings方法
            executeBindings();
            if (mRebindCallbacks != null) {
                mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
            }
        }
        mIsExecutingPendingBindings = false;
    }
    /**
     * @hide
     */
    protected abstract void executeBindings();
  ......
}

来看看ActivityMainBindingImpl 这个类中的executeBindings方法实现

这个executeBindings方法中基本就是在做创建成员变量,然后进行实体类的get方法取值吧
取完值然后调用适配器里面的方法,给获取到并传入的控件进行文字的修改了吧? 注意我们上面在实体类中的设置图片的方法,记得是用的静态的方法static吧?这类通过类名.方法(),方法一调用是不是我们自己写的加载图片的方法,就加载了?给我们方法中传入了imageView控件,和url图片显示的路径

public class ActivityMainBindingImpl extends ActivityMainBinding  {
......
    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeUser((com.lk.mymvvm.entity.User) object, fieldId);
        }
        return false;
    }
    private boolean onChangeUser(com.lk.mymvvm.entity.User User, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x1L;
            }
            return true;
        }
        else if (fieldId == BR.header) {
            synchronized(this) {
                    mDirtyFlags |= 0x2L;
            }
            return true;
        }
        else if (fieldId == BR.userName) {
            synchronized(this) {
                    mDirtyFlags |= 0x4L;
            }
            return true;
        }
        else if (fieldId == BR.password) {
            synchronized(this) {
                    mDirtyFlags |= 0x8L;
            }
            return true;
        }
        return false;
    }

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String javaLangStringUserPassword = null;
        com.lk.mymvvm.entity.User user = mUser;
        java.lang.String userHeader = null;
        java.lang.String javaLangStringUserUserName = null;
        java.lang.String userUserName = null;
        java.lang.String userPassword = null;

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


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

                    if (user != null) {
                        // read user.header
                        userHeader = user.getHeader();
                    }
            }
            if ((dirtyFlags & 0x15L) != 0) {

                    if (user != null) {
                        // read user.userName
                        userUserName = user.getUserName();
                    }


                    // read ("姓名:") + (user.userName)
                    javaLangStringUserUserName = ("姓名:") + (userUserName);
            }
            if ((dirtyFlags & 0x19L) != 0) {

                    if (user != null) {
                        // read user.password
                        userPassword = user.getPassword();
                    }


                    // read ("密码:") + (user.password)
                    javaLangStringUserPassword = ("密码:") + (userPassword);
            }
        }
        // batch finished
        if ((dirtyFlags & 0x13L) != 0) {
            // api target 1
			//类名点方法(静态方法)这个方法一调用,图片也就加载,我们自己那边就做了图片加载吧?
            com.lk.mymvvm.entity.User.getImage(this.mboundView1, userHeader);
        }
        if ((dirtyFlags & 0x15L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, javaLangStringUserUserName);
        }
        if ((dirtyFlags & 0x19L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, javaLangStringUserPassword);
        }
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
        flag 0 (0x1L): user
        flag 1 (0x2L): user.header
        flag 2 (0x3L): user.userName
        flag 3 (0x4L): user.password
        flag 4 (0x5L): null
    flag mapping end*/
    //end
}

在来看看这个TextViewBindingAdapter适配器的setText方法

看到这里
里面做了一下判断,判断了是否新的值和控件上的值是否是一样的,如果一样就不进行赋值;
最终调用的是TextView的setText方法进行赋值

public class TextViewBindingAdapter {
 @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        final CharSequence oldText = view.getText();
        if (text == oldText || (text == null && oldText.length() == 0)) {
            return;
        }
        if (text instanceof Spanned) {
            if (text.equals(oldText)) {
                return; // No change in the spans, so don't set anything.
            }
        } else if (!haveContentsChanged(text, oldText)) {
            return; // No content changes, so don't set anything.
        }
        view.setText(text);
    }
}

总结

1、我们创建的布局,通过标签,里面包揽了data标签和布局,会给我们生产成本两套xml文件,一套是去掉了layout标签和data标签并且控件上带有tag标签的xml文件,一套是记录我们布局文件中控件、控件tag、以及控件位置的xml文件**
2、Activity(DataBindingUtil.setContentView)或者适配器(DataBindingUtil.inflate)中,去调用Android自己的设置布局的方法,然后调用在DataBinderMapperImpl类中的getDataBinder方法中读取控件中的tag标签,创建了ActivityMainBindingImpl对象;
3、创建生成的ActivityMainBindingImpl类的构造方法中去生产出控件的副本,赋值到全局控件变量中;
4、实体类中给get方法打上@Bindable注解的时候,会根据这个注解,生成BR类中的id;
5、在我们调用实体类中的set方法的时候,调用了被观察者的notifyPropertyChanged方法,这个时候ViewDataBinding这个类中静态初始化的监听器就收到回调,开启了一个Runnable调用了修改绑定值的方法,走入到实现类ActivityMainBindingImpl的这个executeBindings实现方法中,里面去进行了实体类的get方法取值赋值到局部变量中,最后调用了TextViewBindingAdapter适配器,把获取到的控件副本、获取到的值传入到这个适配器中
6、适配器最终调用了TextView的setText方法
注意:给图片进行加载更改的时候,是通过类中的静态方法,传给了我们自己,所以我们在这个静态方法中,自己使用了图片加载框架,进行加载的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值