记一次重构:Android实践从MVC架构到MVP架构

sith4465 8年前
   <p>一直以来,想分享MVP的实战,因为很多项目开始并不是就是mvp架构的,可能是从传统的mvc结构变迁过来的。今天呈详给大家分享的这篇从mvc重构到mvp,让大家既能看到前后的对比,又能突出mvp的优点, 话不多说,看下正文。</p>    <p><strong>一、MVC</strong></p>    <p><strong>1.简介</strong></p>    <p>MVC是目前大多数企业采用J2EE的结构设计,主要适用于交互式的Web应用。在Android中也有体现和使用,但是存在一定的弊端(下面将讲述),于是才有了Android官方推荐的MVP。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dd545201a5d4ea46527c4f8b9d83fd28.jpg"></p>    <p>在Android的开发过程中,每个层对应如下:     </p>    <p>Model层:对应Java Bean、Database、SharePreference和网络请求等;</p>    <p>View层:对应xml布局、自定义View或ViewGroup;    </p>    <p>Controller层:对应Activity、Fragment;</p>    <p><strong>2.实践</strong></p>    <p>对于理论的理解 ,还是需要结合实际。下面我们将前面文章实现的https登录Demo,使用MVC的方式来进行重构:</p>    <p>项目结构:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1898d7f4ea7afb1513f2cbb24d37a6a2.jpg"> <strong> </strong></p>    <p>View层:</p>    <p>activity_login.xml</p>    <pre>  <code class="language-java"><?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>  </code></pre>    <p>LoginInputView.java</p>    <pre>  <code class="language-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();     }  }  </code></pre>    <p>Model层:<br> LoginModel.java</p>    <pre>  <code class="language-java">public interface LoginModel {     LoginResult loginByUserNameAndPassword(Context context, LoginParam loginParam);  }  </code></pre>    <p>LoginModelImp.java</p>    <pre>  <code class="language-java">public interface LoginModel {     LoginResult loginByUserNameAndPassword(Context context, LoginParam loginParam);  }  </code></pre>    <p>Controller层:<br> LoginActivity.java</p>    <pre>  <code class="language-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();     }  }  </code></pre>    <p style="text-align:center">运行结果:<br> <img src="https://simg.open-open.com/show/f3df8828ba60667cab9f9fe8b422d277.png"></p>    <p>3.优点<br> Controller层起到桥梁作用,在View层和Model层之间通信,使得View层和Modle层分离解耦;<br> <strong>4.缺点</strong><br> 然而,在Android中由于View层的XML控制太弱,Controler层的Activity并没有和View层完全分离。当需要动态改变一个页面的显示(如背景、显示隐藏按钮等),都无法在xml中处理,只能在Activity中处理。造成了Activity即时Controller层又是View层,代码繁冗。<br> <br> <strong>二、MVP<br> 1.简介 </strong><br> MVP模式是MVC模式在Android上的一种变体。在MVC中Activity应该是属于Controller层,而实质上,它即承担了Contrller,也包含了许多View层的逻辑在里面。把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model,这就是MVP;</p>    <p style="text-align:center"><br> <img src="https://simg.open-open.com/show/ed11dc6e5d0d0b3b26e14dc14fe736a9.png"></p>    <p><br> <strong>Model层:</strong> 同MVC,负责处理数据加载或者存储,如从网络或者数据库获取数据等;<br> <strong>View层:</strong> 处理数据展示,用户的交互。在MVP中Activity,Fragment属于该层;<br> <strong>Presenter层:</strong> 是Model层和View层的桥梁,从Model层中获取数据,展示在View层;<br> <strong>2.实践<br> 项目结构: </strong></p>    <p style="text-align:center"><br> <img src="https://simg.open-open.com/show/f2a2e1b57ccf812cb13a26fb05ffe0ef.jpg"></p>    <p><br> <strong>Model层:</strong> 同上mvc<br> <strong>View层:</strong> 同上mvc,但activity在mvp中为view层,重构如下:</p>    <pre>  <code class="language-java">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;             }         }     }  }  </code></pre>    <p>Presenter层:<br> BasePresenter.java</p>    <pre>  <code class="language-java">public interface BasePresenter {     void start();  }  </code></pre>    <p>BaseView.java</p>    <pre>  <code class="language-java">public interface BaseView<T> {     void setPresenter(T presenter);  }  </code></pre>    <p>LoginContract.java</p>    <pre>  <code class="language-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);     }  }  </code></pre>    <p>LoginPresenter.java</p>    <pre>  <code class="language-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();     }  }  </code></pre>    <p><strong>3.优点</strong></p>    <p>降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Model;</p>    <p>Activity只处理生命周期的任务,代码变得简洁;</p>    <p><strong>4.代码库</strong></p>    <p>QProject:https://github.com/Pengchengxiang/QProject </p>    <p>分支:feature/mvc_mvp</p>    <p> </p>    <p style="text-align:center"> </p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s?__biz=MzI2OTQxMTM4OQ==&mid=2247484283&idx=1&sn=755a69057a561a08cef8addf38412c5a&chksm=eae1f629dd967f3f0c5b6de34bc894609e580e1c40a3ab1e4aea0633448e158dbadbd00265c8#rd</p>    <p> </p>