概述
关于Android MVP设计模式已经出来很久了,在项目中也普遍使用这个设计模式,但是MVP对于初学者来说的话还是有点难以捉摸的!每次看上去都感觉这样写很不错,很好理解,就是增加了很多类,看完之后自己去写又很难写出来。再讲MVP设计模式之前,我们先来讲一下MVC设计模式。
MVC模式
MVC(模型-视图-控制器)用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互时,无须重新编写业务逻辑,MVC模式结构分为三个部分:
- Model:实体模型,我们针对业务模型,建立的数据结构和相关类,对应着Bean实体类。Model与View无关,但是与业务有关。
- View:一般采用XML文件或者Java代码进行界面的描述,也可以使用JS+HTML的方式作为View。
- Controller:Android的控制器通常在Activity、Fragment或者由它们控制的其他业务类中。
MVC用一句话来概括就是通过Controller来操作Model层的数据,并且返回给View层来展示。如图所示:
看起来像是那么回事,但是MVC也有它的缺点,不然就没我们MVP模式什么事了。
- Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用布局和初始化界面,接受并出来来自用户的请求,并作出相应,随着界面及其逻辑复杂度的不断提升,Activity类的职责不断增加,导致其变得庞大而臃肿。
- Model层和View层相互耦合,不易于开发和维护。
MVP模式
MVP模式是MVC模式的一个演化版本,它的结构同样分为三个部分:
Mode:实体模型,同样对应着Bean实体类主要提供数据的存取的功能,Present需要通过Model层来存储和或者获取数据
从网络,数据库,文件,传感器,第三方等数据源读写数据。
对外部的数据类型进行解析转换为APP内部数据交由上层处理。
对数据的临时存储,管理,协调上层数据请求。View:负责处理用户事件交互和视图部分的展示,在Android中它可以使Activity和Fragment或者是某个View控件
提供UI交互
在presenter的控制下修改UI。
将业务事件交由presenter处理。
注意: View层不存储数据,不与Model层交互。View和Presenter通过接口进行交互 面向接口编程Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。它还从Model中去获取数据然后返回给View,使得Model和View没有耦合。
MVP的核心思想
MVP 把 Activity 中的 UI 逻辑抽象成 View 接口,把业务逻辑抽象成 Presenter 接口,Model 类还是原来的 Model。这就是 MVP 模式,现在这样的话,Activity 的工作的简单了,只用来响应生命周期,其他工作都丢到 Presenter 中去完成。从上图可以看出,Presenter 是 Model 和 View 之间的桥梁,为了让结构变得更加简单,View 并不能直接对 Model 进行操作,这也是 MVP 与 MVC 最大的不同之处。
MVP的优点
- 分离了视图逻辑和业务逻辑,降低了耦合
- Activity 只处理生命周期的任务,代码变得更加简洁
- 视图逻辑和业务逻辑分别抽象到了 View 和 Presenter 的接口中去,提高代码的可阅读性
- Presenter 被抽象成接口,可以有多种具体的实现,所以方便进行单元测试
- 把业务逻辑抽到 Presenter 中去,避免后台线程引用着 Activity 导致 Activity 的资源无法被系统回收从而引起内存泄露和 OOM
使用 MVP,至少需要经历以下步骤:
创建 IPresenter 接口,把所有业务逻辑的接口都放在这里,并创建它的实现PresenterCompl(在这里可以方便地查看业务功能,由于接口可以有多种实现所以也方便写单元测试)创建 IView 接口,把所有视图逻辑的接口都放在这里,其实现类是当前的 Activity/Fragment。Activity 里包含了一个 IPresenter,而 PresenterCompl 里又包含了一个 IView 并且依赖了 Model。Activity 只保留对 IPresenter 的调用,其它工作全部留到 PresenterCompl 中实现。Model 并不是必须有的,但是一定会有 View 和 Presenter。
MVP简单的登陆实例
首先看看我们的项目的结构:
就跟我们上面说的一样将业务逻辑和试图逻辑抽象成接口,然后创建他的实现类,其中IView的实现类是Activity。
(一) Model
首先就和我们上面说的一样,实体类User是要有的。然后至少有一个业务方法Login()用来从网络中读取数据。
public class User {
private String name;
private String pwd;
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
public interface IUserBiz {
void login(String name, String pwd, OnLoginListener onLoginListener);
}
public interface OnLoginListener
{
void loginSuccess(User user);
void loginFailed();
}
public class UserBiz implements IUserBiz {
@Override
public void login(final String name, final String pwd, final OnLoginListener onLoginListener) {
//子线程耗时操作
new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//登录成功
if ("admin".equals(name) && "admin".equals(pwd)) {
User user = new User(name,pwd);
onLoginListener.loginSuccess(user);
} else {
onLoginListener.loginFailed();
}
}
}.start();
}
}
(二) View
我们这里是模拟登陆,有Login和Clear两个按钮,用来登陆和清除账号密码。所以我们需要清楚账号密码的方法,还有ProgressBar的显示与隐藏和对登陆这个操作的结果做出响应的方法。综上所述接口为:
public interface IUserLoginView {
void clearUserName();
void clearPassword();
void showLoading();
void hideLoading();
void loginSuccess(User user);
void loginError();
}
实现类代码:
public class LoginOptimized1Activity extends AppCompatActivity implements IUserLoginView, View.OnClickListener {
private EditText editUser;
private EditText editPass;
private Button btnLogin;
private Button btnClear;
private ProgressBar progressBar;
private ILoginPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initViews();
mPresenter = new LoginPresentCompl(this);
}
private void initViews() {
editUser = (EditText) this.findViewById(R.id.et_login_username);
editPass = (EditText) this.findViewById(R.id.et_login_password);
btnLogin = (Button) this.findViewById(R.id.btn_login_login);
btnClear = (Button) this.findViewById(R.id.btn_login_clear);
progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login_clear:
mPresenter.clear();
break;
case R.id.btn_login_login:
mPresenter.login(editUser.getText().toString(), editPass.getText().toString());
break;
}
}
@Override
public void clearUserName() {
editUser.setText("");
}
@Override
public void clearPassword() {
editPass.setText("");
}
@Override
public void showLoading() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void loginSuccess(User user) {
Toast.makeText(this, "Login Success", Toast.LENGTH_SHORT).show();
}
@Override
public void loginError() {
Toast.makeText(this, "Login Error", Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
(三) Presenter
Presenter是用作Model和View之间交互的桥梁,那么应该有什么方法呢?其实也是主要看该功能有什么操作,比如本例,两个操作:login和clear。
public interface ILoginPresenter {
void login(String name, String pwd);
void clear();
}
实现类:
public class LoginPresentCompl implements ILoginPresenter {
private IUserBiz userBiz;
private IUserLoginView userLoginView;
private Handler mHandler = new Handler(Looper.getMainLooper());
public LoginPresentCompl(IUserLoginView userLoginView) {
this.userLoginView = userLoginView;
this.userBiz = new UserBiz();
}
@Override
public void login(String name, String pwd) {
userLoginView.showLoading();
userBiz.login(name, pwd, new OnLoginListener() {
@Override
public void loginSuccess(final User user) {
//需要在UI线程执行
mHandler.post(new Runnable() {
@Override
public void run() {
userLoginView.hideLoading();
userLoginView.loginSuccess(user);
}
});
}
@Override
public void loginFailed() {
mHandler.post(new Runnable() {
@Override
public void run() {
userLoginView.hideLoading();
userLoginView.loginError();
}
});
}
});
}
@Override
public void clear() {
userLoginView.clearUserName();
userLoginView.clearPassword();
}
}
解决OOM和内存泄漏
好了以上基本上就是一个简单的MVP模拟登陆的例子了。但是以上的代码还有一个问题,就是可能会造成内存泄漏,上面我们说MVP的优点的时候也说了。MVP能避免OOM和内存泄漏。接下来我们就利用弱引用结合Activity的生命周期来解决这个问题。
首先我们定义一个Present的基类,让其他presenter继承此类。
public class BasePresenter<V> {
protected Reference<V> mViewRef;
public void attachView(V view) {
mViewRef = new WeakReference<V>(view);
}
public V getView() {
if (mViewRef == null) {
return null;
}
return mViewRef.get();
}
public boolean isViewAttached() {
return mViewRef != null && mViewRef.get() != null;
}
public void detachView() {
if (mViewRef != null) {
mViewRef.clear();
mViewRef = null;
}
}
}
这是一个泛型类,参数为V为要引用的View.
attachView()方法的作用是创建View的虚引用,此方法放在View初始化过程中,例如onCreate()方法中
detachView()方法的作用是销毁View的虚引用,此方法放在View销毁化过程中,例如onDestroy()方法中
getView()放用来获取View
然后我们定义一个View的基类,让其他的View继承此类。
public abstract class MVPBaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
protected T mPresenter;
@Override
protected void onCreate(Bundle arg) {
super.onCreate(arg);
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
protected abstract T createPresenter();
}
这也是一个泛型基类,第一个泛型参数V:代表当前处理的View,第二个泛型参数T:代表当前使用的Presenter,要求此presenter必须继承至 BasePresenter.要求每个Activity必须继承至此类。在onCreate()和onDestroy()中的处理逻辑已经在讲解 Prestener 时候已经说过。createPresenter() 用来创建presenter实例,便于在View中通过其调用presenter的方法,此方法是abstract的,这就要求MVPBaseActivity子类必须实现此方法。
最后讲到这里基本就结束了。其他的有什么问题的地方可以去查看本文的源码去解决。
写在最后(参考文章)
https://blog.csdn.net/lmj623565791/article/details/46596109
http://kaedea.com/2015/10/11/android-mvp-pattern/
https://blog.csdn.net/ShuSheng0007/article/details/77938378
https://www.jianshu.com/p/9a6845b26856