浅析 Android 中的三大模式:MVC, MVP 和 MVVM (二)

文章目录
  1. 1. MVVM 模式
  2. 2. 简单的比较
  3. 3. 最佳实践
  4. 4. 参考文献

你爬得高、走得远,不是为了被世界看到,而是看到整个世界。看得多了,也就知道怎么选了;走得远了,也就知道自己要的是什么了。漂泊的意义,大致就是为了有所选择。

上篇浅析 Android 中的三大模式:MVC , MVP 和 MVVM (一)中,只是谈了 MVC 和 MVP 模式,现在换个实例,谈一谈剩下来的 MVVM 模式。

MVVM 模式

MVVM,即 Model View ViewModel。该模式最早由 MicroSoft 推出,近期 Google 推出 Data Binding 框架后,也让 MVVM 开始火起来。如下图所示:

由图,该模式和 MVP 模式的区别看起来不大,只是 Presenter 层换成了 ViewModel 层。此外,View 层和 ViewModel 层相互绑定,亦即更新 ViewModel 层的数据时,View 层会相应地变动 UI 部分。MVP 与 MVVM 这两个 MVC 的变形究竟孰优孰劣,也得具体情况具体分析。本部分需要对 Data Binding 框架有基本的了解,参考精通 Android Data Binding

假设有这样一个需求,要求实现:点击一个按钮 A,获取 GitHub 上目标公司对应仓库中贡献排行第一人的名字,再点击另一个按钮 B,该第一人的名字会换成指定的名字。Demo 地址:MVVMGitHubDemo,部分代码如下:

首先,View 层即是 XML 和 Activity,多了数据绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="contributor" type="com.iamasoldier6.mvvmgithubdemo.viewmodel.Contributor"/>
</data>

<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">


<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"
android:text="get"/>


<Button
android:id="@+id/btn_change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"
android:text="change"/>


<TextView
android:id="@+id/tv_top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@
{contributor.login}"
android:textSize="30sp"/>


</LinearLayout>

</layout>

MainActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class MainActivity extends AppCompatActivity {

private ProcessDialog dialog;
private MvvmActivityMainBinding binding; // MvvmActivityMain 要与布局文件 mvvm_activity_main 一致

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

private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

@Override
public void onStart() {
showProgress();
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(Contributor contributor) {
binding.setContributor(contributor);
dismissProgress();
}
};

public void get(View view) {
getContributors("square", "retrofit");
}

public void change(View view) {
if (binding.getContributor() != null) {
binding.getContributor().setLogin("Iamasoldier6");
}
}

public void showProgress() {
if (dialog == null) {
dialog = new ProcessDialog(this);
}

dialog.showMessage("正在加载...");
}

public void dismissProgress() {
if (dialog == null) {
dialog = new ProcessDialog(this);
}

dialog.dismiss();
}

public void getContributors(String owner, String repo) {
GitHubApi.getContributors(owner, repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(new Func1<List<Contributor>, Contributor>() {

@Override
public Contributor call(List<Contributor> contributors) {
return contributors.get(0);
}
})
.subscribe(contributorSub);
}
}

用到了 Data Binding 框架和 Rx 系列。

其次,Model 层即是和数据相关的类,如 GitHubApi。

最后的重点,ViewModel 层即是 Contributor 类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Contributor extends BaseObservable {

private String login;
private int contributions;

@Bindable
public String getLogin() {
return login;
}

@Bindable
public int getContributions() {
return contributions;
}

public void setLogin(String login) {
this.login = login;
notifyPropertyChanged(BR.login); // 通知 View 更新
}

public void setContributioins(int contributions) {
this.contributions = contributions;
notifyPropertyChanged(BR.contributions);
}

@Override
public String toString() {
return login + ", " + contributions;
}
}

Contributor 继承 BaseObservable,当其内部的 variable 改变时,UI 可以作出同步的响应。由前所述,View 和 ViewModel 相互绑定在一起,ViewModel 的改变会同步到 View 层,从而 View 层作出响应,亦即这里 Contributor 和 XML 文件中那些组件元素的关系。

此外,binding 类对应之前的 MVVM 图解,其要做的工作如下:

1
2
3
binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);

