什么是MVP
現在,設計一款新的APP,對于Android
而言,至少都是什么Material Design
,然后可能又是什么MVP
什么什么的。在MVC
,MVVM
都還沒有弄明白的時候,又出來了新的MVP
,是不是有種淡淡的憂傷??
那么問題來了,到底什么是MVP
呢?
MVP
是從經典的模式MVC
演變而來,它們的基本思想有相通的地方:Controller/Presenter
負責邏輯的處理,Model
提供數據,View
負責顯示。作為一種新的模式,MVP
與MVC
有著一個重大的區別:在MVP
中View
并不直接使用Model
,它們之間的通信是通過Presenter
(MVC
中的Controller
)來進行的,所有的交互都發生在Presenter
內部,而在MVC
中View
會直接從Model
中讀取數據而不是通過Controller
。
在MVC
里,View
是可以直接訪問Model
的!從而,View
里會包含Model
信息,不可避免的還要包括一些業務邏輯。 在MVC
模型里,更關注的Model
的不變,而同時有多個對Model
的不同顯示,及View
。所以,在MVC
模型里,Model
不依賴于View
,但是View
是依賴于Model
的。不僅如此,因為有一些業務邏輯在View
里實現了,導致要更改View
也是比較困難的,至少那些業務邏輯是無法重用的。
以上是百度百科對MVP
設計優勢的一些解釋。結合之前的MVC
設計模式的話,其實MVP
就是更加簡化了Activity
等View
的職責,讓他們交出了數據管理以及相關業務邏輯處理的權利,而是純粹的經行UI相關的操作!!(比如說控制View
的顯示和隱藏,數據的相關展現。)
MVP的好處
、
根據上面兩個圖示可以看出,
MVP
和MVC
的區別在于功能模塊訪問的權限,在MVP
中,View
層和Mode
數據層是不能直接有聯系的。而是必須通過Presenter
層來完成一個統一的調度,Presenter
充當了View
層和Mode
層的中間橋梁,這樣的好處就是實現了View和數據業務的完全解耦,是的邏輯層次更加分明,利于代碼的維護!這個也滿足設計中要求的單一職責原則。
MVP設計示例
大概說完了好處,那么就要說說剛入手MVP
設計時的一些感覺不爽的地方,采用MVP
設計之后,整體的項目層級大概就是醬紫啦!
data
就是Model
層相關的封裝,包括api
請求等等,presenters
就是管理View
和Model
的中間層,view
就是Activity
相關的封裝。可以看到在
presenter
和view
中都有相關的接口,在MVP
設計中,其精髓就在于面向接口編程,不依賴與具體實現。具體來講就是通過
View
的相關接口定義出view具體實現
(Activity
)的一些基本方法,比如說(onShowLoading()
,onShowDialog()
),在相關的presenter
接口中也需要定義一系列的行為事件或者就叫抽象方法嘛!比如說(onLoginButuonClick()
,onItemClicked(int position)
)這些都是需要去適應的,使用MVP
的模式編寫代碼,就要求我們更加注重于抽象,怎么解釋呢,還是比如說登陸頁面,一個輸入用戶名一個輸入密碼,一個登陸按鈕。大致流程就是點擊登陸按鈕,顯示loading頁面,獲取兩個輸入框的文本內容,經行相關判斷和檢查(本地
trim()
去空格,提交服務器check是否OK),關閉loading頁面,然后根據返回內容做相關提示:登陸成功,跳轉相關頁面,登陸失敗。。
采用MVP設計模式處理這些問題那么就需要如下進行:
Model
層應該封裝用戶名和密碼(UserInfoBean
)等,完成相關的api(checkUserInfo(UserInfoBean bean)
)請求。View
層應該有showLoading()
;closeLoading()
;showErrorView(String msg)
;showHomeView()
;getUserName()
;getUserPasswrod()
;-
Presenter
層應該有(onLoginButtonClick()
:響應View
登陸按鈕的點擊 )
OK,那么接下這三者到底怎么互相協調經行相關操作呢?
首先LoginActivity
我們的登陸頁面,實現View的接口,依然是在onCreate()
里面初始化相關的View
。
在Presenter
和View
的交互中,在View
的初始化的時候new
出對應的Presenter
。并通過Presenter
的構造函數將view的引用傳入Presenter
的具體實現類中。final LoginPresenter presenter = new LoginPresenterImpl(this);
在 PresenterImpl
的中:
private final LoginView view;
private final Activity activity;
public LoginPresenterImpl(LoginView view) {
this.view = view;
this.activity = (Activity) view;
}
這樣就完成了Activity
和Presenter
的交互回調了。
在Activity
的點擊事件中:
Button mSignInButton = (Button) findViewById(R.id.email_sign_in_button);
mSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
presenter.onLoginClick();
}
});
通過 presenter.onLoginClick()
,將相關的處理邏輯調度給對應的Presenter
來處理了!
而在Presenter
的具體實現類LoginPresenterImpl
中:
@Override
public void onLoginClick() {
// Reset errors.
view.setPasswordError(null);
view.setUserNameError(null);
// Store values at the time of the login attempt.
String password = view.getPassWord();
String userName = view.getUserName();
// Check for a valid email address.
if (TextUtils.isEmpty(userName)) {
view.setUserNameError(activity.getString(R.string.error_field_required));
view.setUserNameFocus();
return;
}
// Check for a valid password, if the user entered one.
if (TextUtils.isEmpty(password) ||!isPasswordValid(password)) {
view.setPasswordError(activity.getString(R.string.error_invalid_password));
view.setPasswordFocus();
return;
}
UserLoginTask mLoginTask = new UserLoginTask(this, userName, password);
if (!isRun) {
mLoginTask.execute((Void) null);
}
}
可以看到,在LoginPresenterImpl
中,持有的View
的引用其實就是對應的LogingActivity
,在這里,先是調用view.setPasswordError(null)
,重置相關的錯誤提示,然后通過 String password = view.getPassWord()
來獲取對應的密碼輸入。接下來就是密碼的非空判斷,如果異常,那么就調用view.setUserNameError(String msg)
,來通知Activity
相關的錯誤信息。最后如果沒有問題的話,那么就開啟子線程去走網絡請求。
在Model
層中,定義的LoginTask
的接口:
public interface LoginTask {
void onLoginSuccess();
void onLoginFailed(String msg);
void onLoginCancelled();
void onLoginTaskStart();
}
這里直接讓對應的Presenter
實現該接口,也是通過相關的接口,處理對應的邏輯!
比如說:
@Override
public void onLoginCancelled() {
view.showProgress(false);
isRun = false;
}
@Override
public void onLoginTaskStart() {
isRun = true;
view.showProgress(true);
}
這分別是任務開始和取消的情況。
最后總結起來就是view
定義出對應的Activity
的相關行為(顯現進度條、隱藏對話框等。)Presenter
定義出對應的處理行為動作(Button
的點擊事件,滑動事件其實還有生命周期相關的比如onResume()
、onDestroy()
還有api請求的相關方法。)最后就保證Presenter
擁有絕對的控制和調動權,Model
層的相關結果通過Presenter
層調度之后調用View
的相關方法傳遞給具體的View
實現類,從而保證View
層和Model
層的完全解耦。
一些思考
- 1、View層的一些小動作也一定要報告給
Presenter
嗎?比如說我在點擊某個Button
之后要讓某個widget
隱藏了!!??
如果真的就是很簡單的如上面的這種情況,那么
View
層可以自己處理這情況,但是其實一般都沒有這么簡單純粹的情況,就像在公司做事兒一樣,要養成向領導匯報的好習慣,可能很小的事情或者你覺得領導很忙這種事情完全可以不請示他,那么可要小心了,一旦這個事情變大,領導追究責任時。。所以這個度你要自己體會!!!
- 2、
Presenter
和View
的對應關系?是一對一?是多對一?還是一對多?
這個關系我覺得都是可以出現的,但是一般情況會是一對一的,然后P->V 一對多也是有這種情況的,比如說在
RecycleView
中出現了幾種布局,每種布局的相關邏輯要在P中經行處理,那么就會出現一對多的情況!至于多對一的情況,我覺得這個相對還要少一些吧!處理中心應該是獨一的一個!
- 3、采用
MVP
時不知道對應的View
和Presenter
應該定義那些抽象的方法??
這個問題其實也并不是問題,不要就是在那里空想,其實你把層次分好了,缺什么方法到需要的時候自然就知道了,這個時候再添加到對應的接口中就好了!也沒有人能一次性就把所有的情況都考慮完全,所以多想多寫,多積累相關經驗。
- 4、采用
MVP
會精簡代碼嗎?
我一開始想的肯定能減少很多代碼的。但是真的使用之后,你會發現,其實代碼并沒有減少,相反,可能還要多了!因為你要創建相關的抽象,直觀的第一個體現就是一個項目的class變多了!但是它的層次結構和相關邏輯更加清晰了,尤其是要你去維修之前的代碼,那個效率提升真的很高,至少改動View,你可以不用擔心把相關的業務邏輯代碼改掛了!所以也是有利必有弊吧!
- 5 采用
MVP
會導致內存泄露等問題嗎?
內存泄露的問題,這里主要是說在View層(Activity)相關的生命周期結束之后,由于某些引用一直存在,導致其無法被正常的銷毀和回收,典型的問題出現就是在P層出現了靜態的引用,這也是千萬別出現的寫法!
- 6 需要在Destroy的時候再P層將V層的引用置空嗎?
詳細說就是在P層增加
onDestroy()
的方法回調,在這里將View層相關引用置空,這樣做貌似很應該,但是 ,如果一些耗時的操作在onDestroy()方法執行之后又執行到V層的邏輯,而此時V的引用已經置空了,那么必定出現空指針咯!
- 7
LoginTask
網絡請求相關的一定要定義成獨立的接口嗎?
我覺得這個沒有太多的必要,因為定義的越多,其實你的代碼就會越來越臃腫,所以,你可以在
Presenter
中直接定義出相關網絡請求等的方法就好!
- 8 在P層一定需要將相關的View轉換為對應的Activity嗎?
首先即使轉換了也不要直接去使用Activity去調用相關的方法,這樣就違背了面向抽象的原則了。為什么有時候需要呢?是因為在P層有些操作你是需要使用到上下文的,這樣寫就是可以方便使用這個上下文。(Activity的跳轉,sp dp px的轉換,SP保存相關數據等等都要使用到上下文。)
寫在最后
任何一種模式都不是100%的完美,MVP
也是!而且任何一種模式都不應該束縛相關的開發!而是應該讓程序簡潔(這個不意味著是代碼少的簡潔),易于維護和擴展! MVP也沒有固定的寫法,MVP
就是更加強化了面向接口編程的一種具體體現。我們定義相關抽象的接口,具體的子類實現相關的方法實現相關的業務邏輯,對于習慣了一條線寫下去的編碼方式,剛開始的確會有點兒不習慣。因為擼著擼著,突然你要告訴自己,這里我們要交給Presenter
處理了。然后又到Presenter
里面,擼著擼著,我們這里要通知View
了!!
上面這個例子,只是一個入門的(很復雜的我其實也還沒有寫過呢!)。但是必須強調,這里的Model
層其實是比較弱化的。在實際開發中,數據庫和網絡請求更加強化,對應的Model
層就更復雜,比如說可能我們獲取的相關json
數據,還要在Presenter
里面進行一些處理,然后才能分發給View
去更新相關界面!另外就是子線程和UI線程的頻頻切換,不僅是切換很不爽,而且相關匿名內部類的代碼估計看著也讓人很抓狂的!!
說了這么多,想說個撒呢?其實就是對應的網絡請求,建議大家使用Retrofit ,異步操作就可以用 Rxjava來搞定!
Rxjava
,超級牛的異步請求解決方案,輕松解決你的子線程UI線程切換問題,鏈式調用,邏輯特別清楚。
而對于Retrofit
,http
網絡請求的最佳實踐,并且可以結合Rxjava
,寫出來的代碼那是一個飄逸!!!
相關下載

如有問題歡迎留言,喜歡記得點贊贊贊喲!!