android mvvm 原理分析

前面我们讲了 mvvm 的基本使用方法还没有了解基本使用的可以点击跳转

  • 之前的例子我们使用 mvvm 的时候布局是下面这个样子的。
<?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>

        <variable
            name="user"
            type="com.example.myapplication.User" />

        <variable
            name="user1"
            type="com.example.myapplication.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:layout_width="100dp"
            android:layout_height="200dp"
            app:headUrl="@{user.header}" />

        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{`姓名:`+user.name}" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{`密码:`+user.password}" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="click"
            android:text="click" />

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

那么实际上加载的xml是什么格式呢?代码编译后会编译成两个文件其中一个如下:在/app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml 代码如下:

<?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="100dp"
            android:layout_height="200dp"
            android:tag="binding_1"      />

        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:tag="binding_2"           />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:tag="binding_3"               />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="click"
            android:text="click" />

    </LinearLayout>

其中实际上就是我们要夹在的xml样式,只不过每个我们通过vm绑定的控件都锁了一个 tag。那么这个tag是什么呢。在app/build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml 中有如下代码:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout"
// 包含了文件路径 对应的xml文件,碎影的bean对象的一些信息
  filePath="/Users/***/AndroidStudioProjects/demoProjects/MyApplication/app/src/main/res/layout/activity_main.xml"
    isBindingData="true" isMerge="false" layout="activity_main"
    modulePackage="com.example.myapplication" rootNodeType="android.widget.LinearLayout">
    <Variables name="user" declared="true" type="com.example.myapplication.User">
        <location endLine="8" endOffset="51" startLine="6" startOffset="8" />
    </Variables>
    <Variables name="user1" declared="true" type="com.example.myapplication.User">
        <location endLine="12" endOffset="51" startLine="10" startOffset="8" />
    </Variables>
    // 我们刚才在生成的另外一个xml中看到的tag,都在这里面有相对应的 比如这些 binding_1 id tag 和 view 相对应
    <Targets>
        <Target tag="layout/activity_main_0" view="LinearLayout">
            <Expressions />
            <location endLine="48" endOffset="18" startLine="15" startOffset="4" />
        </Target>
        <Target tag="binding_1" view="ImageView">
            <Expressions>
                <Expression attribute="app:headUrl" text="user.header">
                    <Location endLine="23" endOffset="39" startLine="23" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="23" endOffset="37" startLine="23" startOffset="27" />
                </Expression>
            </Expressions>
            <location endLine="23" endOffset="42" startLine="20" startOffset="8" />
        </Target>
        <Target id="@+id/tv" tag="binding_2" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="`姓名:`+user.name">
                    <Location endLine="29" endOffset="44" startLine="29" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="29" endOffset="42" startLine="29" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="29" endOffset="47" startLine="25" startOffset="8" />
        </Target>
        <Target tag="binding_3" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="`密码:`+user.password">
                    <Location endLine="40" endOffset="48" startLine="40" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="40" endOffset="46" startLine="40" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="40" endOffset="51" startLine="37" startOffset="8" />
        </Target>
    </Targets>
</Layout>

mvvm使用这两个xml来配合处理逻辑。

  • 接下来我们看一下
    app/build/generated/ap_generated_sources/debug/out/com/example/myapplication
    在这里插入图片描述
  • 其中 BR 就是我们之前引用的,他是在编译期生成的。
public class BR {
  public static final int _all = 0;

  public static final int name = 1;

  public static final int password = 2;

  public static final int user = 3;

  public static final int user1 = 4;
}

  • 接下来通过调用流程看一下具体逻辑,我们在MainActivity的onCreate中调用了如下代码:
        ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  • 点进去setContentView查看源码:
    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId, @Nullable DataBindingComponent bindingComponent) {
        // 实际上也是调用了 actvitiy 的 setContentView
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        // 最后调用了 bindToAddedViews 来继续绑定view
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    }
  • 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);
        }
    }

bindToAddedViews 中判断了 child的个数不管多少都是最终调用了 bind 方法

  • bind方法如下:
private static DataBinderMapper sMapper = new DataBinderMapperImpl();

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

其中bind方法是调用了 sMapper 的getDataBinder,sMapper就是上面那个截图中编译时生成的DataBinderMapper的实现类DataBinderMapperImpl 。

public abstract class DataBinderMapper {
    public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId);
    public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent,
            View[] view, int layoutId);
    public abstract int getLayoutId(String tag);
    public abstract String convertBrIdToString(int id);
    @NonNull
    public List<DataBinderMapper> collectDependencies() {
        // default implementation for backwards compatibility.
        return Collections.emptyList();
    }
}
  • 下面是 DataBinderMapperImpl 实现类的 getDataBinder 方法。
// 这是读取一个view调用的方法 返回一个 ViewDataBindimg 
@Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
    	// 	读取到view的标签
      final Object tag = view.getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYMAIN: {
        //	如果标签是 activity_main_0 创建 ActivityMainBindingImpl
          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;
  }

  • 上面代码执行后会返回一个 ViewDataBinding ,ViewDataBinding初始化的时候会执行下面代码。
static {
		// 	判断版本好 如果<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) {
                }
            };
        }
    }
  • ActivityMainBindingImpl 对我们布局的view的绑定 和 对数据的绑定 通过保存了view和data副本。
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  这些定义的View 是我们自己布局中的view 这也是为什么 mvvm要更消耗内存,因为要把每一个我们自己的控件生成一个副本 
    @NonNull
    private final android.widget.LinearLayout mboundView0;
    @NonNull
    private final android.widget.ImageView mboundView1;
    @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, 2
            , (android.widget.TextView) bindings[2]
            );
            // 将定义好的控件副本进行赋值 然后清除tag
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView1 = (android.widget.ImageView) bindings[1];
        this.mboundView1.setTag(null);
        this.mboundView3 = (android.widget.TextView) bindings[3];
        this.mboundView3.setTag(null);
        this.tv.setTag(null);
        // 调用了setRootTag
        setRootTag(root);
        // listeners
        invalidateAll();
    }

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

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.user == variableId) {
            setUser((com.example.myapplication.User) variable);
        }
        else if (BR.user1 == variableId) {
            setUser1((com.example.myapplication.User) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }

    public void setUser(@Nullable com.example.myapplication.User User) {
        updateRegistration(0, User);
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }
    public void setUser1(@Nullable com.example.myapplication.User User1) {
        this.mUser1 = User1;
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeUser((com.example.myapplication.User) object, fieldId);
            case 1 :
                return onChangeUser1((com.example.myapplication.User) object, fieldId);
        }
        return false;
    }
    private boolean onChangeUser(com.example.myapplication.User User, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x1L;
            }
            return true;
        }
        else if (fieldId == BR.name) {
            synchronized(this) {
                    mDirtyFlags |= 0x4L;
            }
            return true;
        }
        else if (fieldId == BR.password) {
            synchronized(this) {
                    mDirtyFlags |= 0x8L;
            }
            return true;
        }
        return false;
    }
    private boolean onChangeUser1(com.example.myapplication.User User1, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x2L;
            }
            return true;
        }
        return false;
    }
	// 执行绑定的操作 这里面将我们设置的数据都存储下来
    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userName = null;
        java.lang.String javaLangStringUserPassword = null;
        com.example.myapplication.User user = mUser;
        java.lang.String userHeader = null;
        java.lang.String javaLangStringUserName = null;
        java.lang.String userPassword = null;

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


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

                    if (user != null) {
                        // read user.name
                        userName = user.getName();
                    }


                    // read ("姓名:") + (user.name)
                    javaLangStringUserName = ("姓名:") + (userName);
            }
            if ((dirtyFlags & 0x11L) != 0) {

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

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


                    // read ("密码:") + (user.password)
                    javaLangStringUserPassword = ("密码:") + (userPassword);
            }
        }
        // batch finished
        if ((dirtyFlags & 0x11L) != 0) {
            // api target 1
			// 这里面直接使用的类名.方法名,所以User中要定义成静态方法
            com.example.myapplication.User.getImage(this.mboundView1, userHeader);
        }
        if ((dirtyFlags & 0x19L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, javaLangStringUserPassword);
        }
        if ((dirtyFlags & 0x15L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv, javaLangStringUserName);
        }
    }
 
    private  long mDirtyFlags = 0xffffffffffffffffL;
  
}

基本的执行原理就是这样了 实际上就是生成了一些类和副本进行相互的调用和更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值