Android MVP模式项目实战

1,073 阅读7分钟

一 . 前言

  快大半年没写技术文章了,原因是我太特么懒了(懒癌晚期没救了╮(╯﹏╰)╭)。这两星期一直看mvp等知识,决定亲手撸一篇文章来总结下。好了废话不多说,请看吧,文笔不好请见谅(mmp反正也没人看,哼 ̄へ ̄)。

二 . MVP模式

 1.什么是MVP?

  MVP全称是Model-View-Presenter,它是MVC的演化版本。

  • M(Model): 模型,数据层。业务逻辑和实体类,负责获取数据等业务操作,将数据的处理结果回调给Presenter处理。
  • V(View): 视图,ui显示层。主要是Activity和Fragment等View的ui交互,不与Mode层交互,只在Presenter控制下 进行UI交互。
  • P(Presenter): 逻辑处理层。负责Model与View之间的交互,通过Model层返回的数据实现业务逻辑并且同时控制 View层进行ui交互。

 2.为什么使用MVP?

 从MVP和MVC的对比图看出:

  1. MVP的model和view是隔离的,没有直接交互,而MVC的model和view是直接交互的。
  2. MVP的model和view是通过presenter通信的,而MVC的model和view是直接交互,这样会导致代码的耦合度比较高。典型的例子如activity中既有ui交互也有业务逻辑。如果该界面的业务比较复杂,有可能导致代码量激增。
  3. 此外Presenter与View、Model的交互使用接口定义交互操作可以进一步达到松耦合也可以通过接口更加方便地进行单元测试。

三 . 项目实战(FuckMvp)

  项目概述: 进去一个登录界面,登录成功后进入主界面请求网络获取一个数据然后通过Snackbar显示出来。登录功能只是代码中设置好的账号密码跟输入的账号密码进行校验。主界面请求的数据是通过rxjava+retrofit(已封装的)来请求豆瓣的一个api接口,获取数据成功后通过Snackbar展示。

tips : 有童鞋可能之前没有了解过rxjava和retrofit,不过没关系,在项目这些东西我都是封装好的,有兴趣的可以了解一下。我们的重点是通过项目实战来学习MVP的思想,撤φ(>ω<*)

  Github传送门

  效果图:

  项目结构:

1. 登录功能

  1.1 model层

  根据需求定义功能主要负责请求数据和处理数据并且返回处理结果。

public interface LoginInteractor {

  interface OnLoginFinishedListener{
      //登录成功
      void onSuccess();
      //登录失败
      void onFailed();
  }

  //验证账号信息
  String validateInfo(String phone,String password);

  //登录
  void login(String phone,String password,OnLoginFinishedListener listener);
}

public class LoginInteractorImpl implements LoginInteractor {

  public static final String PHONE_NULL = "手机号码不能为空";

  public static final String PASSWORD_NULL = "密码不能为空";

  public static final String NOT_NULL = "SUCCESS";

  @Override
  public String validateInfo(String phone, String password) {
      if(TextUtils.isEmpty(phone)){
          return "手机号码不能为空";
      }
      if (TextUtils.isEmpty(password)){
          return "密码不能为空";
      }
      return "SUCCESS";
  }

  @Override
  public void login(final String phone, final String password, final OnLoginFinishedListener listener) {
     new Handler().postDelayed(new Runnable() {
         @Override
         public void run() {
              if(phone.equals("18814118201") && password.equals("123456")){
                  listener.onSuccess();
              }else {
                  listener.onFailed();
              }
         }
     },2000);
  }
}

  1.2 view层

  根据需求定义ui交互,由presenter控制逻辑对view进行ui更新。

public interface LoginView {
  /**
   * 显示进度
   */
  void showLoading();

  /**
   * 隐藏进度
   */
  void hideLoading();

  /**
   * 显示Toast
   * @param msg
   */
  void showToast(String msg);

  /**
   * 显示错误信息
   * @param msg
   */
  void showErrorMessage(String msg);

