Android MVP設計 定制你的減肥模式

什么是MVP

現在,設計一款新的APP,對于Android而言,至少都是什么Material Design,然后可能又是什么MVP什么什么的。在MVCMVVM都還沒有弄明白的時候,又出來了新的MVP,是不是有種淡淡的憂傷??
那么問題來了,到底什么是MVP呢?

MVP 是從經典的模式MVC演變而來,它們的基本思想有相通的地方:Controller/Presenter負責邏輯的處理,Model提供數據,View負責顯示。作為一種新的模式,MVPMVC 有著一個重大的區別:在MVPView 并不直接使用Model,它們之間的通信是通過 Presenter (MVC 中的Controller)來進行的,所有的交互都發生在Presenter內部,而在MVCView會直接從Model中讀取數據而不是通過 Controller
MVC里,View是可以直接訪問Model的!從而,View里會包含Model信息,不可避免的還要包括一些業務邏輯。 在MVC模型里,更關注的Model的不變,而同時有多個對Model的不同顯示,及View。所以,在MVC模型里,Model不依賴于View,但是View是依賴于Model的。不僅如此,因為有一些業務邏輯在View里實現了,導致要更改View也是比較困難的,至少那些業務邏輯是無法重用的。

以上是百度百科MVP設計優勢的一些解釋。結合之前的MVC設計模式的話,其實MVP就是更加簡化了ActivityView的職責,讓他們交出了數據管理以及相關業務邏輯處理的權利,而是純粹的經行UI相關的操作!!(比如說控制View的顯示和隱藏,數據的相關展現。)

MVP的好處

MVC

MVP


根據上面兩個圖示可以看出,MVPMVC的區別在于功能模塊訪問的權限,在MVP中,View層和Mode數據層是不能直接有聯系的。而是必須通過Presenter層來完成一個統一的調度,Presenter充當了View層和Mode層的中間橋梁,這樣的好處就是實現了View和數據業務的完全解耦,是的邏輯層次更加分明,利于代碼的維護!這個也滿足設計中要求的單一職責原則。

MVP設計示例

大概說完了好處,那么就要說說剛入手MVP設計時的一些感覺不爽的地方,采用MVP設計之后,整體的項目層級大概就是醬紫啦!

package

data就是Model層相關的封裝,包括api請求等等,presenters就是管理ViewModel的中間層,view就是Activity相關的封裝。
可以看到在presenterview中都有相關的接口,在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
    PresenterView的交互中,在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;
}

這樣就完成了ActivityPresenter的交互回調了。

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、PresenterView的對應關系?是一對一?是多對一?還是一對多?

這個關系我覺得都是可以出現的,但是一般情況會是一對一的,然后P->V 一對多也是有這種情況的,比如說在RecycleView中出現了幾種布局,每種布局的相關邏輯要在P中經行處理,那么就會出現一對多的情況!至于多對一的情況,我覺得這個相對還要少一些吧!處理中心應該是獨一的一個!

  • 3、采用MVP時不知道對應的ViewPresenter應該定義那些抽象的方法??

這個問題其實也并不是問題,不要就是在那里空想,其實你把層次分好了,缺什么方法到需要的時候自然就知道了,這個時候再添加到對應的接口中就好了!也沒有人能一次性就把所有的情況都考慮完全,所以多想多寫,多積累相關經驗。

  • 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,寫出來的代碼那是一個飄逸!!!

相關下載

登陸示例

知否 MVP+Retrofit

知否
知否

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容