随着 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 { // 做一些数据处理, 网路请求的初始化操作}123123
然后定义交互中间人 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,比较简单,这里不再给出。