MVVM 模式简介
MVVM模式是指Model-View-ViewModel。相信看过笔者关于MVP的文章的读者也会发现,无论如何抽象化,在我们的View层中是无法避免的要处理一部分逻辑的。而MVVM模式中的View是将View的状态和行为完全抽象化,把逻辑与界面的控制完全交给ViewModel处理。
MVVM由下面三个核心组件组成:
- Model: 用于获取业务数据模型
- View: 定义了界面中的布局和外观
- ViewModel: 逻辑控制层,负责处理数据和处理View层中的业务逻辑
什么是DataBinding
DataBinding是Google官方推出的数据绑定器,这个绑定器的作用是把数据和View绑定起来,然后数据改变的时候View会自动刷新,这个DataBinding就是我们实现MVVM模式的关键。
引入DataBinding
引入DataBinding的方式很简单,我们只需要在Module的build.gradle添加如下代码即可。
android{
....
dataBinding{
enabled true
}
}
使用DataBinding
使用DataBinding的布局文件和普通的布局文件有点不同,DataBinding布局文件的根标签是layout标签,layout里面有一个data元素和View元素,这个View元素就是我们没使用DataBinding时候的布局文件。下面看看例子代码:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http:///apk/res/android" xmlns:tools="http:///tools" tools:context="com.loaderman.modedemo.MainActivity"> <data> <variable name="user" type="com.loaderman.modedemo.User" /> <variable name="myHandlers" type="com.loaderman.modedemo.myHandlers" /> </data> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="40dp" android:onClick="@{myHandlers.onClickName}" android:text="@{}" /> <Button android:id="@+id/btn" android:layout_width="match_parent" android:text="更新数据" android:layout_height="wrap_content" /> </LinearLayout> </layout>
Android MVVM 模式
MVVM在不同的平台实现方式是有一定差异性的。在Google IO 2017 ,Google发布了一个官方应用架构库Architecture Components,这个架构库便是Google对Android应用架构的建议,也被称之为Android官方应用架构指南。Android Architecture Components在Google中国开发者网站中能找到。和Data Binding Library一样官方还没翻译为中文。
下图是Architecture的应用架构图。结合Android程序特点,整体上与微软的MVVM类似,但是做了更细致的模块划分。

