Android中MVP小試牛刀

前言

世代在進步,技術在發(fā)展,當自己剛換了一份工作的時候,越來越多的聽到MVP,那么今天就趁此機會來學習一下MVP,對他進行一個徹底的了解

MVP概述

MVP其實同MVC一樣蚓耽,是一種編程模式和思想靖诗,也許更準確地講是一種架構语婴。

MVP分析

況且,隨著項目的深入發(fā)展,很多邏輯很越來越復雜航瞭,Activity處理的東西也會越來越多荤西,代碼越來越臃腫。這樣一來維護起來的代價就會越來越高敞掘,這是因為View的變化會引起Controller的很多變化,反之亦然楣铁。用一句大白話來說明就是–某一段代碼的變動會引起很多其他相關聯的代碼的改動玖雁,而程序員都是懶惰的,所以會恨死這樣的代碼盖腕。
而MVP就是要減輕在Android中的這種困惑赫冬。
View與Model并不直接交互浓镜,而是使用Presenter作為View與Model之間的橋梁。其中Presenter中同時持有Viwe層以及Model層的Interface的引用劲厌,而View層持有Presenter層Interface的引用竖哩。當View層某個界面需要展示某些數據的時候,首先會調用Presenter層的某個接口脊僚,然后Presenter層會調用Model層請求數據相叁,當Model層數據加載成功之后會調用Presenter層的回調方法通知Presenter層數據加載完畢,最后Presenter層再調用View層的接口將加載后的數據展示給用戶辽幌。這就是MVP模式的整個核心過程增淹。
MVP是基于MVC的,它的架構圖如下:


image.png

MVP是模型(Model)乌企、視圖(View)虑润、主持人(Presenter)的縮寫,分別代表項目中3個不同的模塊加酵。

  • M(Model) 數據相關層,model是整個應用或界面的數據加工處理廠拳喻,所謂數據加工廠就是對數據的獲取,數據的解析猪腕,數據的存儲冗澈,數據的分發(fā),數據的增刪改查等操作陋葡。意思就是凡是涉及到數據操作都是在model進行的亚亲,所以model不僅僅只是實體類的集合,同時還包含關于數據的各種處理操作,負責處理數據的加載或者存儲腐缤,比如從網絡或本地數據庫獲取數據等捌归;
  • V(View) 視圖層,就是用戶直接看到的界面,負責界面數據的展示岭粤,與用戶進行交互惜索;如Activity,一個view可以同時擁有多個presenter,也可以只有一個presenter
  • P(Presenter) 紐帶層剃浇,首先presenter是處于mvp的中間層巾兆,將模型與視圖分離開來,用來連接Model與View會把view交給自己的命令進行一定的校驗等操作交給model處理,會把model處理的結果交給view,

MVP開發(fā)在Android中的基本流程

  1. View層定義View.interface偿渡,用來定義View的行為臼寄。一般由Activity或者是Fragment來實現這個接口,它定義了View視圖的各種變化溜宽,如設置Textview,加載對話框,更新進度條等质帅。
  2. Model層定義Modle.interface,這個是用來定義數據層發(fā)生變化時的通知接口适揉,因為Model不能直接與View交互留攒,所以它與Presenter交互,然后再通過Presenter間接達到與View的交互嫉嘀。
  3. Presenter翻譯的意思是主持人炼邀,也就是主持場合,控制節(jié)奏的意思剪侮。在這時Presenter就負責具體的業(yè)務邏輯拭宁,請求數據,把數據送到Model瓣俯,或者監(jiān)聽Model的數據變化杰标,接受View層的動作,負責通過通知View層的視圖變化彩匕。

如果跟MVC的架構圖對比的話腔剂,可以發(fā)現它們有相似之處也有不同。

相似之處

模塊劃分的相似
MVC由Model驼仪、View掸犬、Controller構成。
MVP由Model绪爸、View湾碎、Presenter構成。

不同的地方

  1. MVP中Presenter取代了MVC中的Controller
  2. MVC中Model、View、Controller之間相互發(fā)生通信栋荸,而MVP中Model與Presenter相互通信斜纪,View與Presenter相互通信,而Model與View之間沒有通信河哑。

Android中MVP的好處?

就Android層面上來講MVC架構雖然好,但不是最好廊遍,情況前面有講過。用一句話概括就是“模塊界限很模糊”贩挣。而MVP的出現實際上就是將MVC進行升級

