Android MVP模式專案實戰

Rayho發表於2018-06-13

一 . 前言

  快大半年沒寫技術文章了,原因是我太特麼懶了(懶癌晚期沒救了╮(╯﹏╰)╭)。這兩星期一直看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?

Android 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傳送門

  效果圖:

Android MVP模式專案實戰

  專案結構:

Android MVP模式專案實戰

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比較適合大型的專案,因為大型專案往往是多人開發,每人負責一模組,專案的結構會更加的清晰,更容易維護,更容易測試。

  • 專案具體使用哪種架構是要多方面考慮的,不能為了模式而使用模式,一定要結合實際。




堅持原創不容易 覺得寫得不錯點個讚唄ヾ(◍°∇°◍)ノ゙


知道你們不會打賞的 我只是裝裝樣子而已٩(๑>◡<๑)۶

Android MVP模式專案實戰

相關文章