Android MVP 十分鐘入門!

前言

在平常開發APP 的過程當中,隨着業務的擴展,規模的變化。咱們的代碼規模也會逐漸變得龐大,每個類裏的代碼也會逐漸增多。尤爲是Activity和Fragment ,因爲Context 的存在,基本上全部對視圖的操做咱們只能在Activity和Fragment中完成;即使是對某些邏輯進行封裝,Activity和Fragment 依舊會顯得過於臃腫。所以,咱們須要換一種思路去寫代碼,這個時候MVP模式就應用而生了!那麼MVP 怎麼用呢,下面就來講一說。java

假設你如今如要實現下圖中的功能:android

這個需求很簡單,就是點擊按鈕,下載一張圖片,顯示下載進度;下載完成後,在ImageView中顯示這張圖片。
下面咱們就分別用傳統的方式(也就是所謂的MVC)和MVP 模式分別取實現這個功能。而後分析一下MVP 到底好在哪裏。git

MVC

public class MVCActivity extends AppCompatActivity {

    private Context mContext;
    private ImageView mImageView;
    private MyHandler mMyHandler;
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvc);
        mContext = this;
        init();
    }

    private void init() {
        //view init
        mImageView = (ImageView) findViewById(R.id.image);
        mMyHandler = new MyHandler();

        progressDialog = new ProgressDialog(mContext);
        progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                progressDialog.dismiss();
            }
        });
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setTitle("下載文件");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);


        //click-event
        findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressDialog.show();
                HttpUtil.HttpGet(Constants.DOWNLOAD_URL, new DownloadCallback(mMyHandler));
            }
        });

        findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressDialog.show();
                HttpUtil.HttpGet(Constants.DOWNLOAD_ERROR_URL, new DownloadCallback(mMyHandler));
            }
        });

    }


    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 300:
                    int percent = msg.arg1;
                    if (percent < 100) {
                        progressDialog.setProgress(percent);
                    } else {
                        progressDialog.dismiss();
                        Glide.with(mContext).load(Constants.LOCAL_FILE_PATH).into(mImageView);
                    }
                    break;
                case 404:
                    progressDialog.dismiss();
                    Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    }
}複製代碼

用mvc的方式,一個Activity就能搞定。代碼邏輯很簡單,點擊按鈕後顯示以前初始化好ProgressDialog,而後開始下載任務(這裏HttpUtil 內部簡單封裝了OKHttp 的異步GET請求,實現下載文件保存到本地的功能,實現細節在此不作深刻探討,有興趣的同窗能夠查看源碼),而後將請求結果經過Handler返回,在handleMessage中根據返回數據的信息作出不一樣的UI 處理;下載成功時在ImageView中顯示圖片,下載失敗時Toast提示。github

能夠發現,在這種狀況以前,Activity的任務十分繁重,既要負責下載任務的具體實施,還要根據下載進行再次的邏輯判斷,才能去更新UI。這裏只是一個簡單的任務,你可能以爲無所謂,可是實際開發中,一個Activity中有許多的交互事件,這個時候Activity的代碼就顯得特別的龐大;一旦需求變動或出現bug,那簡直就是噩夢一場。網絡

所以,咱們但願Activity能夠變成下面這樣架構

  • 他負責發起處理和用戶交互的內容,但又不負責具體的實現;
  • 須要顯示什麼,不顯示什麼,什麼東西顯示多少,有個東西能夠直接告訴他,
  • Activity再也不作複雜的邏輯處理;

具體到上面的demo裏就是,Activity負責發起下載任務,可是不負責具體實現;何時顯示ProgressDialog,顯示多少?何時提示錯誤信息,這一切都但願有個東西能直接告訴Activity,而再也不是在Activity裏再作判斷。怎樣才能作到呢?那就得靠MVP 了。mvc

MVP

MVP 模式所作的事情很簡單,就是將業務邏輯和視圖邏輯抽象到接口中。異步

怎麼理解呢,咱們就根據這次要實現的下載功能,用代碼說話。ide

定義Model,View,Presenter 接口

Model Interface

Model 接口定義全部須要實現的業務邏輯,在咱們的下載任務中,業務邏輯只有一個,就是下載;所以Model 接口能夠這麼定義 :佈局

public interface IDownloadModel {
    /** * 下載操做 * @param url */
    void download(String url);
}複製代碼

View Interface

View 接口定義全部須要實現的視圖邏輯,在咱們的下載任務中,視圖邏輯包括

  • 顯示ProgressDialog;
  • 顯示Dialog具體進度;
  • 顯示具體的View(設置圖片);
  • 顯示錯誤信息(Toast提示)

所以View接口能夠這麼定義:

public interface IDownloadView {
    /** * 顯示進度條 * @param show */
    void showProgressBar(boolean show);

    /** * 設置進度條進度 * @param progress */
    void setProcessProgress(int progress);

    /** * 根據數據設置view * @param result */
    void setView(String result);

    /** * 設置請求失敗時的view */
    void showFailToast();
}複製代碼

