美文网首页
Android学习笔记之MVP框架模式

Android学习笔记之MVP框架模式

作者: sssssss_ | 来源:发表于2020-05-28 00:18 被阅读0次

    前言

    在学习了 MVC 架构之后,发现 Activity 和 Fragment 和 XML 界面的开发就是典型的 MVC 架构模式,在 Activity 中不仅要处理 UI 操作,还要处理请求数据的操作。

    然而在我接触到的开发项目中,看到一个 Activity 中的代码行数接近上千行是常态。在修改的过程中经常要在类里面不断的翻上翻下的,修改起来也非常费劲。

    这样就导致了 MVC 架构模式耦合度太高、职责不明确,不易于维护的原因,这次就来学习 MVC 的演变出来的 MVP 架构模式。

    MVC 的工作原理: MVC 即 Model View Controller,简单来说就是通过 Controller 的控制去操作 Model 层的数据,并且返回给 View 层展示。当用户触发事件的时候,View 层会发送指令到 Controller 层,接着 Controller 去通知 Model 层更新数据,Model 层更新完数据以后直接显示在 View 层上,这就是 MVC 的工作原理。

    MVP是什么

    MVP 的全称是 Model-View-Presenter,MVP 是 MVC 的一种演进版本,将 MVC 中的 Controller 改为了 Presenter,View 通过接口与 Presenter 进行交互,有效降低 View(Activity / Fragment) 的复杂性,避免业务逻辑被塞进 View 中,使得 View 变得臃肿。

    另外,MVP 模式会解除 View 与 Model 的耦合,同时又带来了良好的可扩展性、可测试性,保证了系统的整洁性、灵活性。虽然在简单的应用中可能会因为各种接口变得复杂,但在稍有规模的应用中,依然能保持结构的整洁和灵活。

    MVP的结构

    • Model 主要是提供数据的存取功能,Presenter 需要通过 Model 层存储和获取数据,Model 就像一个数据仓库。Model 是管理数据库和网络获取数据的角色。

    • View 一般是指 Activity 和 Fragmen t等,它含有一个 Presenter 成员变量。通常 View 需要实现一个逻辑接口,将 View 上的操作转交给 Presenter 进行实现,最后 Presenter 调用 View 的逻辑接口将结果返回给 View 元素。

    • Presenter 作为 View 与 Model 交互的中间纽带,处理与用户交互的负责逻辑。它从 Model 层检索数据后,返回给 View 层,使得 View 和 Model 之间没有耦合,也将业务逻辑从View角色上抽离出来。

    mvp.jpg

    在通常开发中,View是指 Activity / Fragment,不过,Presenter 和 Activity 通过定义一个 view 接口进行关联,而 Presenter 和 Model 是通过 Callback 接口进行关联。

    • View接口:显示提示框、数据更新;
    • Callback接口:请求数据时反馈状态(成功、失败和异常等等);
    mvp.png

    MVP的优缺点

    优点

    1. 分离了视图逻辑和业务逻辑,降低了耦合

    2. Activity 只处理生命周期的任务,代码变得更加简洁

    3. 视图逻辑和业务逻辑分别抽象到了 View 和 Presenter 的接口中去,提高代码的可阅读性

    4. Presenter 被抽象成接口,可以有多种具体的实现,所以方便进行单元测试

    5. 把业务逻辑抽到 Presenter 中去,避免后台线程引用着 Activity 导致 Activity 的资源无法被系统回收从而引起内存泄露

    6. Presenter 代码可复用,一个 Presenter 可以用于多个 View,而不需要更改 Presenter 的逻辑。

    缺点

    1. Presenter 中除了应用逻辑以外,还有大量的 View->Model,Model->View 的手动同步逻辑,造成 Presenter 比较笨重,维护起来会比较困难。

    2. 由于对视图的渲染放在了 Presenter 中,所以视图和 Presenter 的交互会过于频繁。

    3. 如果 Presenter 过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么 Presenter 也需要变更了。

    4. 额外的代码复杂度及学习成本。

    MVP 和 MVC 的区别

    • MVP 是基于 MVC 模式上演变过来,与 MVC 有一定的相似性,Controller 和 Presenter 负责逻辑的处理,Model 提供数据,View 负责显示。

    • 在 MVC 中,当 Model 被 Controller 更新后,Model 会直接通知 View 并更新显示。而在 MVP 中,Model 与 View 不存在直接关系,这两者之间间隔着的是 Presenter 层,其负责调控 View 与 Model 之间的间接交互,这也是 MVP 和 MVC 两者之间最大的区别。

    • 此外 Presenter 与 View、Model 的交互使用接口定义交互操作可以降低耦合、简化代码。

    MVP 与 Activity、Fragment 的生命周期

    • 问题原因:由于 Model 在进行异步操作,例如请求网络数据,Presenter 持有 Activity 的强引用,如果在请求结束之前使得 Activity 被销毁了,那么由于网络请求还没有返回,导致 Presenter 持有 Activity 对象,使得 Activity 无法被回收,此时就会发生内存泄露。(也许应用中出现一次两次内存泄漏不会造成多大的影响,但应用在长时间使用后,若这些占据系统的大量内存的 Activity 得不到 GC 回收的话,最终会导致 OOM 的出现,就会直接 Crash 应用。)
    • 解决办法:通过弱引用和 Activity、Fragment 的生命周期来绑定/解绑 View 解决这个问题,建立 BasePresenter,是一个泛型类 ,泛型类型为 View 角色要实现的接口类型 。
    • 好处:Presenter 不需要在构造函数中传入 View 对象,而是在 View 中自由地通过Presenter 的 attachView 方法和 detachView 方法绑定和解绑 View 对象,除了 attachViewdetachView,我们还可以另外声明 onResume 和 onStop 方法。

    Model层的单独优化

    前面讲了 View 和 Presenter 两个层次,而 Model 层比较特殊,相对比较独立的存在,帮上层拿数据,这是因为 MVP 模式的理念就是让业务逻辑相互独立。但如果每个网络请求也独立成单个 Model 的话,代码操作起来也会非常麻烦,比如:

    1. 无法对所有 Model 统一管理;
    2. 每个 Model 对外提供的获取数据方法不一样,上层请求数据没有规范;
    3. 代码冗余,重复性代码多;
    4. 对已存在的 Model 进行修改繁琐;

    那么就需要对 Model 进行封装优化,使得 Model 层变成一个庞大且独立单一的模块,请求方式规范化,管理直观化:

    1. 数据请求能够单独编写和测试,无需配合上层界面测试;
    2. 统一以 DataModel 类作为数据请求层的入口,通过反射机制获取对应的 Model;
    3. 无缝切换不同的数据源(网络请求库、缓存、数据库);

    MVP结构图

    MVP模式详细结构图

    示例代码

    • IBaseView:View接口中定义Activity的UI逻辑

      public interface IBaseView {
          void showLoading();
          void hideLoading();
          void showToast(String msg);
          void showErr();
          Context getContext();
      }
      
    • BaseActivity:主要是负责实现 BaseView 中通用的UI逻辑方法,如此这些通用的方法就不用每个Activity都要去实现一遍了。

      public abstract class BaseActivity extends AppCompatActivity implements IBaseView {
          // 加载条
          private ProgressDialog mProgressDialog;
          // 获取Presenter实例,子类实现
          public abstract BasePresenter getPresenter();
          // 初始化Presenter的实例,子类实现
          public abstract void initPresenter();
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              // 初始化Presenter
              initPresenter();
              if (getPresenter()!=null){
                  getPresenter().attachView(this);
              }
              // 初始化进度条
              mProgressDialog = new ProgressDialog(this);
              mProgressDialog.setCancelable(false);
          }
          @Override
          public void showLoading() {
              if (!mProgressDialog.isShowing()) {
                  mProgressDialog.show();
              }
          }
          @Override
          public void hideLoading() {
              if (mProgressDialog.isShowing()) {
                  mProgressDialog.dismiss();
              }
          }
          @Override
          public void showToast(String msg) {
              Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
          }
          @Override
          public void showErr() {
              showToast("err....");
          }
          @Override
          public Context getContext() {
              return BaseActivity.this;
          }
          @Override
          protected void onDestroy() {
              super.onDestroy();
              if (getPresenter() != null){
                  getPresenter().detachView();
              }
          }
      }
      
    • MainActivity:继承了BaseActivity抽象类,实现了getPresenter和initPresenter完成P层绑定。实现IMvpView接口中的showData达到UI更新操作。

      public class Main4Activity extends BaseActivity implements IMvpView {
          TextView mTextView;
          MvpPresenter mvpPresenter;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main3);
              mTextView = findViewById(R.id.text);
          }
          @Override
          public BasePresenter getPresenter() {
              return mvpPresenter;
          }
          @Override
          public void initPresenter() {
              mvpPresenter = new MvpPresenter(); //初始化Presenter
              // mvpPresenter.attachView(this);// attachView放在抽象父类中
          }
          // 按钮1
          public void getData(View view) {
              mvpPresenter.getData("normal");
          }
          // 按钮2
          public void getDataForFailure(View view) {
              mvpPresenter.getData("failure");
          }
          // 按钮3
          public void getDataForError(View view) {
              mvpPresenter.getData("error");
          }
          @Override
          public void showData(String data) {
              mTextView.setText(data);
          }
      }
      
    • IMvpView:IMvpView是Activity与Presenter层的中间层,它的作用是根据具体业务的需要,为Presenter提供调用Activity中具体UI逻辑操作的方法。

      /**
       * View接口是Activity与Presenter层的中间层,它的作用是根据具体业务的需要,
       * 为Presenter提供调用Activity中具体UI逻辑操作的方法。
       */
      public interface IMvpView extends IBaseView {
          /**
           * 当数据请求成功后,调用此接口显示数据
           * @param data 数据源
           */
          void showData(String data);
      }
      
    • BasePresenter:处理View的生命周期;

      public class BasePresenter<V extends IBaseView> {
          // 绑定的view
          private V mView;
          // 绑定view,一般在初始化中调用该方法
          public void attachView(V mvpView) {
              this.mView = mvpView;
          }
          //  断开view,一般在onDestroy中调用
          public void detachView() {
              this.mView = null;
          }
          // 是否与View建立连接,每次请求业务之前都要判断
          public boolean isViewAttached() {
              return mView != null;
          }
          // 获取当前连接的view
          public V getmView() {
              return mView;
          }
      }
      
    • MvpPresenter:该类是具体的逻辑业务处理类,负责请求数据,并对数据请求的反馈进行处理。

      public class MvpPresenter extends BasePresenter<IMvpView> {
          /**
           * 获取网络数据
           * @param userId
           */
          public void getData(String userId) {
              if (!isViewAttached()) {
                  return;
              }
              //显示进度条
              getmView().showLoading();
              DataModel.request1(UserDataModel.class)
                      .params(userId).execute(new MvpCallback() {
                  @Override
                  public void onSuccess(Object data) {
                      //调用view接口显示数据
                      if (isViewAttached()) {
                          getmView().showData((String) data);
                      }
                  }
                  @Override
                  public void onFailure(String msg) {
                      if (isViewAttached()) {
                          getmView().showToast(msg);
                      }
                  }
                  @Override
                  public void onError() {
                      if (isViewAttached()) {
                          getmView().showErr();
                      }
                  }
                  @Override
                  public void onComplete() {
                      if (isViewAttached()) {
                          getmView().hideLoading();
                      }
                  }
              });
          }
      }
      
    • MvpCallback

      /**
       * Callback 接口是Model层给Presenter层反馈请求信息的传递载体,
       * 所以需要在Callback中定义数据请求的各种反馈状态
       * 除了请求成功的回调方法,其他的像请求失败,请求出错这些方法我们做的事几乎是一样的。
       * 后期可以构建一个通用的BaseCallBack去处理请求的异常情况
       */
      public interface MvpCallback<T> {
          /**
           * 数据请求成功
           * @param data 请求到的数据
           */
          void onSuccess(T data);
          /**
           * 网络返回数据失败,请求成功
           * @param msg 无法正常返回数据的原因
           */
          void onFailure(String msg);
          /**
           * 请求数据失败、无法联网、缺少权限、内存泄露等等原因
           */
          void onError();
          /**
           * 无论执行上面那个方法,最后都会执行此方法,可以在这里设置隐藏加载框
           */
          void onComplete();
      }
      
    • DataModel:通过反射机制获取对应的model

      public class DataModel {
          public static <T extends BaseModel> T request1(Class<T> cls) {
              // 声明一个空的BaseModel
              T model = null;
              try {
                  //利用反射机制获得对应Model对象的引用
                  model = (T) cls.newInstance();
              } catch (InstantiationException e) {
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              }
              return model;
          }
      }
      
    • BaseModel:定义了对外的请求数据规则,包括设置参数的方法和设置Callback的方法,还可以定义一些通用的数据请求方法,比如说网络请求的Get和Post方法。
      public abstract class BaseModel<T> {
          //数据请求参数
          protected String[] mParams;
          /**
           * 设置数据请求参数
           * @param args 参数数组
           */
          public BaseModel params(String... args) {
              mParams = args;
              return this;
          }
          // 添加Callback并执行数据请求
          // 具体的数据请求由子类实现
          public abstract void execute(MvpCallback<T> callback);
          // 执行Get网络请求,此类看需求由自己选择写与不写
          protected void requestGetAPI(String url, MvpCallback<T> callback) {
              //这里写具体的网络请求
          }
          // 执行Post网络请求,此类看需求由自己选择写与不写
          protected void requestPostAPI(String url, Map params,              MvpCallback<T> callback) {
              //这里写具体的网络请求
          }
      }
      
    • getNetData:实现具体的Model请求时必须要重写BaseModel的抽象方法execute

      public class UserDataModel extends BaseModel<String> {
          @Override
          public void execute(final MvpCallback<String> callback) {
              new Handler().postDelayed(new Runnable() {
                  @Override
                  public void run() {
                      // mParams 是从父类得到的请求参数
                      switch (mParams[0]){
                          case "normal":
                              callback.onSuccess("根据参数"+mParams[0]+"的请求网络数据成功");
                              break;
                          case "failure":
                              callback.onFailure("请求失败:参数有误");
                              break;
                          case "error":
                              callback.onError();
                              break;
                      }
                      callback.onComplete();
                  }
              },2000);
          }
      }
      

    效果图

    MVP效果图

    参考文章

    相关文章

      网友评论

          本文标题:Android学习笔记之MVP框架模式

          本文链接:https://www.haomeiwen.com/subject/aqwwmhtx.html