转载请注明出处:http://blog.csdn.net/smartbetter/article/details/70853135
随着 UI 创建技术的功能日益增强,UI 层也履行着越来越多的职责。为了更好地细分视图(View)与模型(Model)的功能,让 View 专注于处理数据的可视化以及与用户的交互,同时让 Model 只关系数据的处理,基于 MVC(Model View Controller) 模式的 MVP(Model-View-Presenter) 模式应运而生。目前MVP模式在 Android 应用开发中越来越重要了,大家也都在讨论 MVP 的理论,体系化的资料非常少,所以诞生出了本篇文章。MVP 模式是 MVC 模式的一个演化版本,MVP 模式能有效降低 View 的复杂度,避免业务逻辑被塞进 View 中,MVP 模式会解除 View 与 Model 的耦合,同时又带来了良好的可扩展性、可测试性。
MVP 模式可以分离显示层和逻辑层,它们之间通过接口进行通信,降低耦合。理想化的 MVP 模式可以实现同一份逻辑代码搭配不同的显示界面,因为它们之间并不依赖于具体,而是依赖于抽象,这使得 Presenter 可以运用于任何实现了 View 逻辑接口的 UI,使之具有更广泛的适用性,保证了灵活度。
1.MVP模式的三种角色
角色 | 说明 |
---|---|
Model | 主要做一些数据处理, 网路请求。Presenter 需要通过 Model 层存取、获取数据,Model是封装了数据库 Dao 层或者网络获取数据的角色,或者两种数据获取方式的集合。 |
Presenter | 交互中间人,核心逻辑,处理 View 的业务逻辑,沟通 View 和 Model 的桥梁,Presenter 持有的 View、Model 引用都是抽象,它从 Model 层检索数据后返回给 View 层,使得 View 和 Model 没有耦合,也将业务逻辑从 View 层抽取出来,经常会执行耗时操作。 |
View | 用户界面,Activity、Fragment 或者某个 View 控件,含有一个 Presenter 成员变量,通常 View 层需要实现一个逻辑接口,将 View 上的操作通过会转交给 Presenter 进行实现,最后 Presenter 调用 View 逻辑接口将结果返回给 View 元素。 |
很多缺乏经验的工程师很可能会将各种各样的业务逻辑塞进某个 Activity、Fragment 或者自定义控件中,使得这些组件的单个类型臃肿不堪。MVP 模式可以让 UI 界面和数据分离,职责单一,易于维护。MVP 模式也并不是一个标准化的模式,它有很多实现方式,我们也可以根据自己的需求和自己认为对的方式去修正 MVP 的实现方式,它可以随着 Presenter 的复杂程度变化。只要保证我们是通过 Presenter 将 View 和 Model 解耦合、降低类型复杂度、各个模块可以独立测试、独立变化,这就是正确的方向。
2.实际项目中MVP模式的实现
MVP 模式的实现我们以最简单的用户登录为例进行说明。MVP 模式一个很大特点就是定义接口比较多,代码量变大,下面来看一下实际项目中MVP模式的实现。
首先定义 MVP 基本的三个接口,基本上是固定写法:
public interface IModel {
}
public interface IPresenter<V extends IView> {
/**
* 绑定
* @param view
*/
void attachView(V view);
/**
* 防止内存的泄漏, 清除Presenter与Activity之间的绑定
*/
void detachView();
/**
* @return 获取View
*/
V getIView();
}
public interface IView {
}
然后定义契约类,定义 Presenter、View 用到的一些接口方法:
public class LoginContract {
public interface LoginView {
String getUserName();
String getPwd();
void loginSuccess(LoginBean loginBean); // 登录成功,展示数据
void loginFail(String failMsg);
}
public interface LoginPresenter {
void login(String name, String pwd); // 业务逻辑
}
}
定义 LoginModel 类,主要做一些数据处理, 网路请求:
public class LoginModel extends BaseModel {
private boolean isLogin = false;
public boolean login(@NonNull String username, @NonNull String pwd, @NonNull final DataListener
listener) {
// 此处推荐使用 RxJava + Retrofit 进行网络请求
// 网络请求成功 isLogin = true; listener.successInfo(articles);
// 网络请求失败 isLogin = false; listener.failInfo(str);
return isLogin;
}
// 通过接口产生信息回调
public interface DataListener<T> {
void successInfo(T result);
void failInfo(String result);
}
}
LoginModel 的父类:
public class BaseModel implements IModel {
// 做一些数据处理, 网路请求的初始化操作
}
然后定义交互中间人 LoginPresenter,处理 View 的业务逻辑,它是沟通 View 和 Model 的桥梁,Presenter 持有的 View、Model 引用都是抽象,且经常会执行耗时操作:
public class LoginPresenter extends BasePresenter<LoginActivity> implements
LoginContract.LoginPresenter {
@Override
public void login(String name, String pwd) {
if (!getIView().checkNull()) {
((LoginModel) getiModelMap().get("login")).login(name, pwd, new LoginModel
.DataListener<LoginBean>() {
@Override
public void successInfo(LoginBean result) {
getIView().loginSuccess(result); // 成功
}
@Override
public void failInfo(String result) {
getIView().loginFail(result); // 失败
}
});
}
}
@Override
public HashMap<String, IModel> getiModelMap() {
return loadModelMap(new LoginModel());
}
@Override
public HashMap<String, IModel> loadModelMap(IModel... models) {
HashMap<String, IModel> map = new HashMap<>();
map.put("login", models[0]);
return map;
}
}
Presenter 如果持有 Activity 的强引用,在请求结束之前 Activity 被销毁了,那么由于网络请求还没有返回,导致 Presenter 一直持有 Activity 对象,使得 Activity 无法被回收,此时就容易发生内存泄漏,解决这个问题需要通过弱引用来解决,LoginPresenter 的父类 BasePresenter 如下:
public abstract class BasePresenter<V extends IView> implements IPresenter {
private WeakReference<V> mViewRef; // View接口类型的弱引用
/**
* 建立关联
* @param iview
*/
@Override
public void attachView(IView iview) {
mViewRef = new WeakReference(iview);
}
/**
* 解除关联
*/
@Override
public void detachView() {
if (mViewRef != null) {
mViewRef.clear();
mViewRef = null;
}
}
/**
* 获取View
* @return
*/
@Override
public V getIView() {
return mViewRef.get();
}
/**
* 判断是否与View建立了关联
* @return 建立则返回true
*/
public boolean isViewAttached() {
return mViewRef != null && mViewRef.get() != null;
}
public abstract HashMap<String, IModel> getiModelMap();
/**
* @param models
* @return
* 添加多个model,如有需要
*/
public abstract HashMap<String, IModel> loadModelMap(IModel... models);
}
到这里,Model 和 Presenter 已经都有了,还差 View。Activity 实现需要 IView 和 LoginContract.LoginView 接口,并需要建立与 Presenter 之间的联系,Activity 的业务逻辑都将交给 Presenter 进行处理,处理结果通过 LoginContract.LoginView 接口回调给 Activity 类:
public class LoginActivity extends AppCompatActivity implements IView, LoginContract.LoginView {
LoginPresenter mPresenter;
// 代码省略
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
if (mPresenter != null) {
mPresenter.attachView(this);
}
// 代码省略
// 请求数据,当请求成功后,调用 LoginContract.LoginView 的 loginSuccess 方法将数据传递给 View
mPresenter.login(getUserName(), getPwd());
}
// 代码省略
@Override
public void loginSuccess(LoginBean loginBean) {
// 更新UI
}
@Override
public void loginFail(String failMsg) {
}
/**
* 注意MVP与Activity、Fragment生命周期的处理
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
}
}
}
此时 MVP 的关系此时已经建立成功了。Activity 此时的作用只是做一些 View 的初始化工作,职责单一、功能简单、易于维护。此外,Presenter 与 Model 也依赖于抽象而不是具体,使得 Model 的具体实现可以被轻易地替换。Presenter 与 View 的低耦合使得系统能够应对 UI 的易变性问题,也使得系统的 View 模块变得易于维护。最后 Activity 在实际开发中也会向上抽取出 BaseActivity,比较简单,这里不再给出。
另外基于 MVP 架构,结合 RxJava、Retrofit、OkHttp编写了一个开源框架,易理解,深度解耦,方便迭代。
开源地址:https://github.com/smartbetter/MvpRxJavaRetrofitOkhttp,欢迎Follow、Fork、Star。