  /**
   * 登录成功跳转主界面
   */
  void toMainActivity();
}

  1.3 presenter层

  Presenter扮演着view和model的中间层的角色。获取model层的数据之后构建view层;也可以收到view层UI上的反馈命令后分发处理逻辑,交给model层做业务操作。它也可以决定View层的各种操作。

public class LoginPresenter {

  LoginView loginView;

  LoginInteractorImpl interactorImpl;

  private String phone;

  private String password;

  private String result;

  public LoginPresenter(LoginView loginView) {
      this.loginView = loginView;
      interactorImpl = new LoginInteractorImpl();
  }

  /**
   * 每次调用view层的方法时 判断view的引用是否为空 这里的view一般指activity或者fragment
   * @return
   */
  public boolean isViewAttached() {
      return loginView != null;
  }

  //在Activity被销毁时 解除presenter对activity的引用 防止报空指针异常
  public void detachView() {
      loginView = null;
  }

  /**
   * 验证账号的信息
   *
   * @param phone
   * @param password
   */
  public void validate(String phone, String password) {
      this.phone = phone;
      this.password = password;
      //得到账号信息验证的结果
      result = interactorImpl.validateInfo(phone, password);
      if (isViewAttached()) {
          loginView.showLoading();
      }
      switch (result) {
          case LoginInteractorImpl.PHONE_NULL:
              if (isViewAttached()) {
                  loginView.hideLoading();
                  loginView.showToast(LoginInteractorImpl.PHONE_NULL);
              }
              break;
          case LoginInteractorImpl.PASSWORD_NULL:
              if (isViewAttached()) {
                  loginView.hideLoading();
                  loginView.showToast(LoginInteractorImpl.PASSWORD_NULL);
              }
              break;
          case LoginInteractorImpl.NOT_NULL:
              login();
              break;
      }
  }

  /**
   * 账号信息都不为空开始登录操作
   */
  private void login() {
      interactorImpl.login(phone, password, new LoginInteractor.OnLoginFinishedListener() {
          @Override
          public void onSuccess() {
              if (isViewAttached()) {
                  loginView.hideLoading();
                  loginView.showToast("登录成功");
                  loginView.toMainActivity();
              }
          }

          @Override
          public void onFailed() {
              if (isViewAttached()) {
                  loginView.hideLoading();
                  loginView.showToast("登录失败 手机号或者密码不对");
              }
          }
      });
  }
}



2. 主界面展示数据功能

tips : 请求大家思考下,如果我们每个界面都按上面这种写法去做,那代码量不是增加很多?而且在实际开发中,开发进度是非常重要的,不可能为了一个简单业务要写那么多接口和类。所以我可以把view层和presenter层以及model层公共的东西给它抽取出来,把它们封装成BaseView和BasePresenter以及CallBack<T>(网络请求结果的回调接口)。根据具体的业务需求,可以自定义view接口增加具体的功能同时来继承BaseView。

  2.1 model层

public interface CallBack<T> {
 /**
  * 数据请求成功
  * @param data 请求到的数据
  */
 void onSuccess(T data);

 /**
  *  使用网络API接口请求方式时,虽然已经请求成功但是由
  *  于{@code msg}的原因无法正常返回数据。
  */
 void onFailure(String msg);

 /**
  * 请求数据失败,指在请求网络API接口请求方式时,出现无法联网、
  * 缺少权限,内存泄露等原因导致无法连接到请求数据源。
  */
 void onError(String error);

 /**
  * 当请求数据结束时,无论请求结果是成功,失败或是抛出异常都会执行此方法给用户做处理,通常做网络
  * 请求时可以在此处隐藏“正在加载”的等待控件
  */
 void onComplete();

  MovieDetail : 实体类主要获取title的数据,具体代码看项目吧,这个类太长了。

