Android中MVP模式講解
什麼是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.顯示天氣資訊
那好,介面方法可以這樣定義。
2.顯示獲取資訊等待對話方塊
介面可以這樣寫
3.取消顯示對話方塊
最終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當中。
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就有點繞彎子的感覺了。