記一次重構: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