随着技术的发展,Activity(View) 做的事情越来越多,动辄几百上千行的代码使得 Activity 越来越臃肿。为了更好的划分视图(View)和模型(Model)之间的职责,让 View 只处理数据的展示和用户的交互,把数据的处理交由 Model 处理。 这种模式便是MVP。

      我们都知道Android中比较常用的模式是MVC模式,那么MVC模式是什么呢?它和MVP模式有什么区别呢? 

一、概述。

1.MVC模式;

先看MVC模式的结构图,

android mvvm 界面跳转 android mvc模式_android mvvm 界面跳转

MVC模式的结构分为三部分,Model、View、Controller。

(1).Model:业务逻辑和实体模型;

(2).View:应用的UI界面,也就是布局文件;

(3).Controller:负责处理用户事件和视图部分的展示。在Android中,它可能是Activity或者Fragment类;

在Android开发中使用MVC模式,View是布局文件,它的内容只是布局,而Controller即Activity或者Fragment类或者其他类,既要处理View又要处理数据,因此View视图和Controller控制器并不是完全分离的,并且造成了Controller的代码量非常大,还有可能出现好多重复代码,单元测试也不好测试等等。

2.MVP模式;

下面是MVP模式的结构图,

android mvvm 界面跳转 android mvc模式_ide_02

MVP模式的结构也分为三部分,Model、View、Presenter。

(1).Model: 处理数据的加载或者存储,比如从网络或本地获取数据等;
(2).View:用户交互和视图显示,在Android中,它可能是Activity或者Fragment类;
(3).Presenter: 负责完成View于Model间的逻辑和交互,是模型与视图之间的桥梁,将模型与视图分离开来。

使用MVP模式,当有交互时,调用Presenter里的对应方法。 Presenter 负责完成View于Model间的交互,从Model里取数据,返回给View处理好的数据。 View不直接与Model交互。

3.MVP模式的好处;

    View不直接与Model交互 ,而是通过与Presenter交互来与Model间接交互 Presenter与View的交互是通过接口来进行的,更有利于添加单元测试 通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑
     这样分层的好处就是大大减少了Model与View层之间的耦合度。一方面可以使得View层和Model层单独开发与测试,互不依赖。另一方面Model层可以封装复用,可以极大的减少代码量。当然,MVP还有其他的一些优点,这里不再赘述。下面看下MVP模式在具体项目中的使用。

PS:

1.M(Model),模型:表示数据模型和业务逻辑(business logic)。

 

model层主要负责:

· 从网络,数据库,文件,传感器,第三方等数据源读写数据。

· 对外部的数据类型进行解析转换为APP内部数据交由上层处理。

· 对数据的临时存储,管理,协调上层数据请求。

2.V(View),视图:将数据呈现给用户。一般的视图都只是包含用户界面(UI),而不包含界面逻辑。

view 层主要负责:

· 提供UI交互

· 在presenter的控制下修改UI。

· 将业务事件交由presenter处理。

注意: View层不存储数据,不与Model层交互。

在Android中View层一般是Activity、Fragment、View(控件)、ViewGroup(布局等)等。

3.P(Presenter),

作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。Presenter包含了根据用户在视图中的行为去更新模型的逻辑。视图仅仅只是将用户的行为告知Presenter,而Presenter负责从视图中取得数据然后发送给模型。

4.MVP模式具体的执行过程。

    View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有Viwe层以及Model层的Interface的引用,而View层持有Presenter层Interface的引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的某个接口,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载完毕,最后Presenter层再调用View层的接口将加载后的数据展示给用户。

二、项目示例。

实现一个登录实例。

先看项目的整体结构,

android mvvm 界面跳转 android mvc模式_User_03

  

android mvvm 界面跳转 android mvc模式_android mvvm 界面跳转_04

项目中使用了OkHttp访问网络(有关OkHttp,详情请看Android OkHttp(二)实战),这个项目还需要运行一个后台接口服务,用于登录(有关接口开发,详情请看 SpringMVC 开发接口和 java web开发(二) 接口开发),此处就不再详说了。下面看看具体实现。

1.首先创建一个实体对象,这个肯定有,

[java] view plain copy

 

