Android中的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裏,Presenter徹底把Model和View進行了分離,主要的程序邏輯在Presenter裏實現。並且,Presenter與具體的View是沒有直接關聯的,而是經過定義好的接口進行交互,從而使得在變動View時候能夠保持Presenter的不變,即重用! 不只如此,咱們還能夠編寫測試用的View,模擬用戶的各類操做,從而實現對Presenter的測試--而不須要使用自動化的測試工具。 咱們甚至能夠在Model和View都沒有完成時候,就能夠經過編寫Mock Object(即實現了Model和View的接口,但沒有具體的內容的)來測試Presenter的邏輯。 在MVP裏,應用程序的邏輯主要在Presenter來實現,其中的View是很薄的一層。所以就有人提出了Presenter First的設計模式,就是根據User Story來首先設計和開發Presenter。在這個過程當中,View是很簡單的,可以把信息顯示清楚就能夠了。在後面,根據須要再隨便更改View,而對Presenter沒有任何的影響了。 若是要實現的UI比較複雜,並且相關的顯示邏輯還跟Model有關係,就能夠在View和Presenter之間放置一個Adapter。由這個 Adapter來訪問Model和View,避免二者之間的關聯。而同時,由於Adapter實現了View的接口,從而能夠保證與Presenter之間接口的不變。這樣就能夠保證View和Presenter之間接口的簡潔,又不失去UI的靈活性。 在MVP模式裏,View只應該有簡單的Set/Get的方法,用戶輸入和設置界面顯示的內容,除此就不該該有更多的內容,毫不允許直接訪問Model--這就是與MVC很大的不一樣之處。ide

MVP模式的核心思想:工具

MVP把Activity中的UI邏輯抽象成View接口,把業務邏輯抽象成Presenter接口,Model類仍是原來的Modeloop

這就是MVP模式,如今這樣的話,Activity的工做的簡單了,只用來響應生命週期,其餘工做都丟到Presenter中去完成。從上圖能夠看出,Presenter是Model和View之間的橋樑,爲了讓結構變得更加簡單,View並不能直接對Model進行操做,這也是MVP與MVC最大的不一樣之處。post

 

MVP模式的做用

MVP的好處都有啥,誰說對了就給他 KIRA!!(<ゝω·)☆單元測試

  • 分離了視圖邏輯和業務邏輯,下降了耦合測試

  • Activity只處理生命週期的任務,代碼變得更加簡潔this

  • 視圖邏輯和業務邏輯分別抽象到了View和Presenter的接口中去,提升代碼的可閱讀性

  • Presenter被抽象成接口,能夠有多種具體的實現,因此方便進行單元測試

  • 把業務邏輯抽到Presenter中去,避免後臺線程引用着Activity致使Activity的資源沒法被系統回收從而引發內存泄露和OOM

其中最重要的有三點:

Activity 代碼變得更加簡潔

相信不少人閱讀代碼的時候,都是從Activity開始的,對着一個1000+行代碼的Activity,看了都以爲難受。

使用MVP以後,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其餘的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,並且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,之後要調整業務、刪減功能也就變得簡單許多。

方便進行單元測試

通常單元測試都是用來測試某些新加的業務邏輯有沒有問題,若是採用傳統的代碼風格(習慣性上叫作MV模式,少了P),咱們可能要先在Activity裏寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時若是發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧從新寫吧……

MVP中,因爲業務邏輯都在Presenter裏,咱們徹底能夠寫一個PresenterTest的實現類繼承Presenter的接口,如今只要在Activity裏把Presenter的建立換成PresenterTest,就能進行單元測試了,測試完再換回來便可。萬一發現還得進行測試,那就再換成PresenterTest吧。

避免 Activity 的內存泄露

Android APP 發生OOM的最大緣由就是出現內存泄露形成APP的內存不夠用,而形成內存泄露的兩大緣由之一就是Activity泄露(Activity Leak)(另外一個緣由是Bitmap泄露(Bitmap Leak))。

Java一個強大的功能就是其虛擬機的內存回收機制,這個功能使得Java用戶在設計代碼的時候,不用像C++用戶那樣考慮對象的回收問題。然而,Java用戶老是喜歡隨便寫一大堆對象,而後幻想着虛擬機能幫他們處理好內存的回收工做。但是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用着的對象由於還可能會被調用,因此不能回收。

Activity是有生命週期的,用戶隨時可能切換Activity,當APP的內存不夠用的時候,系統會回收處於後臺的Activity的資源以免OOM。

採用傳統的MV模式,一大堆異步任務和對UI的操做都放在Activity裏面,好比你可能從網絡下載一張圖片,在下載成功的回調裏把圖片加載到 Activity 的 ImageView 裏面,因此異步任務保留着對Activity的引用。這樣一來,即便Activity已經被切換到後臺(onDestroy已經執行),這些異步任務仍然保留着對Activity實例的引用,因此係統就沒法回收這個Activity實例了,結果就是Activity Leak。Android的組件中,Activity對象每每是在堆(Java Heap)裏佔最多內存的,因此係統會優先回收Activity對象,若是有Activity Leak,APP很容易由於內存不夠而OOM。

