Android MVP從懵逼到入門:登陸業務實踐
MVP簡介
最近幾天在啃MVP,現在的你或許跟幾天前的我一樣,對MVP還是一臉懵逼,雖然MVP三個字母都認識,但連在一起卻不明白到底是個什么東東,沒關系,快來干了這碗雞湯,立馬從懵逼到入門,入不了門你來打我,文末統計人數。
首先,MVP是一種設計模式,或者說架構。Google把它列入Android Architecture Blueprints--Android 架構藍圖,并給出了官方的例子來解釋如何實現MVP。本文實現的登陸功能就是根據MVP基礎架構Demo來實現的。
其次,MVP從何而來?想必都知道是MVC的演化版本,現在比較流行,被廣大開發者所認可。被認可的原因我在這里總結一下:
- 代碼清晰,容易理解(掌握MVP的前提下)
- 簡化了萬能的Activity的邏輯
- 解耦了View和Model
- 方便單元測試
一直以來,在MVC模式中Activity的萬能角色就備受詬病,一旦邏輯越來越復雜,Activity就越來越臃腫,承擔越來越多的職責,代碼閱讀起來費勁,維護成本跟著提高,而且,在MVC模式中,View 和 Model直接交互,耦合度高,違背了軟件開發“高類聚、低耦合”的設計目標--于是MVP橫空出世。
關于MVP的理論知識就嗶嗶這些,想要詳細了解的童鞋回頭自己做功課去,這里不是重點,本文的重點是把MVP模式實踐起來。
MVP實現登陸
這是google samples TODO-MVP項目中的MVP圖解。因為本人學習MVP主要也是根據谷歌官方demo來的,所以,這個圖直接拿來。這個圖怎么看?
分為左右兩部分,左邊沒顏色的部分包含的內容是Model,這里面包含了數據實體模型、數據訪問接口、SQLite數據庫操作、網路數據操作、數據內存緩存,這些都是Model要做的事情,跟MVC沒什么差別;
右邊...那個什么顏色的背景(色盲晚期),注意那個Activity,我們看到VIEW和PRESENTER都被放在了Activity里邊,而VIEW的實現類在這里用的是Fragment;
-
隱藏內容:這里面其實還有一個內容--契約類,也就是項目里的XXXContract.java類,是一個接口類,作用是定義VIEW接口和PRESENTER接口提供的接口方法。這個類原則上不屬于MVP模式里的任何角色,所以沒在上圖出現,可以理解。
先看看效果 不方便看視頻的就看看圖吧!
[圖片上傳失敗...(image-32673-1521790364530)]
[圖片上傳失敗...(image-17e711-1521790364530)]
-
1.創建View接口和Presenter接口基類
BaseView作為View接口基類,定義了一個重要的接口:void setPresenter(T presenter);
[圖片上傳失敗...(image-220445-1521790364530)]
[圖片上傳失敗...(image-edaa6-1521790364530)]
這其實是MVP的一個關鍵點,通過這個接口,View的實現類(即Fragment)就持有了Presenter的實例,于是,View就可以通過Presenter來操作Model中的數據接口了。如果你要實現MVP模式,記住,不管三七二十一,先寫這個基佬,哦,不對,是基類。
Presenter接口基類里同樣定義了一個接口:
void start();
這個方法就是直接操作Model的,比如加載數據。通過這兩個基類的定義的接口就能看出,View和Model不直接交互,而是通過Presenter來操作,這是與MVC的不同之處。
-
2.登陸契約類LoginContract
public interface LoginContract {interface Presenter extends BasePresenter { void login(); void reset(); } interface View extends BaseView<Presenter> { String getUserEmail(); String getPassword(); boolean isEmailValid(String email); boolean isPasswordValid(String password); boolean setEmailError(String error); boolean setPasswordError(String error); void showLoginProgress(boolean show); void resetEditView(); void toMainAct(); void showFailedError(); } }
這個類是首次出現于google的mvp示例中,以前的MVP模式并未見到,這個類定義了View接口和Presenter接口為對方的實例提供的方法。
比如,我在View中可以獲取用戶輸入的郵箱和密碼,判斷郵箱密碼是否有效,設置郵箱密碼輸入框錯誤提示信息,顯示登陸ProgressBar等,同樣,在Presenter接口中,提供了登陸和重置兩個功能,用戶通過View上的兩個按鈕,響應Presenter對應的接口,執行相關的業務邏輯。
這個契約類的好處是方便接口統一管理、修改,同時,內容清晰,一目了然。
-
3.View的實現類LoginFragment implements LoginContract.View
實現接口定義的各個方法,必須持有Presenter,并通過接口void setPresenter(T presenter)
為其賦值。
注意官方的demo說明里有這段內容:
Note: in a MVP context, the term "view" is overloaded:
The class android.view.View will be referred to as "Android View"
The view that receives commands from a presenter in MVP, will be simply called "view".
我來獻個丑,翻譯一下:
注意:在MVP的上下文里,“view”一詞有多重含義:
- android.view.View被稱為“Android View”
- 在MVP中,從presenter接收命令的view將被簡單地稱為“view”。
什么意思?
我的理解是這樣的:MVP中的VIEW由兩部分組成,一個是view接口,比如上面的LoginContract.View接口;一個是該接口的實現類,比如上面的LoginFragment。view接口負責與presenter交互,presenter調用view接口定義方法來操作view的實現類;具體實現都是在android.view.View里實現的,即LoginFragment。
-
4.Presenter的實現類LoginPresenter implements LoginContract.Presenter
實現接口定義的各個方法,必須持有Model對象和View對象private final UserRepository mUserRepository; private final LoginContract.View mLoginView;
然后,你想讓View干嘛,調用View相對應的接口就行了,想要什么數據,想對數據做什么操作,調用Model對象的對應方法就行了;或許你已經發現了:
Presenter對View的操作都是通過接口來完成的。
- 5.Activity的角色,看Google官方Sample里怎么介紹的:
It uses fragments for two reasons:
The separation between Activity and Fragment fits nicely with this implementation of MVP: the Activity is the overall controller that creates and connects views and presenters.
Tablet layout or screens with multiple views take advantage of the Fragments framework.
獻丑二進宮:
(MVP中的View實現)使用Fragment有兩個原因:
Activity與Fragment之間的分離很好的符合了MVP的實現:Activity作為整體控制器來創建和連接views與presenters。
Tablet布局或者屏幕上有多個views的布局可以很好的利用Fragments框架。
在MVP模式里,Activity的功能變得簡單了很多,一是創建View布局(Fragment),二是實例化Presenter(LoginPresenter),并將View(Fragment)作為參數,傳入到Presenter(LoginPresenter)中,在Presenter(LoginPresenter)構造函數中傳遞給Presenter(LoginPresenter)持有的View對象,然后View對象調用setPresenter方法,將自身this傳遞給View實例(Fragment)。也就是上面說的“創建和連接views與presenters。”
來看看Activity代碼多簡單:
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
LoginFragment loginFragment = (LoginFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
// Create the view
if (loginFragment == null) {
loginFragment = LoginFragment.newInstance("LOGIN_FRAGMENT");
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.contentFrame, loginFragment);
transaction.commit();
// Create the presenter
new LoginPresenter(
getApplicationContext(),
UserRepository.getInstance(
UserLocalDataSource.getInstance(getApplicationContext()),
UserRemoteDataSource.getInstance()),
loginFragment);
}
}
至此,MVP模式里的VP就可以運行起來了,連通起來了。下面來說說Model。如果對MVC的Model非常熟悉可以跳過。
-
6.Model的創建
在目錄結構圖中,整個data package里的都是Model的內容,包括實體模型(User類)、本地數據庫操作(local package)、遠程數據訪問(remote package)三部分,跟在MVC里并無差別,這里不展開介紹。業務邏輯需要什么樣的數據實體、數據操作,在對應的包里面構建就行了,這里要提到的是,Presenter對Model的持有,這里也是通過接口實現的,間接通過UserDataSource接口類,直接通過UserDataSource的實現類UserRepository。而UserRepository同時持有對本地數據和遠程數據操作的對象:
private final UserDataSource mLocalDataSource;
private final UserDataSource mRemoteDataSource;
MVP實踐總結
使用MVP模式也有一些不盡如人意的地方,比如,類和接口變多了,代碼也多了,項目大了可能會不好管理,但這都不是事,用多點代碼、多點類文件換取低耦合度、結構清晰、容易理解、易擴展的架構,這買賣值了。
第一遍看的時候懵逼沒關系,再看一遍,認真的解讀MVP的設計思路,參考代碼,發現其實真的是很清晰的思路,并不難,難的是啃下來的決心。這碗雞湯我干了,你們隨意。
相信看完這篇文章會對你理解MVP有所幫助,如果你還是一臉懵逼的話,請舉起手來:
[圖片上傳失敗...(image-e459b1-1521790364530)]
最后,奉上本文的源碼GitHub:Login-MVP-Architecture,如果覺得有用,點個Star表示支持。