美文网首页
MVVM开发模式与DataBinding

MVVM开发模式与DataBinding

作者: 梦星夜雨 | 来源:发表于2020-06-15 11:15 被阅读0次

前言

MVVM(Model-View-ViewModel)本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

结构

  • Model: 业务数据模型
  • View: 界面中的布局和外观
  • ViewModel: 逻辑控制层,负责处理数据和处理View层中的业务逻辑

DataBinding简介

DataBinding是Google官方推出的数据绑定器,作用是将数据和View绑定起来,然后数据改变的时候View会自动刷新。
这里需要注意的是MVVM是一种设计思想,而DataBinding是一种工具。二者有本质的差别。
引入dataBinding,在build.gradle中添加以下代码。

android{
  ....
  dataBinding{
    enabled true
  }
}

通过改变XML布局文件来使用dataBinding。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 定义该布局需要绑定的数据名称和类型 -->
    <data>
        <variable
            name="loginViewModel"
            type="com.mvvm.vm.LoginViewModel" />
        <!--<variable-->
            <!--name="user"-->
            <!--type="com.mvvm.model.UserInfo" />-->
    </data>

    <!-- 下部分内容和平时布局文件一样 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:addTextChangedListener="@{loginViewModel.nameInputListener}"
            android:hint="请输入账户"
            android:text="@={loginViewModel.userInfo.name}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:addTextChangedListener="@{loginViewModel.pwdInputListener}"
            android:hint="请输入密码"
            android:text="@={loginViewModel.userInfo.pwd}" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:onClick="@{loginViewModel.loginClickListener}"
            android:text="登录" />

    </LinearLayout>
</layout>

Model层依旧是一个javaBean文件,这里使用的是ObservableField来定义的:

public class UserInfo {
    // 必须是public修饰符,因为是DataBinding的规范
    public ObservableField<String> name = new ObservableField<>();
    public ObservableField<String> pwd = new ObservableField<>();

}

VM(ViewModel)层通过TextWatcher和OnClickListener来实现与View的绑定,极大的精简了Activity里面的代码,

public class LoginViewModel {
    public UserInfo userInfo;
    public LoginViewModel(ActivityMvvmLoginBinding binding) {
        userInfo = new UserInfo();
        // 将ViewModel和View进行绑定,通过DataBinding工具
        binding.setLoginViewModel(this);
    }
    public TextWatcher nameInputListener = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
            userInfo.name.set(String.valueOf(charSequence));
        }

        @Override
        public void afterTextChanged(Editable editable) {

        }
    };
    public TextWatcher pwdInputListener = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // View层接收到用户的输入,改变Model层的javabean属性
            userInfo.pwd.set(String.valueOf(s));
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    };
   public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 模拟网络请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // Model层属性的变更,改变View层的显示
                    userInfo.name.set("zjjj");
                    userInfo.pwd.set("654321");

                    if ("xzzz".equals(userInfo.name.get()) && "123456".equals(userInfo.pwd.get())) {
                        Log.e("xzzz >>> ", "登录成功!");
                    } else {
                        Log.e("xzzz >>> ", "登录失败!");
                    }
                }
            }).start();
        }
    };
}

而Activity中,只有两行代码,极大的精简了Activity中的逻辑。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMvvmLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm_login);
        new LoginViewModel(binding);
    }
}

MVVM模式的优点
1.很好做到数据的一致性。双向绑定技术,当Model变化时,View-Model会自动更新,View也会自动变化。
2.降低了项目的耦合度。一个ViewModel层可以绑定不同的View层,当Model变化时View可以不变。
3.增加的代码的可重用性。可以把一些视图逻辑放在ViewModel层中,让很多的View重用这些视图逻辑,极大的精简了代码。

MVVM模式的缺点
1.数据绑定使得 Bug 很难被调试。你看到界面异常了,由于数据绑定的原因,定位原始问题的出处就变得不那么容易了。
2.内存消耗过大。

DataBinding内存消耗过大的原因

1.对于每个XML文件都需要一个额外的数组,造成额外的数组开销。
2.ViewDataBinding中的静态代码块中会有个OnAttachStateChangeListener方法会根据Activity个数新建监听的线程。
3.频繁调用调用 invalidateAll()方法刷新界面。

1.在ActivityMvvmLoginBindingImpl中我们可以看到有数组的存在,而在ViewDataBinding中使用mapBindings()方法根据每个XML文件生成binding_下标的数组对应每个控件。

private ActivityMvvmLoginBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 2
            );
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView1 = (android.widget.EditText) bindings[1];
        this.mboundView1.setTag(null);
        this.mboundView2 = (android.widget.EditText) bindings[2];
        this.mboundView2.setTag(null);
        this.mboundView3 = (android.widget.Button) bindings[3];
        this.mboundView3.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
        ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding == null) {
            Object objTag = view.getTag();
            String tag = objTag instanceof String ? (String)objTag : null;
            boolean isBound = false;
            int indexInIncludes;
            int id;
            int count;
            if (isRoot && tag != null && tag.startsWith("layout")) {
                id = tag.lastIndexOf(95);
                if (id > 0 && isNumeric(tag, id + 1)) {
                    count = parseTagInt(tag, id + 1);
                    if (bindings[count] == null) {
                        bindings[count] = view;
                    }

                    indexInIncludes = includes == null ? -1 : count;
                    isBound = true;
                } else {
                    indexInIncludes = -1;
                }
            } else if (tag != null && tag.startsWith("binding_")) {
                id = parseTagInt(tag, BINDING_NUMBER_START);
                if (bindings[id] == null) {
                    bindings[id] = view;
                }

                isBound = true;
                indexInIncludes = includes == null ? -1 : id;
            } else {
                indexInIncludes = -1;
            }

            if (!isBound) {
                id = view.getId();
                if (id > 0 && viewsWithIds != null && (count = viewsWithIds.get(id, -1)) >= 0 && bindings[count] == null) {
                    bindings[count] = view;
                }
            }

            if (view instanceof ViewGroup) {
                ViewGroup viewGroup = (ViewGroup)view;
                count = viewGroup.getChildCount();
                int minInclude = 0;

                for(int i = 0; i < count; ++i) {
                    View child = viewGroup.getChildAt(i);
                    boolean isInclude = false;
                    if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                        String childTag = (String)child.getTag();
                        if (childTag.endsWith("_0") && childTag.startsWith("layout") && childTag.indexOf(47) > 0) {
                            int includeIndex = findIncludeIndex(childTag, minInclude, includes, indexInIncludes);
                            if (includeIndex >= 0) {
                                isInclude = true;
                                minInclude = includeIndex + 1;
                                int index = includes.indexes[indexInIncludes][includeIndex];
                                int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                                int lastMatchingIndex = findLastMatching(viewGroup, i);
                                if (lastMatchingIndex == i) {
                                    bindings[index] = DataBindingUtil.bind(bindingComponent, child, layoutId);
                                } else {
                                    int includeCount = lastMatchingIndex - i + 1;
                                    View[] included = new View[includeCount];

                                    for(int j = 0; j < includeCount; ++j) {
                                        included[j] = viewGroup.getChildAt(i + j);
                                    }

                                    bindings[index] = DataBindingUtil.bind(bindingComponent, included, layoutId);
                                    i += includeCount - 1;
                                }
                            }
                        }
                    }

                    if (!isInclude) {
                        mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                    }
                }
            }

        }
    }

2.在静态代码块中,当版本大于19的时候,会有一个OnAttachStateChangeListener监听,在每个Activity发生改变时,会新建一个mRebindRunnable的线程,当Activity数量很多的时候,对应的线程也会增加,由此增加内存的消耗。

static {
        ...
        if (VERSION.SDK_INT < 19) {
            ROOT_REATTACHED_LISTENER = null;
        } else {
            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
                @TargetApi(19)
                public void onViewAttachedToWindow(View v) {
                    ViewDataBinding binding = ViewDataBinding.getBinding(v);
                    binding.mRebindRunnable.run();
                    v.removeOnAttachStateChangeListener(this);
                }

                public void onViewDetachedFromWindow(View v) {
                }
            };
        }
    }
private final Runnable mRebindRunnable = new Runnable() {
        public void run() {
            synchronized(this) {
                ViewDataBinding.this.mPendingRebind = false;
            }

            ViewDataBinding.processReferenceQueue();
            if (VERSION.SDK_INT >= 19 && !ViewDataBinding.this.mRoot.isAttachedToWindow()) {
                ViewDataBinding.this.mRoot.removeOnAttachStateChangeListener(ViewDataBinding.ROOT_REATTACHED_LISTENER);
                ViewDataBinding.this.mRoot.addOnAttachStateChangeListener(ViewDataBinding.ROOT_REATTACHED_LISTENER);
            } else {
                ViewDataBinding.this.executePendingBindings();
            }
        }
    };

3.一但Model层数据发生变化,会调用 invalidateAll()方法刷新界面,频繁使用Handlerf发送消息。

 @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x8L;
        }
        requestRebind();
    }
protected void requestRebind() {
        if (mContainingBinding != null) {
            mContainingBinding.requestRebind();
        } else {
            final LifecycleOwner owner = this.mLifecycleOwner;
            if (owner != null) {
                Lifecycle.State state = owner.getLifecycle().getCurrentState();
                if (!state.isAtLeast(Lifecycle.State.STARTED)) {
                    return; // wait until lifecycle owner is started
                }
            }
            synchronized (this) {
                if (mPendingRebind) {
                    return;
                }
                mPendingRebind = true;
            }
            if (USE_CHOREOGRAPHER) {
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                mUIThreadHandler.post(mRebindRunnable);
            }
        }
    }

Demo源码地址

相关文章

网友评论

      本文标题:MVVM开发模式与DataBinding

      本文链接:https://www.haomeiwen.com/subject/mqsgtktx.html