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
- 其中 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;
}
基本的执行原理就是这样了 实际上就是生成了一些类和副本进行相互的调用和更新。