最近冷静了一段时间,复习复习以前学的东西。再加上阴阳师一直抽不到SSR,因此打副本的时候想了想毕设项目架构该怎么办。php
以前看不少开源软件实现都是各类 MVP ,看起来很高大上,不过说实话,很早就了解 MVP 了,但一直很抗拒去学习,由于以为模式或者架构类的东西属于一种思想,并非固定的写法,而学习思想以前,必需要学会在引进这种思想以前是如何处理这些问题的。html
也就是说,在学 MVP 以前,我得弄明白为啥会提出 MVP ,由于在 MVP 提出以前都是用 MVC 去处理的,因此我得学会 MVC ,当我能熟练的使用 MVC 的时候,再去学习 MVP ,这样能很清楚的明白二者之间的区别或者各自的优缺点,我的以为这样学起来仍是比较好的,而不是盲目跟风,包括如今不少博客提到的 React Native 、 Dagger2 等等都是同样的道理。android
如今我也来简单聊一聊我本身所理解的 MVP ,不过只能算个入门吧。git
关于 MVP 你们或多或少都知道一点,网上关于 MVP 的教程也不少,不过优质的就太少了。入门我只看了两篇文章(泡网+鸿洋),文末都会有连接。github
先上一个经典的图:bash
先来看一下 MVP 与 MVC 差异在哪?简单一眼扫过,就是 C 和 P 的差异。架构
一、先看 Cide
C 就是 Controller,控制器。负责从 View 读取数据,控制用户输入,并向 Model 发送数据。简单来讲,就是起到一个沟通的做用,能很大程度上的解决 Model 和 View 的耦合问题。单元测试
换句话说就是,它是一个 Model 与 View 之间的桥梁,让 Model 和 View 之间再也不牢牢关联。学习
好比 View 接收到了用户输入数据,先交给 Controller ,Controller 再转交给 Model ,反之亦然。
这就像小明喜欢隔壁班小红,小明写了一封情书须要经过隔壁班小王,才能交给小红。
可是注意,我只是说能很大程度上解决,并不能完全解决,也就是说小明若是发现了隔壁小王有问题,他仍然能够选择直接把情书交给小红。
二、再看 P
P 就是 Presenter,我翻译成主持者。跟 C 相似,仍然是负责 View 和 Model 之间的沟通。可是它完全让 View 和 Model 不能直接沟通。若是想要沟通,就必须经过这个主持者来主持它们两个应该干啥。
好比 View 接收到了用户输入数据,不能直接给 Model ,要交给 Presenter ,Presenter 再转交给 Model ,反之亦然。
这就像我给主席寄了一个包裹,但这个包裹必须通过重重安检,才能交到主席手上。
这就完全断了我跟主席……哦不对,Model 和 View 之间的联系。
三、简单区别
仅从目前来看, C 和 P 都是为了解放 Model 和 View 之间的联系,只不过 C 是很大程度上解决,但 P 是完全让它们两断了联系。
换成技术术语来讲就是一句话:
C 让 Model 和 View 作到 松散耦合,而 P 直接将它们 解耦。
知道了各自简单的做用,再来更深层次的理解 C 和 P 在各自的 MV+X 中到底分别作了什么?
一、先看 MVC
从下图中咱们能够看到:
二、再看 MVP
从下图中咱们又能看到:
三、主要区别
在 MVC 中:
在 MVP 中:
固然是那个经典的登陆案例,不过这里顺带学下毕设里几个 MD 风格的开源库。先来看一下运行的效果图吧:
好了,动手以前先分析一下。
从上面内容咱们知道,Presenter 是用来 Model 和 View 之间交互的。因此必需要持有它们各自的对象,根据需求通常都是用接口来实现。
而实现 View 层接口的通常都是 Activity (暂且这样认为,后文还须要讨论)。
固然若是想要 Activity 和 Model 进行交互,那么这个 Activity 中还必须有一个 Presenter 的实例,由于须要这个 Presenter 来进行交互嘛!
OK,把上面全部的东西捋一捋,数一数到底须要啥:
虽然看起来东西确实变多了,可是结构看起来仍是很清晰的,扩展起来也比较方便。
按照上面须要的东西,一步一步来:
一、先建一个 Bean
/**
* @author xiarui 16/09/20
* @description Person的Bean类
*/
public class PersonBean {
private String name ;
private String pwd;
//...省略
}
复制代码
二、再创建 Model Interface
针对这个 Bean ,有注册和登陆的功能,这里强行抽取一个 IPersonModel 接口出来,纯属为了展现用,意义不大:
/**
* @author xiarui 16/09/20
* @description IPersonModel接口
* @remark 接口其实没必要实现 只是为了讲解例子强行抽取的方法
*/
public interface IPersonModel {
//注册帐号
boolean onRegister(String name, String pwd);
//登陆帐号
boolean onLogin(String name, String pwd);
}
复制代码
三、其次创建 Model
实现了上一步创建的 Model Interface ,主要是对注册和登陆方法的实现:
**
* @author xiarui 16/09/20
* @description Model类 实现IPersonModel接口
* @remark 接口其实没必要实现 只是为了讲解例子强行实现的
*/
public class PersonModel implements IPersonModel {
//简单的存一下注册的帐号
private Map<String, String> personMap = new HashMap<>();
/**
* 注册帐号 存入集合
*
* @param name 用户名
* @param pwd 密码
* @return true:注册成功,false:注册失败
*/
@Override
public boolean onRegister(String name, String pwd) {
if (!personMap.containsKey(name)) {
personMap.put(name, pwd);
return true;
}
return false;
}
/**
* 登陆帐号
*
* @param name 用户名
* @param pwd 密码
* @return true:登陆成功,false:登陆失败
*/
@Override
public boolean onLogin(String name, String pwd) {
return pwd.equals(personMap.get(name));
}
}
复制代码
四、还须要 View Interface
在这里我设定了五个方法,其中注册/登陆成功与否分别建了两个方法,缘由后文再说:
/**
* @author xiarui 16/09/20
* @description IPersonView接口
*/
public interface IPersonView {
boolean checkInputInfo(); //检查输入的合法性
void onRegisterSucceed(); //注册成功
void onRegisterFaild(); //注册失败
void onLoginSucceed(); //登陆成功
void onLoginFaild(); //登陆失败
}
复制代码
五、最重要的 Presenter
再次强调,Presenter 是用来 Model 和 View 交互的,而它们各自都实现了接口,那咱们只需保证 Presenter 持有这些接口便可:
/**
* @author xiarui 16/09/20
* @description Person的Presenter类
* @remark 必需要传M和V 由于P须要控制M和V
*/
public class PersonPresenter {
private IPersonModel mPersonModel; //Model接口
private IPersonView mPersonView; //View接口
public PersonPresenter(IPersonView mPersonView) {
mPersonModel = new PersonModel();
this.mPersonView = mPersonView;
}
public void registerPerson(String name, String pwd) {
boolean isRegister = mPersonModel.onRegister(name, pwd);
//根据Model中的结果调用不一样的方法进行UI展现
if(isRegister){
mPersonView.onRegisterSucceed();
}else{
mPersonView.onRegisterFaild();
}
}
public void loginPerson(String name, String pwd) {
boolean isLogin = mPersonModel.onLogin(name, pwd);
//根据Model中的结果调用不一样的方法进行UI展现
if (isLogin) {
mPersonView.onLoginSucceed();
}else{
mPersonView.onLoginFaild();
}
}
}
复制代码
六、最后的 View
这里的 View 其实就是实现 IPersonView 接口的 Activity,它必须有一个 Presenter 的实例才能与 Model 交互:
源码有删减,保留核心方法
/**
* @author xiarui 16/09/20
* @description MVP的简单例子
* @remark View 必须持有 Presenter 的实例才能与 Model 交互
*/
public class MainActivity extends AppCompatActivity implements IPersonView, View.OnClickListener {
/*===== 数据相关 =====*/
private PersonPresenter personPersenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView(); //初始化View
initData(); //初始化Data
}
/**
* 初始化Data
*/
private void initData() {
personPersenter = new PersonPresenter(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_main_register:
if (checkInputInfo()) {
personPersenter.registerPerson(inputName, inputPwd);
}
break;
case R.id.bt_main_login:
if (checkInputInfo()) {
personPersenter.loginPerson(inputName, inputPwd);
}
break;
}
}
/*========== IPersonView接口方法 START ==========*/
/**
* 检查输入信息的合法性
*
* @return true:输入合法,false:输入不合法
*/
@Override
public boolean checkInputInfo() {
inputName = nameEText.getText().toString().trim();
inputPwd = pwdEText.getText().toString().trim();
if (inputName.equals("")) {
nameEText.setError("用户名不能为空");
return false;
}
if (inputPwd.equals("")) {
pwdEText.setError("密码不能为空");
return false;
}
return true;
}
@Override
public void onRegisterSucceed() {
showToast("注册成功");
}
@Override
public void onRegisterFaild() {
showToast("用户已存在");
}
@Override
public void onLoginSucceed() {
showToast("登陆成功");
}
@Override
public void onLoginFaild() {
showToast("用户不存在或密码错误");
}
/*========== IPersonView接口方法 END ==========*/
}
复制代码
当完成这些步骤后,一个简单的 MVP 示例就完成了。
这里是一些疑问和解答:
Q: MVP 模式中 View 层是否就是 Activity ?
A: 其实严格意义上来讲,这么说是不对的。虽然本例中确实是 Activity ,可是在真正的项目中,须要考虑 Activity 和 Fragment 的状况,甚至还要考虑一些特定的 View 或者 ViewGroup 。
注:后面我就用 Activity 统一指代 View 了。
Q: 从例子上看,几乎每个 Activity 都对应着 一个 Presenter ,还须要其余的接口,那若是 Activity 不少怎么办?
A: 其实这个问题一直是 MVP 饱受诟病的地方,虽然 MVP 结构很清晰,但确实要增长不少不少的类,因此须要尽可能让接口能适用于多种 View ,但若是实在忍受不了,建议不用 MVP。
Q: 使用 MVP 后感受项目更加臃肿和复杂了怎么办?
A: 历来都没有人说过 MVP 能使得项目简单,只是它会让项目结构更加清晰更加易于扩展而已。就像 RxJava 同样,代码量仍是那么多,可是流程更加清晰了,这就是能让开发者拥护的缘由。
Q: 为何案例中 IPersonView 这个接口将注册登陆成功与否分开成独立方法?
A: 这里确实能够不分开,只要将注册/登陆的结果做为参数便可,可是这样的话,咱们仍然须要在 Activity 中根据结果参数来决定显示的 Toast 内容。
也就是说 View 仍然须要处理一些来自 Model 的逻辑,这样不是太符合 MVP 的意义。因此将判断逻辑放在 Presenter 中处理,View 层只管展现就好了。
包括鸿洋大神的那篇文章中,有一个 View 的方法直接传递了涉及 Model 层的类,显然违背了 MVP 的定义,我以为不是太好(批判了大神,果断逃……)。
Q: Presenter 若是进行耗时操做,但此时对应的 Activity 被杀死,会报空指针么?
A: 其实在这种状况下,已经存在内存泄漏的状况了。但有意思的是,并不会报空指针,具体缘由暂时还不是特别清楚,但好友xiasuhuei321提醒我说,可能回收的时候并无彻底回收,由于系统会认为还存在相关的引用,因此不会空指针。
Q: 那该如何避免内存泄漏这种状况呢?
A: 这个问题我看的时候以为很简单,后来发现这是颇有趣的问题。具体方法有不少,也有不少的开源库专门处理这样的问题。其实解决办法概括起来就是一个 如何让 Presenter 的生命周期跟 Activity 的生命周期保持一致。
我看了不少方法,只以为经过 Loader 的方法来解决是最简单也最有效的方法。可是我尚未完全学完,暂时不班门弄斧,有兴趣能够直接点击下面的连接进行学习:
到此,关于 MVP 的简单入门级知识大概就说完了,虽然网上教程不少不少,但仍是用本身的话去讲清楚比较舒服。固然了, MVP 可远远不止这些,其余的东西学到以后再提吧。
不过就像开头说的那样,这东西就是一个思想,不必死板硬套,再者说了谷歌不是又推出了 MVVM 了么。说到 MVVM 又头疼,感受总有学不完的东西,虽然总比别人慢一步,可是没办法,学技术得冷静。
当别人大张旗鼓的时候,更要谋本身的路,证本身的道。
下面两篇是个人入门教程,写的不错:
下面这个确实对得起标题,真的很详细,主要是一些资源综合,有上下两篇,这里只贴上篇,都颇有价值:
下面这个是我朋友写的,也很详细而清晰,例子也很具备表明性:
哦对了,这是 MD 风格控件的开源库,扔物线大神的:
我的博客:www.iamxiarui.com