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