記一次重構:Android實踐從MVC架構到MVP架構

一直以來,想分享MVP的實戰,因為很多項目開始并不是就是mvp架構的,可能是從傳統的mvc結構變遷過來的。今天呈詳給大家分享的這篇從mvc重構到mvp,讓大家既能看到前后的對比,又能突出mvp的優點, 話不多說,看下正文。

一、MVC

1.簡介

MVC是目前大多數企業采用J2EE的結構設計,主要適用于交互式的Web應用。在Android中也有體現和使用,但是存在一定的弊端(下面將講述),于是才有了Android官方推薦的MVP。

在Android的開發過程中,每個層對應如下:     

Model層:對應Java Bean、Database、SharePreference和網絡請求等;

View層:對應xml布局、自定義View或ViewGroup;    

Controller層:對應Activity、Fragment;

2.實踐

對于理論的理解 ,還是需要結合實際。下面我們將前面文章實現的https登錄Demo,使用MVC的方式來進行重構:

項目結構:

View層:

activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context="com.qunar.hotel.controller.LoginActivity">
   <!--登錄輸入用戶名-->
   <com.qunar.hotel.view.LoginInputView
       android:id="@+id/login_intput_username"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
   <!--登錄輸入密碼-->
   <com.qunar.hotel.view.LoginInputView
       android:id="@+id/login_intput_password"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
   <!--登錄按鈕-->
   <Button
       android:id="@+id/login_login_button"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Login" />
   <!--登錄結果文案-->
   <TextView
       android:id="@+id/login_result_text"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
</LinearLayout>

LoginInputView.java

public class LoginInputView extends LinearLayout {
   private TextView title;
   private EditText content;
   public LoginInputView(Context context, AttributeSet attrs) {
       super(context, attrs);
       LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
       layoutInflater.inflate(R.layout.inputview_login, this);
       title = (TextView) findViewById(R.id.input_title);
       content = (EditText) findViewById(R.id.input_content);
   }
   /**
    * 設置輸入項目的標題  
    * @param title 標題
    */
   public void setTitle(String title) {
       this.title.setText(title);
   }
   /**
    * 獲取用戶輸入的內容
    * @return 用戶輸入的內容
    */
   public String getContent() {
       return content.getText().toString();
   }
}

Model層:
LoginModel.java

public interface LoginModel {
   LoginResult loginByUserNameAndPassword(Context context, LoginParam loginParam);
}

LoginModelImp.java

public interface LoginModel {
   LoginResult loginByUserNameAndPassword(Context context, LoginParam loginParam);
}

Controller層:
LoginActivity.java

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
   //View層渲染用戶登錄頁面 組件
   private LoginInputView userNameInput;
   private LoginInputView passWordInput;
   private Button loginButton;
   private TextView responseTextView;
   //Modle層提封裝了登錄請求數據和行為
   private LoginModel loginModel;
   private Handler handler = new Handler() {
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
           switch (msg.what) {
               case 1:
                   //Controller層獲取Modle更新變化,選擇到合適的視圖更新顯示
                   Bundle bundle = msg.getData();
                   LoginResult loginResult = (LoginResult) bundle.getSerializable("result");
                   responseTextView.setText(loginResult.getMessage());
                   break;
           }
       }
   };
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_login);
       userNameInput = (LoginInputView) findViewById(R.id.login_intput_username);
       passWordInput = (LoginInputView) findViewById(R.id.login_intput_password);
       loginButton = (Button) findViewById(R.id.login_login_button);
       responseTextView = (TextView) findViewById(R.id.login_result_text);
       loginButton.setOnClickListener(this);
       userNameInput.setTitle("UserName:");
       passWordInput.setTitle("PassWord:");
       loginModel = new LoginModelImp();
   }
   @Override
   public void onClick(View v) {
       //接受從View層獲取的用戶點擊,分發到Controller處理
       responseTextView.setText("");
       //Controller層從View層選擇視圖,獲取用戶輸入
       final String userName = userNameInput.getContent();
       final String passWorld = passWordInput.getContent();
       new Thread(new Runnable() {
           @Override
           public void run() {
               //Controller層將用戶輸入登錄信息,發送到Model層執行登錄相關邏輯
               LoginParam loginParam = new LoginParam(userName,passWorld);
               LoginResult loginResult = loginModel.loginByUserNameAndPassword(LoginActivity.this,loginParam);
               //Model層獲取登錄信息后,通知Controller層更新UI
               Message message = handler.obtainMessage();
               message.what = 1;
               Bundle bundle = new Bundle();
               bundle.putSerializable("result", loginResult);
               message.setData(bundle);
               handler.sendMessage(message);
           }
       }).start();
   }
}

運行結果:

3.優點
Controller層起到橋梁作用,在View層和Model層之間通信,使得View層和Modle層分離解耦;
4.缺點
然而,在Android中由于View層的XML控制太弱,Controler層的Activity并沒有和View層完全分離。當需要動態改變一個頁面的顯示(如背景、顯示隱藏按鈕等),都無法在xml中處理,只能在Activity中處理。造成了Activity即時Controller層又是View層,代碼繁冗。

二、MVP
1.簡介

MVP模式是MVC模式在Android上的一種變體。在MVC中Activity應該是屬于Controller層,而實質上,它即承擔了Contrller,也包含了許多View層的邏輯在里面。把Activity中的UI邏輯抽象成View接口,把業務邏輯抽象成Presenter接口,Model類還是原來的Model,這就是MVP;