View
显而易见 Activity/Fragment 便是MVVM中的View,当收到ViewModel传递来的数据时,Activity/Fragment负责将数据以你喜欢的方式显示出来。实际是View成还包括ViewDataBinding(根据xml自动生成),上面中并没有体现。
ViewModel
ViewModel作为Activity/Fragment与其他组件的连接器。负责转换和聚合Model中返回的数据,使这些数据易于显示,并把这些数据改变及时的通知给Activity/Fragment。
ViewModel是具有生命周期意识的,当Activity/Fragment销毁时ViewModel的onClear方法会被回调,你可以在这里做一些清理工作。
LiveData是具有生命周期意识的一个可观察的的数据持有者,ViewModel中的数据由LiveData持有,并且只有当Activity/Fragment处于活动时才会通知UI数据的改变,避免无用的刷新UI;
Model
Repository及其下方就是Model了。Repository负责提取和处理数据。数据可以来自本地数据库(Room),也可以来自网络,这些数据统一有Repository处理,对应隐藏数据来源及获取方式
Binder 绑定器
上图中并没有标出绑定器在哪里,其实在任何MVVM的实现中,数据绑定技术都是必须的。而上图仅仅是应用架构图。
Android中的数据绑定技术由 DataBinding和LiveData共同实现。当Activity/Fragment接收到来自ViewModel中的新数据时(由LiveData自动通知数据的改变),将这些数据通过DataBinding绑定到ViewDataBinding中,UI将会自动刷新,而不用书写类似setText的方法。
model层
import java.util.List;
/**
* Author: loaderman
*/
public class TestEntity {
private int code;
private String message;
private List<ResultBean> result;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List<ResultBean> getResult() {
return result;
}
public void setResult(List<ResultBean> result) {
this.result = result;
}
@Override
public String toString() {
return "TestEntity{" +
"code=" + code +
", message='" + message + '\'' +
", result=" + result +
'}';
}
public static class ResultBean {
/**
* data : {"subTitle":null,"dataType":"TextCard","actionUrl":null,"id":0,"text":"今日社区精选","type":"header5","follow":null,"adTrack":null}
* adIndex : -1
* tag : null
* id : 0
* type : textCard
*/
private DataBean data;
private int adIndex;
private Object tag;
private int id;
private String type;
public DataBean getData() {
return data;
}
public void setData(DataBean data) {
this.data = data;
}
public int getAdIndex() {
return adIndex;
}
public void setAdIndex(int adIndex) {
this.adIndex = adIndex;
}
public Object getTag() {
return tag;
}
public void setTag(Object tag) {
this.tag = tag;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
return "ResultBean{" +
"data=" + data +
", adIndex=" + adIndex +
", tag=" + tag +
", id=" + id +
", type='" + type + '\'' +
'}';
}
public static class DataBean {
/**
* subTitle : null
* dataType : TextCard
* actionUrl : null
* id : 0
* text : 今日社区精选
* type : header5
* follow : null
* adTrack : null
*/
private Object subTitle;
private String dataType;
private Object actionUrl;
private int id;
private String text;
private String type;
private Object follow;
private Object adTrack;
public Object getSubTitle() {
return subTitle;
}
public void setSubTitle(Object subTitle) {
this.subTitle = subTitle;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public Object getActionUrl() {
return actionUrl;
}
public void setActionUrl(Object actionUrl) {
this.actionUrl = actionUrl;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Object getFollow() {
return follow;
}
public void setFollow(Object follow) {
this.follow = follow;
}
public Object getAdTrack() {
return adTrack;
}
public void setAdTrack(Object adTrack) {
this.adTrack = adTrack;
}
@Override
public String toString() {
return "DataBean{" +
"subTitle=" + subTitle +
", dataType='" + dataType + '\'' +
", actionUrl=" + actionUrl +
", id=" + id +
", text='" + text + '\'' +
", type='" + type + '\'' +
", follow=" + follow +
", adTrack=" + adTrack +
'}';
}
}
}
}
Respository用户从服务器获取数据,这里使用的网络框架是retrofit+rxjava+okhttp:
public class TestRespository { public void getData(final MutableLiveData<TestEntity> liveData) { HttpServiceDataProvder.getInstence().loadTestData(new HttpCallBack<TestEntity>() { @Override public void onSuccess(TestEntity testEntity) { liveData.setValue(testEntity); } @Override public void onError(Throwable e) { } }); } }
ViewModel层
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.loaderman.frameappdemo.mvvm.model.TestEntity; import com.loaderman.frameappdemo.mvvm.repository.TestRespository; /** * Author: loaderman */ public class TestViewModel extends ViewModel { private final TestRespository testRespository; private MutableLiveData<TestEntity> liveData; public MutableLiveData<TestEntity> getTestEntityLiveData() { if (liveData == null) { liveData = new MutableLiveData<>(); } return liveData; } public TestViewModel() { testRespository = new TestRespository(); } public void getDataFromNet() { testRespository.getData(liveData); } }
view层
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http:///apk/res/android" xmlns:app="http:///apk/res-auto" xmlns:tools="http:///tools"> <data> <variable name="test" type="com.loaderman.frameappdemo.mvvm.model.TestEntity" /> </data> <androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/refresh" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mvvm.view.MVVMActivity"> <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="match_parent" android:text="@{test.result.toString()}"></TextView> </LinearLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </layout>
import ; import com.loaderman.frameappdemo.R; import com.loaderman.frameappdemo.databinding.ActivityMvvmBinding; import com.loaderman.frameappdemo.mvvm.model.TestEntity; import com.loaderman.frameappdemo.mvvm.viewmodel.TestViewModel; public class MVVMActivity extends AppCompatActivity { private ActivityMvvmBinding viewDataBinding; private TestViewModel testViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mvvm); viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm); testViewModel = new ViewModelProvider(this).get(TestViewModel.class); MutableLiveData<TestEntity> testViewModelData = testViewModel.getTestEntityLiveData(); initData(); testViewModelData.observe(this, new Observer<TestEntity>() { @Override public void onChanged(TestEntity testEntity) { viewDataBinding.refresh.setRefreshing(false); viewDataBinding.setVariable(BR.test, testEntity); } }); viewDataBinding.refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { initData(); } }); } private void initData() { viewDataBinding.refresh.setRefreshing(true); testViewModel.getDataFromNet(); } }
效果:

如果你对jetpack不了解,欢迎关注微信公众号【码上加油站】学习~
在listview 等列表的adapter中使用数据绑定,可以使用如下方式
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); DataBindingUtil.inflate(inflater,R.layout.activity_main, viewgroup,false);
MVVM设计模式的优点
1.双向绑定技术,当Model变化时,View-Model会自动更新,View也会自动变化。很好做到数据的一致性,不用担心,在模块的这一块数据是这个值,在另一块就是另一个值了。所以 MVVM模式有些时候又被称作:model-view-binder模式。
2.View的功能进一步的强化,具有控制的部分功能,若想无限增强它的功能,甚至控制器的全部功几乎都可以迁移到各个View上(不过这样不可取,那样View干了不属于它职责范围的事情)。View可以像控制器一样具有自己的View-Model.
3.由于控制器的功能大都移动到View上处理,大大的对控制器进行了瘦身。不用再为看到庞大的控制器逻辑而发愁了。
4.可以对View或ViewController的数据处理部分抽象出来一个函数处理model。这样它们专职页面布局和页面跳转,它们必然更一步的简化。
MVVM设计模式的缺点
第一点:数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
第二点:一个大的模块中,model也会很大,虽然使用方便了也很容易保证了数据的一致性,当时长期持有,不释放内存,就造成了花费更多的内存。
第三点:数据双向绑定不利于代码重用。客户端开发最常用的重用是View,但是数据双向绑定技术,让你在一个View都绑定了一个model,不同模块的model都不同。那就不能简单重用View了。
















