记一次重构: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

原文发布于微信公众号 - 何俊林(DriodDeveloper)

原文发表时间:2016-12-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android干货

Volley网络连接

2937
来自专栏飞雪无情的博客

Android Activity的生命周期

通过上一节“Android系列之四:Android项目的目录结构”我们已经知道了什么是Activity,那么为什么我们创建一个Activity的导出类的时候为什...

733
来自专栏水击三千

Android学习自定义Dialog

Dialog是Android提供的各种对话框的基类,和上篇的DialogFragment类似。为什么还要介绍Dialog呢,因为DialogFragment只能...

3219
来自专栏Android干货

Android 购物车功能的实现

4455
来自专栏Android干货

浅谈SnackBar(Toast大兄弟)

2604
来自专栏Android干货

安卓开发_浅谈Notification(通知栏)

3728
来自专栏androidBlog

常用的自定义View例子三(MultiInterfaceView多界面处理)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

691
来自专栏学海无涯

Android开发之Fragment回退栈

在Android开发之Fragment动态使用一文中,说到replace方法切换Fragment,会造成Fragment不断销毁、创建,但是有没有办法实现像Ac...

2994
来自专栏james大数据架构

Android中BroadcastReceiver广播

BroadCastReceiver 简介 广播接收者( BroadcastReceiver )用于接收广播 Intent ,广播 Intent 的发送是通过调用...

2155
来自专栏沃趣科技

Oracle 12c系列(7)| Non-CDB转换为PDB

当我们需要将Non-CDB数据库类型更改为PDB数据库类型时,可以使用Cloning的方式将其复制到现有的CDB中,但是该方法需要将Non-CDB中的数据文件复...

3859

扫码关注云+社区

领取腾讯云代金券