1喉前、學習過設計模式的人都知道,這樣做基本符合了單一職責原則王财。
2卵迂、符合單一職責原則后,導致類與類組織更清晰绒净。
3见咒、對應Android開發(fā)中就是幫助Activity解壓,View層與Model層交互需要通過Presenter層進行,這樣v與m層級間的耦合性降低挂疆。
4改览、MVC中Activity同時充當了V和C的角色下翎,這就屬于界限劃分不清楚。而MVP則劃分的很清楚宝当,Activity只充當V的角色视事,業(yè)務邏輯控制交給了Presenter.通過這種分層處理,每一層的測試也相對簡單庆揩,維護性更高俐东。

個人對MVP模式的理解

這一段是我自己的看法,也許不正確订晌。
我個人覺得MVP沒有什么很神秘的虏辫,因為Android SDK上開發(fā),本來就差不多是MVC的角色腾仅。Activity基本上Android開發(fā)中最重要的一環(huán)乒裆。
我以前在團隊工作的時候,團隊分工是每人負責相應的Activity,在這里Activity是最小的開發(fā)單元推励。再后來鹤耍,某些Activity變得越來越重要,越來越復雜验辞,代碼也越來越多稿黄,這樣會造成團隊某個人的開發(fā)任務重,而其他的團隊成員也幫不上忙跌造。而MVP的出現可以將Activity再細分杆怕,劃為View和Presenter兩個部分,所以Activity不再是最小的開發(fā)單元壳贪,如果可以完全可以這樣分配任務陵珍,一個開發(fā)人員負責View部分,另一個開發(fā)人員負責Presenter部分违施。
況且因為MVP的劃分互纯,所以各個部分其實相對獨立,V的變動會對P的部分造成較少的影響磕蒲,而M對V或者說V對M幾乎是透明的留潦。
因為Presenter的存在,View和Model就可以很輕松辣往,頂多Presenter累一點兔院。
還有一個特點是MVP模式很適合測試,單獨測試VIEW成了一種可能站削。我們可以模擬View和Model的數據來測試Presenter的邏輯坊萝。

MVP實戰(zhàn)

所以,我用一個簡單的DEMO來講解,大家一看就明白屹堰。

場景需求

假設現在需要做一款APP肛冶,就是顯示天氣街氢,界面很簡單扯键,一個TextView顯示天氣信息,一個Button用來請求實時天氣珊肃。
如下圖所示


image.png

軟件啟動后荣刑,會自動獲取天氣,然后TextView就可以顯示信息伦乔。而用戶點擊獲取實時天氣的按鈕厉亏,界面上會彈出正在獲取中的進度對話框,等待數據加載成功后烈和,對話框消失爱只。Textview顯示就新的天氣情況。


1.gif

代碼開發(fā),包的組織

因為選定MVP模式招刹,所以第一步就是包的組織恬试。


image.png

View層的接口定義及實現

在MVP中Activity用來專注視圖的表現。
而在本例子中View的表現有哪些呢疯暑?很多教程直接就上來貼代碼训柴,個人覺得這樣是不好的。View的表現當然要用View.interface接口來定義
現在我們來分析一下妇拯,在本例中View應該有哪些表現幻馁。
1.顯示天氣信息
接口方法定義

void onInfoUpdate(String info);

2.顯示獲取信息等待對話框
接口方法定義

void showWaitingDialog();

3.取消顯示對話框
接口方法定義

void dissmissWaitingDialog();

最終View層的接口定義就完成了,完整代碼如圖所示:

public interface IWetherView {

    void onInfoUpdate(String info);

    void showWaitingDialog();

    void dissmissWaitingDialog();
}

接口文件已經定義好了,那么View的實現呢越锈?在這里用MainActivity去實現它仗嗦。

public class MainActivity extends AppCompatActivity implements IWetherView{

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

    @Override
    public void onInfoUpdate(String info) {

    }

    @Override
    public void showWaitingDialog() {

    }

    @Override
    public void dissmissWaitingDialog() {

    }
}

具體的業(yè)務代碼,我們等會再實現甘凭。
Model層的接口定義及實現
Model層是數據層稀拐,用來存儲數據并且提供數據。在這里為了便于演示对蒲,數據被簡化為了String類型钩蚊。
接口定義如下:

public interface IWetherModel {

    //提供數據
    public String getInfo();

    //存儲數據
    public void setInfo(String info);
}

它的實現文件如下:

public class IWetherImpl implements IWetherModel {

    @Override
    public String getInfo() {
        return null;
    }

