从我们的项目到JD,处处可见Android MVP 模式的身影。
项目越来越庞大,开发越来越多,MVP的优势越来越明显。
今天我们通过一个简单的登录Demo,初步学会MVP的使用。
首先,什么是MVP?
MVP模式是MVC模式在Android上的一种变体,要介绍MVP就得先介绍MVC。
在MVC模式中,Activity应该是属于View这一层。而实质上,它既承担了View,同时也包含一些Controller的东西。因为耦合度高,因此不利于我们的开发与维护。
所以我们把Activity的View和Controller抽离出来就变成了View和Presenter,这就是MVP模式。
MVP模式的核心思想:
这样,Activity的工作简单了,只用来响应生命周期,其他工作都由Presenter去完成。从上图可以看出,Presenter是Model和View之间的桥梁,为了让结构变得更加简单,View并不能直接对Model进行操作,这也是MVP与MVC最大的不同之处。MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model不变。
MVP的优势:
分离了视图逻辑和业务逻辑,降低了耦合
Activity只处理生命周期的任务,代码变得更加简洁
视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性
Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
把业务逻辑抽到Presenter中去,避免后台线程引用Activity导致Activity的资源无法被系统回收从而引起内存泄露和OOM
使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。
一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格(习惯性上叫做MV模式,少了P),我们可能要先在Activity里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,咦,测试代码已经删掉了!好吧重新写吧……MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成PresenterTest吧。
Android APP 发生OOM的最大原因就是出现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是Activity泄露(Activity Leak)(另一个原因是Bitmap泄露(Bitmap Leak))。Activity是有生命周期的,用户随时可能切换Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免OOM。采用传统的MV模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。采用MVP模式,只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。
说了那么多,开始实战,先看战略地图(UML图,PPT纯手绘):
创建IPresenter接口,把所有业务逻辑的接口都放在这里,并创建它的实现PresenterImpl(在这里可以方便地查看业务功能,由于接口可以有多种实现所以也方便写单元测试)
创建IView接口,把所有视图逻辑的接口都放在这里,其实现类是当前的Activity/Fragment
由UML图可以看出,Activity里包含了一个IPresenter,而PresenterImpl里又包含了一个IView并且依赖了Model。Activity里只保留对IPresenter的调用,其它工作全部留到PresenterImpl中实现
Model并不是必须有的,但是一定会有View和Presenter
通过上面的介绍,MVP的主要特点就是把Activity里的许多逻辑都抽离到View和Presenter接口中去,并由具体的实现类来完成。这种写法多了许多IView和IPresenter的接口,在某种程度上加大了开发的工作量,刚开始使用MVP的小伙伴可能会觉得这种写法比较别扭,而且难以记住。其实一开始想太多也没有什么卵用,只要在具体项目中多写几次,就能熟悉MVP模式的写法,理解TA的意图,以及享受其带来的好处。
PS:
注意一下UML图中的关系,相互依赖(虚线箭头),相互关联(实线箭头)。 依赖表现为函数中的参数(use a),是类与类之间的连接,表示一个类依赖于另一个类的定义,其中一个类的变化将影响另外一个类。例如如果A依赖于B,则B体现为局部变量,方法的参数、或静态方法的调用。如电视(TV)依赖于频道(channel)常见的依赖关系如下:
(1)类B以参数的形式传入类A的方法。
(2)类B以局部变量的形式存在于类A的方法中。
(3)类A调用类B的静态方法。
关联关系包括单项关联,双向关联,组合和聚合
分析依赖和关联的关系
1.依赖是一种弱关联
2.依赖关系表现在局部变量,方法的参数,以及对静态方法的调用
3.关联关系中,体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。
4.依赖关系中,可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。
开始Coding,一个登录小Demo。
先看一下项目结构:
从V开始,先看接口ILoginView,定义实现类需要实现的方法,清除文本和登录结果展示。
package com.example.quan.quanstudy.MVP_login.view;
/**
* Created by xingquan.he on 2017/3/7.
*/
public interface ILoginView {
public void onClearText();
public void onLoginResult(Boolean result, int code);
}
package com.example.quan.quanstudy.MVP_login.view;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.example.quan.quanstudy.MVP_login.presenter.ILoginPresenter;
import com.example.quan.quanstudy.MVP_login.presenter.LoginPresenterImpl;
import com.example.quan.quanstudy.R;
/**
* Created by xingquan.he on 2017/3/7.
*/
public class LoginActivity extends Activity implements ILoginView, View.OnClickListener {
private EditText mEditUser;
private EditText mEditPass;
private Button mBtnLogin;
private Button mBtnClear;
private ProgressBar mProgressBarLogin;
private ILoginPresenter mLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp_login);
initView();
initOnClickListener();
mLoginPresenter = new LoginPresenterImpl(this);
mProgressBarLogin.setVisibility(View.INVISIBLE);
}
private void initView() {
mEditUser = (EditText) this.findViewById(R.id.et_login_username);
mEditPass = (EditText) this.findViewById(R.id.et_login_password);
mBtnLogin = (Button) this.findViewById(R.id.btn_login_login);
mBtnClear = (Button) this.findViewById(R.id.btn_login_clear);
mProgressBarLogin = (ProgressBar) this.findViewById(R.id.progressbar_login);
}
private void initOnClickListener() {
mBtnLogin.setOnClickListener(this);
mBtnClear.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login_clear:
mLoginPresenter.clear();
break;
case R.id.btn_login_login:
mProgressBarLogin.setVisibility(View.VISIBLE);
mBtnLogin.setEnabled(false);
mBtnClear.setEnabled(false);
mLoginPresenter.doLogin(mEditUser.getText().toString(), mEditPass.getText().toString());
break;
}
}
@Override
public void onClearText() {
mEditUser.setText("");
mEditPass.setText("");
}
@Override
public void onLoginResult(Boolean result, int code) {
mProgressBarLogin.setVisibility(View.INVISIBLE);
mBtnLogin.setEnabled(true);
mBtnClear.setEnabled(true);
if (result) {
Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
package com.example.quan.quanstudy.MVP_login.presenter;
/**
* Created by xingquan.he on 2017/3/7.
*/
public interface ILoginPresenter {
void clear();
void doLogin(String name, String passwd);
}
private ILoginView mLoginView;
private User mUser;
private Handler mHandler;//模拟登录耗时操作
public LoginPresenterImpl(ILoginView iLoginView) {
this.mLoginView = iLoginView;
mUser = new User("xq.he","mvp");
mHandler = new Handler(Looper.getMainLooper());
}
@Override
public void clear() {
mLoginView.onClearText();
}
@Override
public void doLogin(String name, String password) {
Boolean isLoginSuccess = true;
final int code = mUser.checkUserValidity(name,password);
if (code != 0) {
isLoginSuccess = false;
}
final Boolean result = isLoginSuccess;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mLoginView.onLoginResult(result, code);
}
}, 4000);
}
package com.example.quan.quanstudy.MVP_login.model;
/**
* Created by xingquan.he on 2017/3/7.
*/
public class User {
private String mName;
private String mPassword;
public User(String name, String password) {
this.mName = name;
this.mPassword = password;
}
public String getName() {
return mName;
}
public String getPassword() {
return mPassword;
}
public int checkUserValidity(String name, String password) {
if ( name == null || password == null || !name.equals(getName()) || !password.equals(getPassword()) ){
return -1;
}
return 0;
}
}
完成后界面如图,十分简单的一个登录Demo,关键是领会MVP。
原创不易,转载请注明出处哈。
权兴权意
代码可以更优雅~
http://blog.csdn.net/hxqneuq2012/article/details/60870945
项目源代码,欢迎提建议(star)。
https://github.com/HXQWill/QuanStudy/tree/master/app/src/main/java/com/example/quan/quanstudy/MVP_login
参考:
http://kaedea.com/2015/10/11/android-mvp-pattern/
UML类图实例(重要的基础课)
http://blog.csdn.net/xhf55555/article/details/6896316/