  MainModel : 负责网络请求数据,使用rxjava+retrofit(已封装)的方式去获取数据。如果你没接触过rxjava和retrofit的话,你会一脸懵逼的。所以你只需知道onNext方法是代表网络请求成功回调CallBack的onSuccess方法传入一个对象参数,onError方法是代表网络请求失败回调CallBack的onError方法传入一个异常的String。

public class MainModel {
 /**
  * 请求网络获取数据
  *
  * @param callBack
  */
 public void getData(final CallBack<MovieDetail> callBack) {
     new Handler().postDelayed(new Runnable() {
         @Override
         public void run() {
             RetrofitLoader.getInstance()
                     .getMovie()
                     .subscribe(new NetObserver<MovieDetail>() {
                         @Override
                         public void onNext(MovieDetail movieDetail) {
                             callBack.onSuccess(movieDetail);
                         }

                         @Override
                         public void onError(ApiException ex) {
                             callBack.onError(ex.getDisplayMessage());
                         }
                     });
         }
     }, 2000);
 }

  2.2 view层

public interface BaseView {
 /**
  * 隐藏view
  */
 void hideView();

 /**
  * 显示view
  */
 void showView();

 /**
  * 显示进度
  */
 void showLoading();

 /**
  * 隐藏进度
  */
 void hideLoading();

 /**
  * 显示Toast
  * @param msg
  */
 void showToast(String msg);

 /**
  * 显示错误信息
  * @param msg
  */
 void showErrorMessage(String msg);

 /**
  * 跳转activity
  */
 void toActivity();
}

  MainView : 根据需求自定义view接口(增加特定功能)。

public interface MainView extends BaseView {
 void showSnackbar(String content);
}

  2.3 presenter层

public class BasePresenter <V extends BaseView> {
 //继承BaseView子类的对象
 private V mView;

 //绑定activity
 public void attachView(V mView){
     this.mView = mView;
 }

 //取消绑定
 public void detachView(){
     this.mView = null;
 }

 //判断activity的引用是否为空
 public boolean isViewAttached(){
     return mView != null;
 }

 //返回activity引用
 public V getView(){
     return mView;
 }
}
public class MainPresenter extends BasePresenter<MainView>{

 private MainModel mainModel;

 public MainPresenter(){
     mainModel = new MainModel();
 }

 public void getData(){
     if(!isViewAttached()){
         return;
     }
     getView().showLoading();
     mainModel.getData(new CallBack<MovieDetail>() {
         @Override
         public void onSuccess(MovieDetail data) {
             if(data != null){
                 if(isViewAttached()){
                     getView().hideLoading();
                     getView().showSnackbar(data.title);
                 }
             }else {
                 if (isViewAttached()){
                     getView().hideLoading();
                     getView().showToast("请求失败 数据为空");
                 }
             }
         }

         @Override
         public void onFailure(String msg) {}

         @Override
         public void onError(String error) {
             if (isViewAttached()){
                 getView().hideLoading();
                 getView().showToast(error);
             }
         }

         @Override
         public void onComplete() {}
     });
 }
}

四 . 总结

  • MVP与MVC相比,项目的结构更加清晰,耦合度大大的降低,使业务逻辑和视图之间完全分开,更加解耦,提高了维护性,更容易测试。

  • 但是MVP的缺点也很明显,就是代码量明显的增多,presenter层和视图的交互更加频繁(由于视图的渲染是放在presenter层,这导致了视图和presenter之间的关系十分紧密,一旦视图发生改变,presenter也要跟着改变)。

  • 在实际的项目开发过程中,工作量会增大许多。如果项目不是大型复杂的,可以直接上MVC。MVP比较适合大型的项目,因为大型项目往往是多人开发,每人负责一模块,项目的结构会更加的清晰,更容易维护,更容易测试。

  • 项目具体使用哪种架构是要多方面考虑的,不能为了模式而使用模式,一定要结合实际。




坚持原创不容易 觉得写得不错点个赞呗ヾ(◍°∇°◍)ノ゙

知道你们不会打赏的 我只是装装样子而已٩(๑>◡<๑)۶