/

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


一直以来,想分享MVP的实战,因为很多项目开始并不是就是mvp架构的,可能是从传统的mvc结构变迁过来的。今天呈详给大家分享的这篇从mvc重构到mvp,让大家既能看到前后的对比,又能突出mvp的优点,呈详,目前在去哪儿网就职,同时也是csdn博客专家,他的blog地址:http://blog.csdn.net/p106786860.【阅读原文】,可看对应文章链接,话不多说,看下正文。

一、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


发布评论

热门评论区: