欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

android mvvm 原理分析

程序员文章站 2022-03-16 16:30:15
...

前面我们讲了 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
    android mvvm 原理分析
  • 其中 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;
  
}

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