Android中MVP模式講解

電腦通訊 9547 258 2018-09-17
編輯推薦:
本文來自于csdn,本文主要以一款顯示天氣的APP實戰為例詳細闡述MVP在Android開發中的優勢。

什么是MVP模式?

看NBA的都知道MVP(National Basketball Association Most Valuable Player Award ,簡稱MVP)這個概念,我當時的第一反應也是這個。但是,此MVP非彼MVP.我們今天要討論的MVP其實同MVC一樣,是一種編程模式和思想,也許更準確地講是一種架構。

MVP和MVC

MVC簡介

開發Android的都知道MVC。

M對應Model,代表業務數據

V對應View,代表視圖

C對應Controller,代表控制器。

MVC架構將視圖和數據分離,在WEB領域中應用的很廣泛。

用戶通過界面組件進行操作,也就是View層,相應的動作會傳遞給控制器也就是Controller層,而Controller根據自己的業務邏輯去操作數據層也就是Model,而最終數據層的變化會同步更新到視圖層。

MVC好處

這里直接引用百度百科

MVC 分層有助于管理復雜的應用程序,因為您可以在一個時間內專門關注一個方面。例如,您可以在不依賴業務邏輯的情況下專注于視圖設計。同時也讓應用程序的測試更加容易。

MVC 分層同時也簡化了分組開發。不同的開發人員可同時開發視圖、控制器邏輯和業務邏輯。

可以看到MVC的主要目的是為了視圖和數據分離,這對于開發大型軟件來說更方便進行模塊的劃分,提高編碼速度與質量。

Android中的MVC

Android世界中也經常運用到MVC模式。

Activity對應視圖界面也就是View層。

數據庫文件,Sharedprefrence,內存緩沖,磁盤緩沖等數據內容對應Model層。

而Controller控制層基本上也由Activity層面來進行。

Android中mvc中基本動作流程

假設我們現在有這么一個需求,需要在一個界面上顯示當天的天氣,不僅如此,還可以通過列表項選擇以往某一天的天氣。

mvc架構開發的話,大概是這樣。

在layout制定相應的布局文件,然后顯示在Activity上,用于顯示天氣信息。這對應于View層,這里的View并不是Android中開發中的組件view而是對視圖的統稱.

Activity在onCreate方法或者onResume方法去服務器獲取數據,或者通過界面上的某個按鈕之類去啟動獲取服務器數據的任務,這里就對應到View—>Controller,只不過這里的View和Controller對是由Activity來完成。

Controller獲取到了數據之后,分別存在,內存、磁盤和數據庫中,并且數據獲取成功或者失敗后,Activity界面需要同步更新狀態。這由對應上面流程中的Controller—>Model 和Model—->View。

這里的流程還算清晰,也便于理解。

MVP為什么?

上面講解了MVC的基礎知識,大家可能覺得MVC挺好的啊?怎么還要整一個MVP。是的MVC是挺好的,但是它也有它的缺點,特別是針對Androi開發。

因為Android的特殊性,使得Activity對應了MVC中的V和C,同時擔任兩個角色,就有了類似“既當爹又當媽”的感覺,這顯然就不符合軟件設計原則的“單一職責”原則。但現實中是很多的APP代碼中有這么的處境,特別是Androi原生的很多系統APK,某些Activity動則幾千行代碼。

況且,隨著項目的深入發展,很多邏輯很越來越復雜,Activity處理的東西也會越來越多,代碼越來越臃腫。這樣一來維護起來的代價就會越來越高,這是因為View的變化會引起Controller的很多變化,反之亦然。用一句大白話來說明就是–某一段代碼的變動會引起很多其他相關聯的代碼的改動,而程序員都是懶惰的,所以會恨死這樣的代碼。

而MVP就是要減輕在Android中的這種困惑。

MVP是基于MVC的,它的架構圖如下:

M(Model) 數據相關層

V(View) 視圖層,如Activity上的布局

P(Presenter) 紐帶層,用來連接Model與View.

MVP開發在Android中的基本流程

1. View層定義View.interface,用來定義View的行為。一般由Activity或者是Fragment來實現這個接口,它定義了View視圖的各種變化,如設置Textview,加載對話框,更新進度條等。

2. Model層定義Modle.interface,這個是用來定義數據層發生變化時的通知接口,因為Model不能直接與View交互,所以它與Presenter交互,然后再通過Presenter間接達到與View的交互。

3. Presenter翻譯的意思是主持人,也就是主持場合,控制節奏的意思。在這時Presenter就負責具體的業務邏輯,請求數據,把數據送到Model,或者監聽Model的數據變化,接受View層的動作,負責通過通知View層的視圖變化。

如果跟MVC的架構圖對比的話,可以發現它們有相似之處也有不同。

相似之處

模塊劃分的相似

MVC由Model、View、Controller構成。

MVP由Model、View、Presenter構成。

不同的地方

MVP中Presenter取代了MVC中的Controller

MVC中Model、View、Controller之間相互發生通信,而MVP中Model與Presenter相互通信,View與Presenter相互通信,而Model與View之間沒有通信。

Android中MVP的好處?

就Android層面上來講MVC架構雖然好,但不是最好,情況前面有講過。用一句話概括就是“模塊界限很模糊”。而MVP的出現實際上就是將MVC進行升級,對應Android開發中就是幫助Activity解壓。

MVC中Activity同時充當了V和C的角色,這就屬于界限劃分不清楚。而MVP則劃分的很清楚,Activity只充當V的角色,業務邏輯控制交給了Presenter.

個人對MVP模式的理解

這一段是我自己的看法,也許不正確。

我個人覺得MVP沒有什么很神秘的,因為Android SDK上開發,本來就差不多是MVC的角色。Activity基本上Android開發中最重要的一環。

我以前在團隊工作的時候,團隊分工是每人負責相應的Activity,在這里Activity是最小的開發單元。再后來,某些Activity變得越來越重要,越來越復雜,代碼也越來越多,這樣會造成團隊某個人的開發任務重,而其他的團隊成員也幫不上忙。而MVP的出現可以將Activity再細分,劃為View和Presenter兩個部分,所以Activity不再是最小的開發單元,如果可以完全可以這樣分配任務,一個開發人員負責View部分,另一個開發人員負責Presenter部分。

況且因為MVP的劃分,所以各個部分其實相對獨立,V的變動會對P的部分造成較少的影響,而M對V或者說V對M幾乎是透明的。

因為Presenter的存在,View和Model就可以很輕松,頂多Presenter累一點。

還有一個特點是MVP模式很適合測試,單獨測試VIEW成了一種可能。我們可以模擬View和Model的數據來測試Presenter的邏輯。

MVP實戰

在現在的公司項目中,我已經用上了MVP模式開發。但是在這里,我不想照搬代碼。主要是因為怕復雜的代碼或者其它的知識點干擾MVP本身的脈絡。所以,我用一個簡單的DEMO來講解,大家一看就明白。

場景需求

假設現在需要做一款APP,就是顯示天氣,界面很簡單,一個TextView顯示天氣信息,一個Button用來請求實時天氣。

如下圖所示

軟件啟動后,會自動獲取天氣,然后TextView就可以顯示信息。而用戶點擊獲取實時天氣的按鈕,界面上會彈出正在獲取中的進度對話框,等待數據加載成功后,對話框消失。Textview顯示就新的天氣情況。

代碼開發

因為選定MVP模式,所以第一步就是包的組織。

View層的接口定義及實現

在MVP中Activity用來專注視圖的表現。

而在本例子中View的表現有哪些呢?很多教程直接就上來貼代碼,個人覺得這樣是不好的。View的表現當然要用View.interface接口來定義

現在我們來分析一下,在本例中View應該有哪些表現。

1.顯示天氣信息

那好,接口方法可以這樣定義。

public void onInfoUpdate(String info);

2.顯示獲取信息等待對話框

接口可以這樣寫

public void showWaitingDialog();

3.取消顯示對話框