Presenter Interface

Presenter 接口做爲鏈接Model和View的中間橋樑,須要將兩者鏈接起來,所以他須要完成如下工做:

  • 執行下載任務
  • 下載成功返回下載結果
  • 下載過程返回下載進度
  • 下載失敗回調

所以,Presenter 就能夠這麼定義:

public interface IDowndownPresenter {
    /** * 下載 * @param url */
    void download(String url);

    /** * 下載成功 * @param result */
    void downloadSuccess(String result);

    /** * 當前下載進度 * @param progress */
    void downloadProgress(int progress);

    /** * 下載失敗 */
    void downloadFail();
}複製代碼

接口Model,View,Presenter 具體實現

上面實現了,各個接口的定義,下面來看看他們具體的實現:

Model 具體實現

public class DownloadModel implements IDownloadModel {
    private IDowndownPresenter mIDowndownPresenter;
    private MyHandler mMyHandler = new MyHandler();

    public DownloadModel(IDowndownPresenter IDowndownPresenter) {
        mIDowndownPresenter = IDowndownPresenter;
    }

    @Override
    public void download(String url) {
        HttpUtil.HttpGet(url, new DownloadCallback(mMyHandler));
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 300:
                    int percent = msg.arg1;
                    if (percent < 100) {
                        mIDowndownPresenter.downloadProgress(percent);
                    } else {
                        mIDowndownPresenter.downloadSuccess(Constants.LOCAL_FILE_PATH);
                    }
                    break;
                case 404:
                    mIDowndownPresenter.downloadFail();
                    break;
                default:
                    break;
            }
        }
    }
}複製代碼

在MVP模式中,Model的工做就是完成具體的業務操做,網絡請求,持久化數據增刪改查等任務。同時Model中又不會包含任何View。
這裏Model的具體實現很簡單,將Http任務的結果返回到Handler當中,而在Handler中的實現又是由Presenter完成。
那麼Presenter接口又是怎樣實現的呢?趕忙來看看

Presenter 具體實現

public class DownloadPresenter implements IDowndownPresenter {
    private IDownloadView mIDownloadView;
    private IDownloadModel mIDownloadModel;


    public DownloadPresenter(IDownloadView IDownloadView) {
        mIDownloadView = IDownloadView;
        mIDownloadModel = new DownloadModel(this);
    }

    @Override
    public void download(String url) {
        mIDownloadView.showProgressBar(true);
        mIDownloadModel.download(url);
    }

    @Override
    public void downloadSuccess(String result) {
        mIDownloadView.showProgressBar(false);
        mIDownloadView.setView(result);
    }

    @Override
    public void downloadProgress(int progress) {
        mIDownloadView.setProcessProgress(progress);
    }

    @Override
    public void downloadFail() {
        mIDownloadView.showProgressBar(false);
        mIDownloadView.showFailToast();
    }
}複製代碼

能夠看到,咱們在DownloadPresenter的構造方法中,同時實例化了Model和View,這樣Presenter中就同時包含了二者;
這樣;在Presenter具體實現中,業務相關的操做由Model去完成(例如download),視圖相關的操做由View去完成
(如setView等)
。Presenter 做爲橋樑的做用就這樣體現出來了,巧妙的將View和Model的具體實現鏈接了起來。

View具體實現

最後再看一下View接口的具體實現,也就是Activity的實現:

public class MVPActivity extends AppCompatActivity implements IDownloadView {
    private Context mContext;
    private ImageView mImageView;
    private ProgressDialog progressDialog;

    private DownloadPresenter mDownloadPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        setContentView(R.layout.activity_mvp);
        init();
    }

    private void init() {
        mDownloadPresenter = new DownloadPresenter(this);
        //view init
        mImageView = (ImageView) findViewById(R.id.image);
        findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDownloadPresenter.download(Constants.DOWNLOAD_URL);
            }
        });

        findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDownloadPresenter.download(Constants.DOWNLOAD_ERROR_URL);
            }
        });

        progressDialog = new ProgressDialog(mContext);
        progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                progressDialog.dismiss();
            }
        });
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setTitle("下載文件");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

    }

    @Override
    public void showProgressBar(boolean show) {
        if (show) {
            progressDialog.show();
        } else {
            progressDialog.dismiss();
        }
    }

    @Override
    public void setProcessProgress(int progress) {
        progressDialog.setProgress(progress);
    }

    @Override
    public void setView(String result) {
        Glide.with(mContext).load(result).into(mImageView);
    }

    @Override
    public void showFailToast() {
        Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
    }
}複製代碼

在點下按鈕執行開始下載任務的時候,View(Activity)中沒有具體的實現,只是調用了Presenter中的download方法,而Presenter中的download又會去調用Model的download方法,Model又會在根據具體邏輯(在這裏就是Http請求)的狀態去調用Presenter中的方法,例如咱們在handleMessage方法中,調用mIDowndownPresenter.downloadProgress(percent)時,就會去調用Presenter的具體實現