1. public class User implements Serializable {  
2. public int id;  
3. public String name;  
4. public int age;  
5. public int sex;  
6. public int mobile;  
7.   
8.   
9. public int getMobile() {  
10. return mobile;  
11.     }  
12.   
13. public void setMobile(int mobile) {  
14. this.mobile = mobile;  
15.     }  
16.   
17. public int getId() {  
18. return id;  
19.     }  
20.   
21. public void setId(int id) {  
22. this.id = id;  
23.     }  
24.   
25. public String getName() {  
26. return name;  
27.     }  
28.   
29. public void setName(String name) {  
30. this.name = name;  
31.     }  
32.   
33. public int getAge() {  
34. return age;  
35.     }  
36.   
37. public void setAge(int age) {  
38. this.age = age;  
39.     }  
40.   
41. public int getSex() {  
42. return sex;  
43.     }  
44.   
45. public void setSex(int sex) {  
46. this.sex = sex;  
47.     }  
48.   
49. @Override  
50. public String toString() {  
51. return "User [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + "]";  
52.     }  
53.   
54.   
55. }

User类定义了用户的一些属性,很简单,不多说了。

 

2.Model层;

需要定义一个接口,有一个登录方法,

[java] view plain copy

 

1. public interface IUserLoginModel {  
2. public void login(String userName, String passWord, CallBackListener callBackListener);  
3. }

然后实现该接口,

[java] view plain copy

1. public class UserLoginModelImpl implements IUserLoginModel {  
2. @Override  
3. public void login(String userName, String passWord, final CallBackListener callBackListener) {  
4. //接口返回的json  
5. new TypeToken<EntityResponse<User>>() {  
6.         };  
7. new HashMap<>();  
8. "name",userName);  
9. "pws",passWord);  
10. new BaseResponseCallback<EntityResponse<User>>() {  
11. @Override  
12. public void onCompleted(final Throwable e, EntityResponse<User> result) {  
13. if (e != null) {  
14.                     callBackListener.fail(e.getMessage());  
15. else {  
16.                     User user = result.getObject();  
17. "登录成功", user);  
18.                 }  
19.             }  
20.         });  
21.     }  
22.   
23.   
24. }

这个类就是具体完成功能的类。目前这个类使用Okhttp实现了登录方法,登录成功返回登录用户的信息,登录失败返回错误提示。

3.View层;

需要定义View层的接口。View层具体需要哪些接口,这个要根据实际情况来定义,这里借用鸿洋博文中的原文: 

[plain] view plain copy


  1. 对于View的接口,去观察功能上的操作,然后考虑:  
  2. 该操作需要什么?(getUserName, getPassword)  
  3. 该操作的结果,对应的反馈?(toMainActivity, showFailedError)  
  4. 该操作过程中对应的友好的交互?(showLoading, hideLoading)  

 例如,异步操作的话,友好提示,要显示进度条、关闭进度条,

[java] view plain copy
 
1. public void showProgressDialog();  
2. public void stopProgressDialog();

登录成功、失败后的处理,

[java] view plain copy

 

1. public void userLoginSuccess(User user);  
2. public void userLoginFail(String result);

显示提示信息, 

[java] view plain copy
 
1. public  void showMessage(String msg);

综上,该View的接口有如下这些方法,

[java] view plain copy

1. public interface IUserLoginView {  
2.   
3. public void showProgressDialog();  
4. public void stopProgressDialog();  
5.   
6. public void userLoginSuccess(User user);  
7. public void userLoginFail(String result);  
8.   
9. public  void showMessage(String msg);  
10.   
11. }

下面就是View的实现类,即Activity,此处是LoginActivity,

[java] view plain copy

1. public class LoginActivity extends Activity implements IUserLoginView {  
2.   
3. private EditText etUserName;  
4. private EditText etPassWord;  
5. private Button btnLogin;  
6.   
7. private UserLoginPresenterImpl userLoginPresenter;  
8. private ProgressDialog progressDialog;  
9.   
10. @Override  
11. protected void onCreate(Bundle savedInstanceState) {  
12. super.onCreate(savedInstanceState);  
13.         setContentView(R.layout.login_activity);  
14.         etUserName = (EditText) findViewById(R.id.et_userName);  
15.         etPassWord = (EditText) findViewById(R.id.et_passWord);  
16.         btnLogin = (Button) findViewById(R.id.btn_login);  
17.   
18.   
19. new UserLoginPresenterImpl(this);  
20. new View.OnClickListener() {  
21. @Override  
22. public void onClick(View v) {  
23.                 userLoginPresenter.login(etUserName.getText().toString(), etPassWord.getText().toString());  
24.             }  
25.         });  
26.     }  
27.   
28. @Override  
29. public void showProgressDialog() {  
30. if (progressDialog == null) {  
31. new ProgressDialog(this);  
32. "提示");  
33. "正在登录中...");  
34.         }  
35.         progressDialog.show();  
36.     }  
37.   
38. @Override  
39. public void stopProgressDialog() {  
40. if (progressDialog != null) {  
41.             progressDialog.dismiss();  
42.         }  
43.     }  
44.   
45. @Override  
46. public void userLoginSuccess(User user) {  
47. "登录成功!");  
48.         stopProgressDialog();  
49. final StringBuffer sb = new StringBuffer();  
50. "姓名:" + user.getName() + ", 年龄" + user.getAge() + ", 电话" + user.getMobile()).append("\n");  
51.         showMessage(sb.toString());  
52.     }  
53.   
54. @Override  
55. public void userLoginFail(String result) {  
56.         stopProgressDialog();  
57.         showMessage(result);  
58.     }  
59.   
60. @Override  
61. public void showMessage(String msg) {  
62. this, msg);  
63.     }  
64. }