public void dissmissWaitingDialog();

最終View.interface就完成了,非常簡單

public interface IWetherView {

public void onInfoUpdate(String info);

public void showWaitingDialog();

public void dissmissWaitingDialog();

}

接口文件已經定義好了,那么View的實現呢?在這里用MainActivity去實現它。

public class MainActivity extends AppCompatActivity implements IWetherView{

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

@Override

public void onInfoUpdate(String info) {

}

@Override

public void showWaitingDialog() {

}

@Override

}

}

具體的業務代碼,我們等會再實現。

Model層的接口定義及實現

Model層是數據層,用來存儲數據并且提供數據。在這里為了便于演示,數據被簡化為了String類型。

接口定義如下:

public interface IWetherModel {

//提供數據

public String getInfo();

//存儲數據

public void setInfo(String info);

}

它的實現文件如下:

public class IWetherImpl implements IWetherModel {

@Override

public String getInfo() {

return null;

}

@Override

public void setInfo(String info) {

}

}

Presenter代碼及實現

Presenter是個大忙人,因為要同時對View和Model對接,所以內部必須持有它們的接口引用。

所以有如下:

public class WetherPresenter {

IWetherModel mModel;

IWetherView mView;

}

Presenter與View的通信

View—–>Presenter

從視圖界面出發,用戶要請求數據,而Presenter是具體實現者,所以Presenter要提供方法代View的實現者調用,并且View的實現中必須要有Presenter的引用。

所以MainActivity.java中要有WetherPresenter的引用。

public class MainActivity extends AppCompatActivity implements IWetherView{

......

WetherPresenter mPresenter;

......

}

而Presenter也要開發API供View調用。

所以Presenter要有requestWetherInfo()方法:

public class WetherPresenter {

IWetherModel mModel;

IWetherView mView;

//供View層調用,用來請求天氣數據

public void requestWetherInfo(){

}

}

presenter—–>View

presenter操作View,是通過View.interface,也就是View層定義的接口。

所以很容易得到下面的代碼:

public class WetherPresenter {

......

private void showWaitingDialog(){

if (mView != null) {

mView.showWaitingDialog();

}

}

private void dissmissWaitingDialog(){

if (mView != null) {

mView.dissmissWaitingDialog();

}

}

private void updateWetherInfo(String info){

if (mView != null) {

mView.onInfoUpdate(info);

}

}

......

}

因為Presenter持有View的引用,所以在這里要將View.interface注入到Presenter當中。

public class WetherPresenter {

IWetherModel mModel;

IWetherView mView;

......

public WetherPresenter(IWetherView mView) {

this.mView = mView;

}

......

}

Presenter與Model的通信

Presenter與Model的通信也是雙方的。

Presenter—->Model

presenter獲取到了數據,可以交給Model處理

private void saveInfo(String info){

mModel.setInfo(info);

}

Model—–>Presenter

Model處理完數據后它也能對Presenter提供數據。Presenter可以通過Model對象獲取本地數據。

WetherPresenter.java

private String localInfo(){

return mModel.getInfo();

}

Presenter代碼實現

前面已經講了Presenter與Model,Presenter與View之間的通信,現在就可以編寫代碼將它們粘合起來。

Presenter本身需要向服務器獲取代碼,所以還要編寫它的相應方法:

public void requestWetherInfo(){

getNetworkInfo();;

}

private void getNetworkInfo(){

new Thread(new Runnable() {

@Override

public void run() {

try {

//打開等待對話框

showWaitingDialog();

//模擬網絡耗時

Thread.sleep(6000);

String info = "21度,晴轉多云";

//保存到Model層

saveInfo(info);

//從Model層獲取數據,為了演示效果,實際開發中根據情況需要。

String localinfo = localInfo();

//通知View層改變視圖

updateWetherInfo(localinfo);

} catch (InterruptedException e) {

e.printStackTrace();

}finally {

//取消對話框

dissmissWaitingDialog();

}

}

}).start();

}

到此,完整的Presenter代碼如下:

public class WetherPresenter {

IWetherModel mModel;

IWetherView mView;

public WetherPresenter(IWetherView mView) {

this.mView = mView;

mModel = new IWetherModelImpl();

}

public void requestWetherInfo(){

getNetworkInfo();;

}

private void showWaitingDialog(){

if (mView != null) {

mView.showWaitingDialog();

}

}

private void dissmissWaitingDialog(){

if (mView != null) {

mView.dissmissWaitingDialog();

}

}

private void updateWetherInfo(String info){

if (mView != null) {

mView.onInfoUpdate(info);

}

}

private void saveInfo(String info){

mModel.setInfo(info);

}

private String localInfo(){

return mModel.getInfo();

}

private void getNetworkInfo(){

new Thread(new Runnable() {

@Override

public void run() {

try {

//打開等待對話框

showWaitingDialog();

//模擬網絡耗時

Thread.sleep(6000);

String info = "21度,晴轉多云";

//保存到Model層

saveInfo(info);

//從Model層獲取數據,為了演示效果,實際開發中根據情況需要。

String localinfo = localInfo();

//通知View層改變視圖

updateWetherInfo(localinfo);

} catch (InterruptedException e) {

e.printStackTrace();

}finally {

//取消對話框

dissmissWaitingDialog();

}

}

}).start();

}

}

MainActivity代碼編寫

生成Presenter。這個在Activity中的onCreate方法中,并把自身當成IWetherView注入到presenter當中。

mPresenter = new WetherPresenter(this);

2 . 操作Presenter。當用戶點擊按鈕時,通過調用mPresenter獲取數據,然后靜待更新。

mButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

mPresenter.requestWetherInfo();

}

});

View.interface回調方法被觸發時,進行相應的視圖更新。

這里主要的視圖有

顯示對話框

取消對話框

顯示 天氣信息。

對應代碼如下:

@Override

public void onInfoUpdate(final String info) {

Log.d(TAG, "onInfoUpdate: "+info);

runOnUiThread(new Runnable() {

@Override

public void run() {

mTvInfo.setText(info);

}

});

}

@Override

public void showWaitingDialog() {

runOnUiThread(new Runnable() {

@Override

public void run() {

if(mDialog != null && mDialog.isShowing()){

mDialog.dismiss();

}

mDialog = ProgressDialog.show(MainActivity.this,"","正在獲取中...");

}

});

}

@Override

public void dissmissWaitingDialog() {

runOnUiThread(new Runnable() {

@Override

public void run() {

if(mDialog != null && mDialog.isShowing()){

mDialog.dismiss();

}

}

});

}

所以整個MainActivity.java代碼如下:

public class MainActivity extends AppCompatActivity implements IWetherView{

private static final String TAG = "MainActivity";

WetherPresenter mPresenter;

private TextView mTvInfo;

private Button mButton;

private ProgressDialog mDialog;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mPresenter = new WetherPresenter(this);

mTvInfo = (TextView) findViewById(R.id.tv_info);

mButton = (Button) findViewById(R.id.btn_request);

mButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

mPresenter.requestWetherInfo();

}

});

}

@Override

public void onInfoUpdate(final String info) {

Log.d(TAG, "onInfoUpdate: "+info);

runOnUiThread(new Runnable() {

@Override

public void run() {

mTvInfo.setText(info);

}

});

}

@Override

public void showWaitingDialog() {

runOnUiThread(new Runnable() {

@Override

public void run() {

if(mDialog != null && mDialog.isShowing()){

mDialog.dismiss();

}

mDialog = ProgressDialog.show(MainActivity.this,"","正在獲取中...");

}

});

}

@Override

public void dissmissWaitingDialog() {

runOnUiThread(new Runnable() {

@Override

public void run() {

if(mDialog != null && mDialog.isShowing()){

mDialog.dismiss();

}

}

});

}

}

效果如上面的:

總結

mvp非常適合大型的APP開發,越復雜它的優勢越明顯,但是如果APP代碼本身很簡明,mvp就有點繞彎子的感覺了。