binding.setContributor(contributor);

binding 通过 DataBindingUtils.setContentView() 方法将 XML 即 View 层设定。然后,通过 setContributor() 方法将 ViewModel 层注入进去。

如此,View 层( XML 的各个组件)和 ViewModel 层( Contributor )绑定在一起。binding 类,即图解中 View 和 ViewModel 中间的线。

简单的比较

综合讨论了 MVC,MVP 和 MVVM 具体的实现方案,三者各有其自己的使用方式,也有相互之间的联系。然而,改进后的框架也并不是完美的,MVP 和 MVVM 也有着自己的不足之处。

MVP

使用接口的方式连接 View 层和 Presenter 层,会产生一个问题,若有个逻辑复杂的页面,接口会很多,如一二十个。这样,维护接口的成本增加。所以,应该根据业务逻辑去斟酌着写接口,比如,定义一些基类接口,将公共的逻辑,诸如网络请求成功或失败、toast 等放入其中,而后,定义新接口的时候,去继承基类。

MVVM

参照上面所列出 MVVM 模式下的 MainActivity,发现 Data Binding 框架解决了数据绑定的问题,但是,View 层还是过重。Activity 在 MVVM 中是 View 层的,里面却写了对 Model 层的处理。

最佳实践

综合前面所有的分析,可以将 MVP 和 MVVM 的优点组合起来,实现 MVP + Data Binding,使用 Presenter 和 Model 层通信,Data Binding 去绑定数据,Demo 地址:MVPDataBindDemo,代码如下:

首先,View 层中,XML 文件不变,Activity 稍作改动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class MainActivity extends MvpActivity<ContributorView, ContributorPresenter>
implements ContributorView {


private ProcessDialog dialog;
private ActivityMainBinding binding; // ActivityMain 要与布局文件 activity_main 一致

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

@NonNull
@Override
public ContributorPresenter createPresenter() {
return new ContributorPresenter();
}

public void get(View view) {
getPresenter().get("square", "retrofit");
}

public void change(View view) {
if (binding.getContributor() != null) {
binding.getContributor().setLogin("Iamasoldier6");
}
}

@Override
public void onLoadContributorStart() {
showProgress();
}

@Override
public void onLoadContributorComplete(Contributor contributor) {
binding.setContributor(contributor);
dismissProgress();
}

public void showProgress() {
if (dialog == null) {
dialog = new ProcessDialog(this);
}

dialog.showMessage("正在加载...");
}

public void dismissProgress() {
if (dialog == null) {
dialog = new ProcessDialog(this);
}

dialog.dismiss();
}

}

其次,Presenter 层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ContributorPresenter extends MvpBasePresenter<ContributorView> {

private Subscriber<Contributor> contributorSub = new Subscriber<Contributor>() {

@Override
public void onStart() {
ContributorView view = getView();
if (view != null) {
view.onLoadContributorStart();
}
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {

}

@Override
public void onNext(Contributor contributor) {
ContributorView view = getView();
if (view != null) {
view.onLoadContributorComplete(contributor);
}
}
};

public void get(String owner, String repo) {
GitHubApi.getContributors(owner, repo)
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.map(new Func1<List<Contributor>, Contributor>() {

@Override
public Contributor call(List<Contributor> contributors) {
return contributors.get(0);
}
})
.subscribe(contributorSub);
}
}

最后,ContributorView 接口:

1
2
3
4
5
public interface ContributorView extends MvpView {
void onLoadContributorStart();

void onLoadContributorComplete(Contributor topContributor);
}

使用 Data Binding 框架节省了类似 findViewById 和数据绑定的时间,同时,Presenter 层将业务逻辑和 View 层分离。

此外,也可以在 ViewModel 中实现相应的接口,Presenter 层的数据发送到 ViewModel 中更新,由于 View 和 ViewModel 绑定在一起,如此,View 也会作出相应的反应。

总之,最佳实践也是在实践中摸索出来的,框架改变或者升级的目标也是为了实现尽量低的耦合性和尽量高的复用性。

至此,浅析 Android 中的三大模式:MVC,MVP 和 MVVM 到此结束。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.http://t.cn/Rqitf2k