採用MVP模式,只要在當前的Activity的onDestroy裏,分離異步任務對Activity的引用,就能避免 Activity Leak。

 

MVP模式簡單實例

一個簡單的登陸界面(實在想不到別的了╮( ̄▽ ̄")╭),點擊LOGIN則進行帳號密碼驗證,點擊CLEAR則重置輸入。

項目結構看起來像是這個樣子的,MVP的分層仍是很清晰的。個人習慣是先按模塊分Package,在模塊下面再去建立model、view、presenter的子Package,固然也能夠用model、view、presenter做爲頂級的Package,而後把全部的模塊的model、view、presenter類都到這三個頂級Package中,就好像有人喜歡把項目裏全部的Activity、Fragment、Adapter都放在一塊兒同樣。

首先來看看LoginActivity

public class LoginActivity extends ActionBarActivity implements ILoginView, View.OnClickListener {

    private EditText editUser;
    private EditText editPass;
    private Button   btnLogin;
    private Button   btnClear;
    ILoginPresenter loginPresenter;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //find view
        editUser = (EditText) this.findViewById(R.id.et_login_username);
        editPass = (EditText) this.findViewById(R.id.et_login_password);
        btnLogin = (Button) this.findViewById(R.id.btn_login_login);
        btnClear = (Button) this.findViewById(R.id.btn_login_clear);
        progressBar = (ProgressBar) this.findViewById(R.id.progress_login);

        //set listener
        btnLogin.setOnClickListener(this);
        btnClear.setOnClickListener(this);

        //init
        loginPresenter = new LoginPresenterCompl(this);
        loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_login_clear:
                loginPresenter.clear();
                break;
            case R.id.btn_login_login:
                loginPresenter.setProgressBarVisiblity(View.VISIBLE);
                btnLogin.setEnabled(false);
                btnClear.setEnabled(false);
                loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());
                break;
        }
    }

    @Override
    public void onClearText() {
        editUser.setText("");
        editPass.setText("");
    }

    @Override
    public void onLoginResult(Boolean result, int code) {
        loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
        btnLogin.setEnabled(true);
        btnClear.setEnabled(true);
        if (result){
            Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
            startActivity(new Intent(this, HomeActivity.class));
        }
        else
            Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
    }


    @Override
    public void onSetProgressBarVisibility(int visibility) {
        progressBar.setVisibility(visibility);
    }
}

從代碼能夠看出LoginActivity只作了findView以及setListener的工做,並且包含了一個ILoginPresenter,全部業務邏輯都是經過調用ILoginPresenter的具體接口來完成。因此LoginActivity的代碼看起來很舒爽,甚至有點愉♂悅呢 (/ω\*)。視力不錯的你可能還看到了ILoginView接口的實現,若是不懂爲何要這樣寫的話,能夠先往下看,這裏只要記住LoginActivity實現了ILoginView接口

再來看看ILoginPresenter

public interface ILoginPresenter {
    void clear();
    void doLogin(String name, String passwd);
    void setProgressBarVisiblity(int visiblity);
}
public class LoginPresenterCompl implements ILoginPresenter {
    ILoginView iLoginView;
    IUser user;
    Handler    handler;

    public LoginPresenterCompl(ILoginView iLoginView) {
        this.iLoginView = iLoginView;
        initUser();
        handler = new Handler(Looper.getMainLooper());
    }

    @Override
    public void clear() {
        iLoginView.onClearText();
    }

    @Override
    public void doLogin(String name, String passwd) {
        Boolean isLoginSuccess = true;
        final int code = user.checkUserValidity(name,passwd);
        if (code!=0) isLoginSuccess = false;
        final Boolean result = isLoginSuccess;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                iLoginView.onLoginResult(result, code);
            }
        }, 3000);

    }

    @Override
    public void setProgressBarVisiblity(int visiblity){
        iLoginView.onSetProgressBarVisibility(visiblity);
    }

    private void initUser(){
        user = new UserModel("mvp","mvp");
    }
}

從代碼能夠看出,LoginPresenterCompl保留了ILoginView的引用,所以在LoginPresenterCompl裏就能夠直接進行UI操做了,而不用在Activity裏完成。這裏使用了ILoginView引用,而不是直接使用Activity,這樣一來,若是在別的Activity裏也須要用到相同的業務邏輯,就能夠直接複用LoginPresenterCompl類了(一個Activity能夠包含一個以上的Presenter,總之,須要什麼業務就new什麼樣的Presenter,是否是很靈活(@ ̄︶ ̄@)),這也是MVP的核心思想

經過IVIew和IPresenter,把Activity的UI LogicBusiness Logic分離開來,Activity just does its basic job! 至於Model嘛,仍是原來MVC裏的Model。

再來看看ILoginView,至於ILoginView的實現類呢,翻到上面看看LoginActivity吧

public interface ILoginView {
    public void onClearText();
    public void onLoginResult(Boolean result, int code);
    public void onSetProgressBarVisibility(int visibility);
}
相關文章
相關標籤/搜索