    @Override
    public void setInfo(String info) {

    }
}

Presenter代碼及實現
Presenter是個大忙人,因為要同時對View和Model對接蹈矮,所以內部必須持有它們的接口引用砰逻。
所以有如下:

public class WetherPresenter {
    IWetherModel mModel;
    IWetherView mView;
}

Presenter與View的通信
View—–>Presenter
從視圖界面出發(fā),用戶要請求數據泛鸟,而Presenter是具體實現者蝠咆,所以Presenter要提供方法代View的實現者調用,并且View的實現中必須要有Presenter的引用。
所以MainActivity.java中要有WetherPresenter的引用刚操。

public class MainActivity extends AppCompatActivity implements IWetherView{
    ......
    WetherPresenter mPresenter;
    ......
} 

而Presenter也要開發(fā)API供View調用闸翅。
所以Presenter要有requestWetherInfo()方法:

public class WetherPresenter {
    IWetherModel mModel;
    IWetherView mView;

    //供View層調用,用來請求天氣數據
    public void requestWetherInfo(){

    }
}

presenter—–>View
presenter操作View菊霜,是通過View.interface坚冀,也就是View層定義的接口。
所以很容易得到下面的代碼:

public class WetherPresenter {

    ......
    private void showWaitingDialog(){
        if (mView != null) {
            mView.showWaitingDialog();
        }
    }

    private void dissmissWaitingDialog(){
        if (mView != null) {
            mView.dissmissWaitingDialog();
        }
    }

    private void updateWetherInfo(String info){
        if (mView != null) {
            mView.onInfoUpdate(info);
        }
    }
    ......
}

因為Presenter持有View的引用鉴逞,所以在這里要將View.interface注入到Presenter當中记某。

public class WetherPresenter {
    IWetherModel mModel;
    IWetherView mView;

    ......
    public WetherPresenter(IWetherView mView) {
        this.mView = mView;
    }
    ......
}

Presenter與Model的通信
Presenter與Model的通信也是雙方的。
Presenter—->Model
presenter獲取到了數據构捡,可以交給Model處理

private void saveInfo(String info){
        mModel.setInfo(info);
}

Model—–>Presenter
Model處理完數據后它也能對Presenter提供數據液南。Presenter可以通過Model對象獲取本地數據。

WetherPresenter.java
private String localInfo(){
return mModel.getInfo();
}

Presenter代碼實現
前面已經講了Presenter與Model,Presenter與View之間的通信勾徽,現在就可以編寫代碼將它們粘合起來滑凉。
Presenter本身需要向服務器獲取代碼,所以還要編寫它的相應方法:

public void requestWetherInfo(){
        getNetworkInfo();;
    }

private void getNetworkInfo(){
        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    //打開等待對話框
                    showWaitingDialog();
                    //模擬網絡耗時
                    Thread.sleep(6000);

                    String info = "21度喘帚,晴轉多云";
                    //保存到Model層
                    saveInfo(info);
                    //從Model層獲取數據畅姊,為了演示效果,實際開發(fā)中根據情況需要啥辨。
                    String localinfo = localInfo();

                    //通知View層改變視圖
                    updateWetherInfo(localinfo);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //取消對話框
                    dissmissWaitingDialog();
                }
            }
        }).start();
    }

到此涡匀,完整的Presenter代碼如下:

public class WetherPresenter {
    IWetherModel mModel;
    IWetherView mView;

    public WetherPresenter(IWetherView mView) {
        this.mView = mView;
        mModel = new IWetherModelImpl();
    }

    public void requestWetherInfo(){
        getNetworkInfo();;
    }

    private void showWaitingDialog(){
        if (mView != null) {
            mView.showWaitingDialog();
        }
    }

    private void dissmissWaitingDialog(){
        if (mView != null) {
            mView.dissmissWaitingDialog();
        }
    }

    private void updateWetherInfo(String info){
        if (mView != null) {
            mView.onInfoUpdate(info);
        }
    }

    private void saveInfo(String info){
        mModel.setInfo(info);
    }

    private String localInfo(){
        return mModel.getInfo();
    }

    private void getNetworkInfo(){
        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    //打開等待對話框
                    showWaitingDialog();
                    //模擬網絡耗時
                    Thread.sleep(6000);

                    String info = "21度,晴轉多云";
                    //保存到Model層
                    saveInfo(info);
                    //從Model層獲取數據溉知,為了演示效果陨瘩,實際開發(fā)中根據情況需要。
                    String localinfo = localInfo();

                    //通知View層改變視圖
                    updateWetherInfo(localinfo);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //取消對話框
                    dissmissWaitingDialog();
                }
            }
        }).start();
    }
}