点击按钮时,调用Presenter的登录方法。可以看出LoginActivity的代码是比较整洁,简单的。最后再看看Presenter的实现。(更新UI记得要在主线程中哦!即使是显示Toast,也要在主线程中显示,否则会出错哦!)

4.Presenter层,

实现Presenter功能,可以定义一个接口,也可以定义一个类。Presenter要实现的功能是直接调用Model中实现方法,并且要向View提供方法。(这里我先定义了一个接口,然后去实现该接口),

[java] view plain copy

1. public interface IUserLoginPresenter {  
2.   
3. public void login(String userName, String passWord);  
4. }

接着实现该接口,

[java] view plain copy

1. public class UserLoginPresenterImpl implements IUserLoginPresenter {  
2.   
3. private UserLoginModelImpl userLoginModel;  
4. private IUserLoginView mIUserLoginView;  
5. private Handler myHandler = new Handler(Looper.getMainLooper());  
6.   
7.   
8. public UserLoginPresenterImpl(IUserLoginView userLoginView) {  
9. this.mIUserLoginView = userLoginView;  
10. new UserLoginModelImpl();  
11.     }  
12.   
13. @Override  
14. public void login(String userName, String passWord) {  
15. if (TextUtils.isEmpty(userName)) {  
16. "用户名不能为空!");  
17. return;  
18.         }  
19. if (TextUtils.isEmpty(passWord)) {  
20. "密码不能为空!");  
21. return;  
22.         }  
23.         mIUserLoginView.showProgressDialog();  
24. new CallBackListener<User>() {  
25.   
26. @Override  
27. public void success(String result, final User user) {  
28. if (!TextUtils.isEmpty(result)) {  
29. new Runnable() {  
30. @Override  
31. public void run() {  
32.                             mIUserLoginView.userLoginSuccess(user);  
33.                         }  
34.                     });  
35.                 }  
36.             }  
37.   
38. @Override  
39. public void fail(final String result) {  
40. if (!TextUtils.isEmpty(result)) {  
41. new Runnable() {  
42. @Override  
43. public void run() {  
44.                             mIUserLoginView.userLoginFail(result);  
45.                         }  
46.                     });  
47.                 }  
48.             }  
49.         });  
50.     }  
51. }

该类调用了Model层提供的登录方法。

好了,以上就是使用MVP模式实现一个登陆功能。下面运行程序看看效果。

首先这张截图是数据库中的表中的数据,有两条数据,

android mvvm 界面跳转 android mvc模式_android mvvm 界面跳转_05

下面是App的运行截图,

android mvvm 界面跳转 android mvc模式_android mvvm 界面跳转_06

  

android mvvm 界面跳转 android mvc模式_java_07

这两种截图分别是,密码正确和密码错误的截图。

三、MVP模式小结。

 

在MVP模式里通常包含4个要素:

(1) View :负责绘制UI元素、与用户进行交互(在Android中体现为Activity);

(2) View interface :需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试;

(3) Model :负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合);

(4) Presenter :作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。

   至此,以上就是有关MVP模式的一点浅谈,本文参考了鸿洋的MVP博客。目前也有一些观点是,Activity是一个上帝类,其实不适合作为View,所以有些MVP方案将Activity作为Presenter,最主要在于他的生命周期牵扯太多逻辑处理业务,这些由Presenter负责的话情况可以改善很多。所以,MVP模式的实现方式是多样的,在此也呼吁更多人来关注该模式!

PS:推荐一个Android Studio插件,方便快捷使用MVP模式开发,插件名为MVPHelper,直接可以在Android Studio插件管理中搜索,当然也可以先下载,然后安装。