@Override
    public void downloadProgress(int progress) {
        mIDownloadView.setProcessProgress(progress);
    }複製代碼

而他的內部實現又是操做具體的View,也就是咱們在Activity中初始化Presenter中傳遞的this,也就是當前Activity(View),這樣最終回到了Activity中的

@Override
    public void setProcessProgress(int progress) {
        progressDialog.setProgress(progress);
    }複製代碼

咱們爲progressDialog 設置進度。

至此,咱們就經過MVP 的模式實現了咱們以前所設想的Activity

  • Button的click方法負責發起下載任務,但又不負責具體實現,而是由Presenter轉接給Model去實現
  • Activity 何時顯示ProgressDialog,何時顯示Toast直接由Presenter告訴他,他只作一個View想作的事情
  • Activity裏沒有任何邏輯處理,全部的邏輯判斷都在Model中完成了。

這就是MVP !!!

MVC VS MVP

經過上面的兩種實現方案,相信每一個人都已經理解了MVC和MVP的區別;下面就其各自的優缺點再作一下
總結;固然,這裏的優缺點只是相對而言

優勢

MVC

MVP

上面兩張圖分別是MVC和MVP架構圖。相信許多和我同樣嘗試去學習和了解MVP架構的同窗對這兩圖(或相似的圖)並不陌生。

結構更加清晰*

咱們回過頭再去看MVCActivity 的實現,暫且將咱們對Http請求的封裝歸結爲Model(M),那麼剩下的就只有Activity了,而這個Activity即實現視圖邏輯,又須要實現部分業務邏輯,也就是說他既是Controller(C)又是View(V)。V和C的劃分徹底不清晰;所以,傳統的代碼結構只能勉強稱爲MV 或者是MC,若是算上xml 的佈局文件,才能牽強的稱爲MVC 結構。

而MVP 就不一樣了,Model,View,Presenter各司其職,互相搭配,實現瞭解耦,徹底解放了Activity(或者是Fragment)。這就是MVP 的優點,代碼結構更加清晰。能夠這樣說,同一個模塊的實現,甚至容許幾我的分工完成;假設有一個很是複雜的Activity,若是使用MVP 的模式開發;那麼這個時候,定義好MVP的接口以後,就能夠有人專門去作Model,另外一我的專門去作View;再由一我的寫Presenter的代碼,固然這須要極強的代碼規範和協做能力;但這在傳統的MVC模式中根本是沒法想象的,全部的東西都在一個類裏,兩我的一塊兒改,有了衝突怎麼玩/(ㄒoㄒ)/~~。

需求變動,再也不是噩夢

假設如今有新的需求,產品經理認爲下載失敗後只有一個Toast提示太單調了(並且用戶有可能錯過了這Toast的顯示,而誤覺得APP失去了響應),所以,如今但願在下載失敗後彈出一個Dialog,能夠重試下載任務。是想,若是代碼使用傳統的MVC 結構,恰巧這個代碼不是你寫的,或者說就是你寫的,可是你已經忘記了具體的邏輯;那麼爲了實現這個需求你又得去從新捋一遍邏輯,到某個類的xxx行進行修改;可是若是使用MVP就不一樣了View接口已經定義好了showFailToast就是用來顯示錯誤提示的;所以即使代碼不是你寫的,你均可以很快的找到,應該去哪裏改;而省去不少時間。

更容易寫單元測試

這個就不展開說了,總之寫過單元測試的人應該都有這樣的體會。

缺點

MVP這麼好,也不是沒有缺點。

如圖中所示,使用MVP 架構以後,多出了許多類;這是必然的;每個View(Activity或Fragment)都至少須要各自的Model、Presenter和View接口,在加上他們各自的實現,也就是說每個頁面都會有6個java文件(算上Fragment或Activity,由於他就是View的實現),這樣一個稍有點規模的APP,類就會變得異常的多,而每個類的加載又會消耗資源;所以,相較於MVC,這算是MVP最大的缺點了吧。

固然,對於這個問題咱們能夠經過泛型參數、抽象父類的方式,將一些公用的Model及Presenter抽象出來。這應該就是使用MVP架構的精髓了。

最後

我的感受,使用MVP 架構是利大於弊的;隨着項目規模的增長,代碼邏輯的清晰纔是最重要的事情。何況Google官方也出推出了一系列關於MVP的使用demo
所以,這也是官方提倡你們使用的。凡事,有利必有弊;類數目的增加是沒法避免的事情,所以如何使用泛型和抽象優化MVP 的結構就變成了咱們用好
MVP的關鍵了。

固然,咱們不能爲了MVP而去MVP,若是項目結構不是很龐大,業務不是很複雜;那麼傳統的MVC 架構足以,並且也方便!


年前的最後一個工做日了,我竟然寫了一篇學習筆記;今天必定是上了假的班兒!明天回家過年,O(∩_∩)O哈哈哈~!每個人,新年快樂!

相關文章
相關標籤/搜索