MainActivity代碼編寫
生成Presenter级乍。這個在Activity中的onCreate方法中舌劳,并把自身當成IWetherView注入到presenter當中。

mPresenter = new WetherPresenter(this);

2 . 操作Presenter玫荣。當用戶點擊按鈕時甚淡,通過調用mPresenter獲取數據,然后靜待更新捅厂。

mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.requestWetherInfo();
            }
 });

View.interface回調方法被觸發(fā)時贯卦,進行相應的視圖更新。
這里主要的視圖有
顯示對話框
取消對話框
顯示 天氣信息焙贷。
對應代碼如下:

@Override
    public void onInfoUpdate(final String info) {
        Log.d(TAG, "onInfoUpdate: "+info);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTvInfo.setText(info);
            }
        });
    }

    @Override
    public void showWaitingDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(mDialog != null && mDialog.isShowing()){
                    mDialog.dismiss();
                }

                mDialog = ProgressDialog.show(MainActivity.this,"","正在獲取中...");
            }
        });

    }

    @Override
    public void dissmissWaitingDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(mDialog != null && mDialog.isShowing()){
                    mDialog.dismiss();
                }
            }
        });

    }

所以整個MainActivity.java代碼如下:

public class MainActivity extends AppCompatActivity implements IWetherView{
    private static final String TAG = "MainActivity";

    WetherPresenter mPresenter;
    private TextView mTvInfo;
    private Button mButton;
    private ProgressDialog mDialog;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mPresenter = new WetherPresenter(this);

        mTvInfo = (TextView) findViewById(R.id.tv_info);
        mButton = (Button) findViewById(R.id.btn_request);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.requestWetherInfo();
            }
        });
    }

    @Override
    public void onInfoUpdate(final String info) {
        Log.d(TAG, "onInfoUpdate: "+info);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTvInfo.setText(info);
            }
        });
    }

    @Override
    public void showWaitingDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(mDialog != null && mDialog.isShowing()){
                    mDialog.dismiss();
                }

                mDialog = ProgressDialog.show(MainActivity.this,"","正在獲取中...");
            }
        });

    }

    @Override
    public void dissmissWaitingDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(mDialog != null && mDialog.isShowing()){
                    mDialog.dismiss();
                }
            }
        });

    }
}

總結

mvp非常適合大型的APP開發(fā)撵割,越復雜它的優(yōu)勢越明顯,但是如果APP代碼本身很簡明辙芍,mvp就有點繞彎子的感覺了啡彬。
完整代碼請點擊
參考代碼
SimpleNews

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末羹与,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子庶灿,更是在濱河造成了極大的恐慌纵搁,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件往踢,死亡現場離奇詭異腾誉,居然都是意外死亡,警方通過查閱死者的電腦和手機菲语,發(fā)現死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門妄辩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惑灵,“玉大人山上,你說我怎么就攤上這事∮⒅В” “怎么了佩憾?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長干花。 經常有香客問我妄帘,道長,這世上最難降的妖魔是什么池凄? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任抡驼,我火速辦了婚禮,結果婚禮上肿仑,老公的妹妹穿的比我還像新娘致盟。我一直安慰自己,他們只是感情好尤慰,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布馏锡。 她就那樣靜靜地躺著,像睡著了一般伟端。 火紅的嫁衣襯著肌膚如雪杯道。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天责蝠,我揣著相機與錄音党巾,去河邊找鬼。 笑死霜医,一個胖子當著我的面吹牛齿拂,可吹牛的內容都是我干的。 我是一名探鬼主播支子,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼创肥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起叹侄,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤巩搏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后趾代,有當地人在樹林里發(fā)現了一具尸體贯底,經...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年撒强,在試婚紗的時候發(fā)現自己被綠了禽捆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡飘哨,死狀恐怖胚想,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情芽隆,我是刑警寧澤浊服,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站胚吁,受9級特大地震影響牙躺,放射性物質發(fā)生泄漏。R本人自食惡果不足惜腕扶,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一孽拷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧半抱,春花似錦脓恕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至棉磨,卻和暖如春江掩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乘瓤。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工环形, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衙傀。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓抬吟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親统抬。 傳聞我的和親對象是個殘疾皇子火本,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內容