Model層: 同MVC,負責處理數據加載或者存儲,如從網絡或者數據庫獲取數據等;
View層: 處理數據展示,用戶的交互。在MVP中Activity,Fragment屬于該層;
Presenter層: 是Model層和View層的橋梁,從Model層中獲取數據,展示在View層;
2.實踐
項目結構:



Model層: 同上mvc
View層: 同上mvc,但activity在mvp中為view層,重構如下:

public class LoginActivity extends AppCompatActivity implements View.OnClickListener, LoginContract.View {
   private LoginInputView userNameInput;
   private LoginInputView passWordInput;
   private Button loginButton;
   private TextView responseTextView;
   private Handler handler = new LoginHander();
   private LoginContract.Presenter loginPesenter;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_login);
       userNameInput = (LoginInputView) findViewById(R.id.login_intput_username);
       passWordInput = (LoginInputView) findViewById(R.id.login_intput_password);
       loginButton = (Button) findViewById(R.id.login_login_button);
       responseTextView = (TextView) findViewById(R.id.login_result_text);
       loginPesenter = new LoginPresenter(new LoginModelImp(), this);
   }
   @Override
   protected void onResume() {
       super.onResume();
       loginPesenter.start();
   }
   @Override
   public void onClick(View v) {
       loginPesenter.doLoginRequest(LoginActivity.this);
   }
   @Override
   public void setPresenter(LoginContract.Presenter presenter) {
       loginPesenter = presenter;
   }
   @Override
   public void initLoginShow() {
       userNameInput.setTitle("UserName:");
       passWordInput.setTitle("PassWord:");
       loginButton.setOnClickListener(this);
   }
   @Override
   public LoginParam getInputLoginParam() {
       final String userName = userNameInput.getContent();
       final String passWorld = passWordInput.getContent();
       LoginParam loginParam = new LoginParam(userName, passWorld);
       return loginParam;
   }
   @Override
   public void sendShowLoginMessage(LoginResult loginResult) {
       Message message = handler.obtainMessage();
       message.what = 1;
       Bundle bundle = new Bundle();
       bundle.putSerializable("result", loginResult);
       message.setData(bundle);
       handler.sendMessage(message);
   }
   @Override
   public void updateLoginResultByMessage(Message message) {
       Bundle bundle = message.getData();
       LoginResult loginResult = (LoginResult) bundle.getSerializable("result");
       updateLoginResultByString(loginResult.getMessage());
   }
   @Override
   public void updateLoginResultByString(String result) {
       responseTextView.setText(result);
   }
   /**
    * 登錄Handler,處理來自子線程更新登錄頁面的消息
    */
   private class LoginHander extends Handler {
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
           switch (msg.what) {
               case 1:
                   updateLoginResultByMessage(msg);
                   break;
           }
       }
   }
}

Presenter層:
BasePresenter.java

public interface BasePresenter {
   void start();
}

BaseView.java

public interface BaseView<T> {
   void setPresenter(T presenter);
}

LoginContract.java

public interface LoginContract {
   interface View extends BaseView<Presenter> {
       /**
        * 初始化登錄頁面顯示
        */
       void initLoginShow();
       /**
        * 獲取輸入的登錄參數
        */
       LoginParam getInputLoginParam();
       /**
        * 發送顯示登錄結果消息
        */
       void sendShowLoginMessage(LoginResult loginResult);
       /**
        * 通過消息更新登錄結果
        */
       void updateLoginResultByMessage(Message message);
       /**
        * 更新登錄結果信息
        */
       void updateLoginResultByString(String s);
   }
   interface Presenter extends BasePresenter {
       /**
        * 執行登錄請求
        */
       void doLoginRequest(Context context);
   }
}

LoginPresenter.java

public class LoginPresenter implements LoginContract.Presenter {
   private final LoginModel loginModel;
   private final LoginContract.View loginView;
   public LoginPresenter(LoginModel loginModel, LoginContract.View loginView) {
       this.loginModel = loginModel;
       this.loginView = loginView;
       loginView.setPresenter(this);
   }
   @Override
   public void start() {
       loginView.initLoginShow();
   }
   @Override
   public void doLoginRequest(final Context context) {
       loginView.updateLoginResultByString("");
       new Thread(new Runnable() {
           @Override
           public void run() {
               LoginParam loginParam = loginView.getInputLoginParam();
               LoginResult loginResult = loginModel.loginByUserNameAndPassword(context, loginParam);
               loginView.sendShowLoginMessage(loginResult);
           }
       }).start();
   }
}

3.優點

降低耦合度,實現了Model和View真正的完全分離,可以修改View而不影響Model;

Activity只處理生命周期的任務,代碼變得簡潔;

4.代碼庫

QProject:https://github.com/Pengchengxiang/QProject 

分支:feature/mvc_mvp

 

 

 

來自:http://mp.weixin.qq.com/s?__biz=MzI2OTQxMTM4OQ==&mid=2247484283&idx=1&sn=755a69057a561a08cef8addf38412c5a&chksm=eae1f629dd967f3f0c5b6de34bc894609e580e1c40a3ab1e4aea0633448e158dbadbd00265c8#rd

 

 本文由用戶 sith4465 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!