安卓 APP 架构模式:MVC MVP MVVM (代码讲解)
上一篇文章:安卓 APP 架构模式:MVC MVP MVVM (图解)
本篇主要介绍三种架构的代码
目录
情景介绍:
一个APP两个按钮A、B
按钮A:用户点击,获取github上对应公司对应仓库中贡献排行第一的任的名字,
按钮B:用户点击,界面上排行第一的那个人的名字就会换成自己的。
一、MVC实现
流程:
View层:在xml中写好布局代码。
Controller层:activity作为一个controller,里面的逻辑是监听用户点击按钮并作出相应的操作。比如针对get按钮,做的工作就是调用GithubApi的方法去获取数据。
Model层:GithubApi,Contributor等类里面是数据和一些具体的逻辑操作
1.对应view层的xml文件:
两个Button一个TextView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
tools:context=".ui.view.MainActivity"
android:fitsSystemWindows="true">
<Button
android:text="A: 获取"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get" />
<Button
android:text="B: 改变"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
<TextView
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="20sp"
android:hint="此处显示结果:"/>
</LinearLayout>
部分截图:
2.对应controller层的activity
public class MainActivity extends AppCompatActivity {
private ProcessDialog dialog;
private Contributor contributor = new Contributor();
private TextView topContributor;
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) {
MainActivity.this.contributor = contributor;
topContributor.setText(contributor.login);
dismissProgress();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
topContributor = (TextView)findViewById(R.id.top_contributor);
}
public void get(View view){
getTopContributor("square", "retrofit");
}
public void change(View view){
contributor.login = "zjutkz";
topContributor.setText(contributor.login);
}
public void getTopContributor(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);
}
public void showProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}
dialog.showMessage("正在加载...");
}
public void dismissProgress(){
if(dialog == null){
dialog = new ProcessDialog(this);
}
dialog.dismiss();
}
}
代码解释:
get()方法中调用的getTopContributor方法:
public void getTopContributor(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);
}
功能解释:获取github上 owner 公司中的 repo 仓库里贡献排名第一的那个人。
rxjava参考:给 Android 开发者的 RxJava 详解
retrofit参考:用 Retrofit 2 简化 HTTP 请求
3.model层
贡献者是通过Contributor这个java bean存储的。
login表示贡献者的名字,contributor表示贡献的次数。然后通过rxjava的subscriber中的onNext()函数得到这个数据。
public class Contributor {
public String login; //贡献者的名字
public int contributions; //贡献的次数
@Override
public String toString() {
return login + ", " + contributions;
}
}
然后通过rxjava的subscriber中的onNext()函数得到这个数据。
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) {
MainActivity.this.contributor = contributor;
topContributor.setText(contributor.login);
dismissProgress();
}
};
至于另外那个change按钮的工作大家应该都看得懂,这里不重复了。
4.问题:
MVC在Android上的应用,一个具体的问题就是activity的责任过重,既是controller又是view。
这里是怎么体现的呢?
MainActivity中有一个progressDialog,在加载数据的时候显示,加载完了以后取消,=。
逻辑其实是view层的逻辑,但是这个我们没办法写到xml里面,包括TextView.setTextView(),这个也一样。
我们只能把这些逻辑写到activity中,这就造成了activity的臃肿,这个例子可能还好,如果是一个复杂的页面呢?大家自己想象一下。
二、MVP
推荐github上的一个第三方库Mosby,通过这个库大家可以很轻松的实现MVP。
流程:
和MVC最大的不同,MVP把activity作为了view层,
view层activity没有任何和model层相关的逻辑代码,取而代之的是把代码放到了presenter层中,
presenter获取了model层的数据之后,通过接口的形式将view层需要的数据返回给它就OK了。
这样的好处是什么呢?
首先,activity的代码逻辑减少了
其次,view层和model层完全解耦,
如果你需要测试一个http请求是否顺利,你不需要写一个activity,只需要写一个java类,实现对应的接口,presenter获取了数据自然会调用相应的方法,
相应的,你也可以自己在presenter中mock(虚拟)数据,分发给view层,用来测试布局是否正确。
1.首先还是xml
这个和MVC是一样的,毕竟界面的形式是一样的嘛。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
tools:context=".ui.view.MainActivity"
android:fitsSystemWindows="true">
<Button
android:text="A: 获取"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get" />
<Button
android:text="B: 改变"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
<TextView
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="20sp"
android:hint="此处显示结果:"/>
</LinearLayout>
2.接口ContributorView
接口作用:
MVP模式中,view层和presenter层靠的就是接口进行连接,而具体的就是上面的这个了,
里面定义的三个方法,第一个是开始获取数据,第二个是获取数据成功,第三个是改名。
我们的view层(activity)只要实现这个接口就可以了。
public interface ContributorView extends MvpView {
void onLoadContributorStart();
void onLoadContributorComplete(Contributor topContributor);
void onChangeContributorName(String name);
}
3.view层activity的代码
它继承自MvpActivity,实现了刚才的ContributorView接口。
继承的那个MvpActivity大家这里不用太关心主要是做了一些初始化和生命周期的封装。
我们只要关心这个activity作为view层,到底是怎么工作的。
public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {
private ProcessDialog dialog;
private TextView topContributor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
topContributor = (TextView)findViewById(R.id.top_contributor);
}
@NonNull
@Override
public ContributorPresenter createPresenter() {
return new ContributorPresenter();
}
public void get(View view){
getPresenter().get("square", "retrofit");
}
public void change(View view){
getPresenter().change();
}
@Override
public void onLoadContributorStart() {
showProgress();
}
@Override
public void onLoadContributorComplete(Contributor contributor) {
topContributor.setText(contributor.toString());
dismissProgress();
}
@Override
public void onChangeContributorName(String name) {
topContributor.setText(name);
}
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 get(View view){
getPresenter().get("square", "retrofit");
}
public void change(View view){
getPresenter().change();
}
get()和change()方法是点击按钮以后执行的,里面完全没有和model层逻辑相关的东西,只是简单的委托给了presenter。
4.presenter层
其实就是把刚才MVC中activity的那部分和model层相关的逻辑抽取了出来,并且在相应的时机调用ContributorView接口对应的方法,而我们的View层activity是实现了这个接口的,自然会走到对应的方法中。
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 topContributor) {
ContributorView view = getView();
if(view != null){
view.onLoadContributorComplete(topContributor);
}
}
};
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);
}
public void change(){
ContributorView view = getView();
if(view != null){
view.onChangeContributorName("zjutkz");
}
}
}
5.问题
由于使用接口的方式去连接view层和presenter层,这样就导致了一个问题,如果你有一个逻辑很复杂的页面,你的接口会有很多,十几二十个都不足为奇。
想象一个app中有很多个这样复杂的页面,维护接口的成本就会非常的大。
这个问题的解决方案就是你得根据自己的业务逻辑去斟酌着写接口。你可以定义一些基类接口,把一些公共的逻辑,比如网络请求成功失败,toast等等放在里面,之后你再定义新的接口的时候可以继承自那些基类,这样会好不少。
三、MV VM
首先在看这段内容之前,你需要保证你对data binding框架有基础的了解。
MVVM最近在Android上可谓十分之火,最主要的原因就是谷歌推出了data binding这个框架,可以轻松的实现MVVM。
网上的一个错误说法:data binding的ViewModel层是binding类。
那data binding中真正的viewmodel是什么呢?
1.viewmodel层
在这个例子中viewmodel层是Contributor类。
Contributor和MVP相比,继承自了BaseObservable,这是为了当Contributor内部的variable改变的时候ui可以同步的作出响应。
为什么说Contributor是一个viewmodel呢。大家还记得viewmodel的概念吗?
view和viewmodel相互绑定在一起,viewmodel的改变会同步到view层,从而view层作出响应。
这不就是Contributor和xml中组件的关系吗?
所以,大家不要被binding类迷惑了,data binding框架中的viewmodel是自己定义的那些看似是model类的东西!比如这里的Contributor!
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);
}
public void setContributions(int contributions){
this.contributions = contributions;
notifyPropertyChanged(BR.contributions);
}
@Override
public String toString() {
return login + ", " + contributions;
}
}
2.binding类
其实具体对应到之前MVVM的那张图就很好理解了,我们想一下,binding类的工作是什么?
binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);
binding.setContributor(contributor);
首先,binding要通过DataBindingUtil.setContentView()方法将view层(xml)设定。
接着,通过setXXX()方法将viewmodel层注入进去。
由于这两个工作,view层(xml的各个组件)和viewmodel层(contributor)绑定在了一起。
binding类其实就是上图中view和viewmodel中间的那根线啊!!
3.view层。
只不过多了数据绑定。view层是xml和activity。
XML:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="contributor" type="zjutkz.com.mvvm.viewmodel.Contributor"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
android:fitsSystemWindows="true">
<Button
android:id="@+id/get"
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>
<Button
android:id="@+id/change"
android:text="change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
<TextView
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30sp"
android:text="@{contributor.login}"/>
</LinearLayout>
</layout>
Activity:
public class MainActivity extends AppCompatActivity {
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();
}
};
private ProcessDialog dialog;
private MvvmActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.mvvm_activity_main);
}
public void get(View view){
getContributors("square", "retrofit");
}
public void change(View view){
if(binding.getContributor() != null){
binding.getContributor().setLogin("zjutkz");
}
}
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);
}
}
3.model层
model层是那些和数据相关的类,GithubApi等等。
4.问题
MVVM的问题呢,其实和MVC有一点像。data binding框架解决了数据绑定的问题,但是view层还是会过重,大家可以看上面那个MVVM模式下的activity
大家有没有发现,activity在MVVM中应该是view层的,但是里面却和MVC一样写了对model的处理。
有人会说你可以把对model的处理放到viewmodel层中,这样不是更符合MVVM的设计理念吗?
这样确实可以,但是progressDialog的show和dismiss呢?
你怎么在viewmodel层中控制?这是view层的东西啊,而且在xml中也没有,我相信会有解决的方案,但是我们有没有一种更加便捷的方式呢?
为何不结合一下MVP和MVVM的特点呢?其实谷歌已经做了这样的事,大家可以看下Android Architecture Blueprints。没错,就是MVP+data binding,我们可以使用presenter去做和model层的通信,并且使用data binding去轻松的bind data。
还是让我们看代码吧。
1.首先还是view层
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="contributor" type="zjutkz.com.mvpdatabinding.viewmodel.Contributor"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"
android:fitsSystemWindows="true">
<Button
android:id="@+id/get"
android:text="get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="get"/>
<Button
android:id="@+id/change"
android:text="change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="change"/>
<TextView
android:id="@+id/top_contributor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="30sp"
android:text="@{contributor.login}"/>
</LinearLayout>
</layout>
public class MainActivity extends MvpActivity<ContributorView,ContributorPresenter> implements ContributorView {
private ProcessDialog dialog;
private ActivityMainBinding binding;
@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("zjutkz");
}
}
@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();
}
}
2.presenter层
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 topContributor) {
ContributorView view = getView();
if(view != null){
view.onLoadContributorComplete(topContributor);
}
}
};
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);
}
}
3.model层
model层就是GithubApi等等。
4.总结
使用data binding框架去节省了类似findViewById和数据绑定的时间,又使用了presenter去将业务逻辑和view层分离。
当然这也不是固定的,你大可以在viewmodel中实现相应的接口,presenter层的数据直接发送到viewmodel中,在viewmodel里更新,因为view和viewmodel是绑定的,这样view也会相应的作出反应。
说到这里,我还是想重复刚才的那句话,最佳实践都是人想出来的,用这些框架根本的原因也是为了尽量低的耦合性和尽量高的可复用性。
参考链接:教你认清MVC,MVP和MVVM