Android學習筆記19 框架模式MVC MVP與MVVM

MVC煮甥、MVP盗温、MVVM,作為Android開發(fā)中重要的三種框架模式成肘,能夠理解并能較好地將其運用在自己的項目中是很重要的卖局。本文主要是自己對Android中這三種框架模式的學習總結,其中重點介紹了MVP模式双霍,如有錯誤或者表達不當之處砚偶,歡迎指出。

一洒闸、概述
二染坯、MVC
三、MVP
   1.簡介
   2.示例
   3.改進
四顷蟀、MVVM
五酒请、參考

一、概述

設計模式是一套被反復使用的鸣个、多數(shù)人知曉的羞反、經過分類編目的、代碼設計經驗的總結囤萤。常見的有單例模式昼窗,適配器模式,代理模式等等涛舍。MVC澄惊,MVP等等不屬于設計模式,而是框架模式富雅,那么框架模式與設計模式的區(qū)別是什么呢掸驱。

簡單來說,框架模式是一種思想没佑,框架是框架模式的具體實現(xiàn)毕贼,是對代碼的重用,而設計模式是設計重用蛤奢。設計模式是對在某種環(huán)境中反復出現(xiàn)的問題以及解決該問題的方案的描述鬼癣,它比框架更抽象陶贼;框架可以用代碼表示,也能直接執(zhí)行或復用待秃,而對模式而言只有實例才能用代碼表示;設計模式是比框架更小的元素拜秧,一個框架中往往含有一個或多個設計模式,框架總是針對某一特定應用領域章郁,但同一模式卻可適用于各種應用枉氮。可以說驱犹,框架是軟件嘲恍,而設計模式是軟件的知識。

二雄驹、MVC

MVC全稱是Model-View-Controller佃牛,即模型-視圖-控制器,它用一種業(yè)務邏輯医舆、數(shù)據(jù)俘侠、界面顯示分離的方法組織代碼,將業(yè)務邏輯聚集到一個部件里面蔬将,在改進和個性化定制界面及用戶交互的同時爷速,不需要重新編寫業(yè)務邏輯。MVC三部分具體如下:

  • Model(模型)是應用程序中用于處理應用程序數(shù)據(jù)邏輯的部分霞怀”苟  
    通常模型對象負責從網絡中或者在數(shù)據(jù)庫中存取數(shù)據(jù)。

  • View(視圖)是應用程序中處理數(shù)據(jù)顯示的部分毙石×冢  
    通常視圖是依據(jù)模型數(shù)據(jù)創(chuàng)建的。

  • Controller(控制器)是應用程序中處理用戶交互的部分徐矩≈褪保  
    通常控制器負責從視圖讀取數(shù)據(jù)滤灯,控制用戶輸入坪稽,并向模型發(fā)送數(shù)據(jù)。

MVC的目的主要是將數(shù)據(jù)模型和視圖分離開鳞骤,并以控制器作為連接二者的橋梁以實現(xiàn)解耦窒百,下面這張圖很清楚地展示了整體結構:

在Android開發(fā)中,MVC的應用比較經典豫尽,比如layout文件夾下的各種布局文件就對應于view層贝咙,從網絡或者數(shù)據(jù)庫獲取數(shù)據(jù)就對應于model層,controller層就是各種activity拂募。

三庭猩、MVP

MVP,全稱是Model-View-Presenter陈症,它針對MVC做了一些改進蔼水,解除了Android中View和Model的耦合,使得寫出的代碼邏輯更加清晰录肯,可擴展性更好趴腋。

簡介

在Android中,傳統(tǒng)的MVC中的View论咏,對應的是各種Layout布局文件优炬,但是這些布局文件中并不像Web端那樣強大,能做的事情非常有限厅贪。Controller對應的是Activity蠢护,但是在實際的項目中也會有很多UI操作在這一層,做了很多View中應該做的事情养涮,當然Controller中也包含Controller應該做的事情葵硕,比如各種事件的派發(fā)回調,而且在一層中我們會根據(jù)事件再去調用Model層操作數(shù)據(jù)贯吓,所以這種MVC的方式在實際項目中懈凹,Activity所在的Controller是非常重的,各層次之間的耦合情況也比較嚴重悄谐,不方便單元測試介评。

MVC的進化版——MVP把Layout布局和Activity作為View層,增加了Presenter爬舰,Presenter層與Model層進行業(yè)務的交互们陆,完成后再與View層交互(也就是Activity)進行回調來刷新UI。這樣一來洼专,所有業(yè)務邏輯的工作都交給了Presenter中進行棒掠,使得View層與Model層的耦合度降低,Activity中的工作也進行了簡化屁商。下面通過一個簡單的例子來展示一些它的用法烟很,當然想要靈活地掌握MVP這種框架模式還是需要不斷地實踐的。

示例

在這個例子中蜡镶,我們的主要功能是在一個Activity中獲取新聞數(shù)據(jù)雾袱,并將其展示在頁面上,下面是幾部分主要的代碼官还。

  • Model層

1芹橡、接口NewsModel

public interface NewsModel {

    //給定新聞數(shù)據(jù)的url獲取數(shù)據(jù)
    void getNewsFromNet(String url, NewsModelImpl.NewsCallBack newsCallBack);
}

2、NewsModelImpl實現(xiàn)數(shù)據(jù)獲取


public class NewsModelImpl implements NewsModel {

    private String newsData;

    @Override
    public void getNewsFromNet(final String url, final NewsCallBack newsCallBack) {

        //模擬獲取數(shù)據(jù)耗時操作
        new Thread() {
            @Override
            public void run() {

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
               //模擬從url獲取數(shù)據(jù)
                newsData = url + "湄公河票房突破十億 1天前";
                newsCallBack.onSuccess(newsData);
            }
        }.start();

    }


    public interface NewsCallBack {
        void onSuccess(String mNewsData);
    }
}
  • Presenter
public class NewsPresenter {
    //View的引用
    private NewsView mNewsView;
    //Model的引用
    private NewsModel mNewsModel = new NewsModelImpl();

    private Handler handler = new Handler();

    public NewsPresenter(NewsView mNewsView) {
        this.mNewsView = mNewsView;
    }

    public void getNewsData() {

        mNewsView.showLoading();

        mNewsModel.getNewsFromNet("", new NewsModelImpl.NewsCallBack() {
            @Override
            public void onSuccess(final String mNewsData) {

                handler.post(new Runnable() {
                    @Override
                    public void run() {

                        mNewsView.displayNews(mNewsData);
                        mNewsView.dismissLoading();

                    }
                });

            }
        });
    }
}
  • View層

1望伦、邏輯抽象

public interface NewsView {

    //加載中
    void showLoading();

    //展示新聞
    void displayNews(String newsData);

    //取消加載
    void dismissLoading();

    //加載失敗
    void displayFailing();

}

2林说、具體實現(xiàn)

public class NewsActivity extends AppCompatActivity implements NewsView {

    private TextView tvData;
    private String data;
    private ProgressDialog progressDialog;
    private NewsPresenter newsPresenter;


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

        tvData = (TextView) findViewById(R.id.tv_data);

        newsPresenter = new NewsPresenter(this);
        newsPresenter.getNewsData();

    }

    @Override
    public void showLoading() {

        progressDialog = ProgressDialog.show(this, "請稍候", "數(shù)據(jù)正在加載中", true, false);
        progressDialog.show();

    }

    @Override
    public void displayNews(String newsData) {

        data = newsData;
        tvData.setText(data);
    }

    @Override
    public void dismissLoading() {
        progressDialog.dismiss();
    }

    @Override
    public void displayFailing() {
        Toast.makeText(this, "加載失敗", Toast.LENGTH_SHORT).show();
    }
}

上面代碼的主要邏輯如下:

1煎殷、Model層主要是數(shù)據(jù)獲取,我們定義了接口NewsModel腿箩,并在NewsModelImpl中實現(xiàn)數(shù)據(jù)獲取豪直,注意這里回調機制的用法。

2珠移、View層的話我們先定義接口NewsView弓乙,抽象出通用的方法。

3钧惧、Presenter層的話暇韧,作為Model和View的橋梁,持有NewsModel和NewsView的引用浓瞪,NewsPresenter主要完成業(yè)務邏輯的處理懈玻。

4、最后我們再看下NewsActivity追逮,實現(xiàn)了接口NewsView酪刀,并在onCreate()方法里通過newsPresenter = new NewsPresenter(this);將自身傳遞給NewsPresenter建立引用,這樣M钮孵、V骂倘、P三者的聯(lián)系就建立起來了。

改進

上面的三部分代碼給出了MVP模式的一個典型的例子巴席,但是還是存在一定的問題的历涝。考慮如下:在上面的NewsPresenter中主要是一些業(yè)務邏輯的處理漾唉,其中getNewsData()是一個較為耗時的操作荧库,因為NewsPresenter持有了對NewsActivity的強引用,如果在請求結束之前NewsActivity被銷毀了赵刑,因為網絡請求還沒有返回分衫,導致NewsPresenter一直持有NewsActivity對象,使得NewsActivity對象無法被回收般此,發(fā)生內存泄漏蚪战。解決方法主要是** 對presenter和view層進行修改 **。在上面代碼的基礎上铐懊,我們做出下面的修改邀桑。

  • 添加BasePresenter
    BasePresenter主要是建立Presenter和View之間的聯(lián)系,方便控制二者的關系科乎。
public abstract class BasePresenter<T> {

    private Reference<T> mViewReference;

    //建立關聯(lián)
    public void attachView(T view) {
        mViewReference = new WeakReference<>(view);
    }

    //獲取關聯(lián)
    protected T getView() {
        return mViewReference.get();
    }

    //是否關聯(lián)
    public boolean isViewAttached() {
        return mViewReference != null && mViewReference.get() != null;
    }

    //解除關聯(lián)
    public void detachView() {
        if (mViewReference != null) {
            mViewReference.clear();
            mViewReference = null;
        }
    }
}
  • 修改NewsPresenter 使其繼承自BasePresenter
public class NewsPresenter extends BasePresenter<NewsView> {

    private NewsModel mNewsModel;
    private Handler handler;

    public NewsPresenter() {
        this.mNewsModel = new NewsModelImpl();
        this.handler= new Handler();
    }

    public void getNewsData() {

       //getView()獲取已經關聯(lián)的View
        getView().showLoading();
        mNewsModel.getNewsFromNet("", new NewsModelImpl.NewsCallBack() {
            @Override
            public void onSuccess(final String mNewsData) {

                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        getView().displayNews(mNewsData);
                        getView().dismissLoading();
                    }
                });

            }
        });
    }
}

  • 添加BaseActivity
    在onCreate()里將View與Presenter關聯(lián)壁畸,onDestroy()里解除關聯(lián)。
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {

    protected T mPresenter;

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = setPresenter();
        mPresenter.attachView((V) this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
    }

    protected abstract T setPresenter();

}
  • 最后是修改后的NewsActivity
public class NewsActivity extends BaseActivity<NewsView, NewsPresenter> implements NewsView {

    private TextView tvData;
    private String data;
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvData = (TextView) findViewById(R.id.tv_data);

        mPresenter.getNewsData();
    }

    @Override
    public void showLoading() {
        progressDialog = ProgressDialog.show(this, "請稍候", "數(shù)據(jù)正在加載中", true, false);
        progressDialog.show();
    }

    @Override
    public void displayNews(String newsData) {
        data = newsData;
        tvData.setText(data);
    }

    @Override
    public void dismissLoading() {
        progressDialog.dismiss();
    }

    @Override
    public void displayFailing() {
        Toast.makeText(this, "加載失敗", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected NewsPresenter setPresenter() {
        return new NewsPresenter();
    }
}

到這里,MVP的部分介紹就結束了捏萍。MVP是開發(fā)過程中比較好的框架模式太抓,它能夠將各個組件進行解耦,并且?guī)砹己玫目蓴U展性照弥、可測試性腻异、穩(wěn)定性、可維護性这揣,同時使得每個類型的職責相對單一、簡單影斑,有效地將業(yè)務邏輯和數(shù)據(jù)處理等工作從Activity里抽離出來给赞,使得每個類盡可能簡單。

四矫户、MVVM

MVVM與MVP非常相似片迅,唯一的區(qū)別在于View和Model進行雙向綁定,兩者之間有一方發(fā)生變化則會反應到另一方上皆辽。MVP中的View更新需要通過Presenter柑蛇。Android中的ListView、Adapter以及數(shù)據(jù)之間的關系就像MVVM驱闷,其中Adapter就是ViewModel角色耻台,它與View進行了綁定,又與數(shù)據(jù)集進行了綁定空另,當數(shù)據(jù)集合發(fā)生變化時盆耽,調用Adapter的notifyDataSetChanged之后View就直接更新,它們之間沒有直接的耦合扼菠,使得ListView變得更為靈活摄杂。

五、參考

到這里循榆,關于常見的幾種框架模式就介紹完了析恢,這里也只是自己的初步學習總結,在之后的開發(fā)實踐中會繼續(xù)完善秧饮,爭取帶來更完善的總結與思考映挂。

相關參考:

1、何紅輝 《Android源碼設計模式 解析與實戰(zhàn)》
2浦楣、MVP框架Mosby架構詳解
3袖肥、Hongyang - 淺談 MVP in Android

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市振劳,隨后出現(xiàn)的幾起案子椎组,更是在濱河造成了極大的恐慌,老刑警劉巖历恐,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寸癌,死亡現(xiàn)場離奇詭異专筷,居然都是意外死亡,警方通過查閱死者的電腦和手機蒸苇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門磷蛹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溪烤,你說我怎么就攤上這事味咳。” “怎么了檬嘀?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵槽驶,是天一觀的道長。 經常有香客問我鸳兽,道長掂铐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任揍异,我火速辦了婚禮全陨,結果婚禮上,老公的妹妹穿的比我還像新娘衷掷。我一直安慰自己辱姨,他們只是感情好,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布棍鳖。 她就那樣靜靜地躺著炮叶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渡处。 梳的紋絲不亂的頭發(fā)上镜悉,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機與錄音医瘫,去河邊找鬼侣肄。 笑死,一個胖子當著我的面吹牛醇份,可吹牛的內容都是我干的稼锅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼僚纷,長吁一口氣:“原來是場噩夢啊……” “哼矩距!你這毒婦竟也來了?” 一聲冷哼從身側響起怖竭,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤锥债,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哮肚,經...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡登夫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了允趟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恼策。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖潮剪,靈堂內的尸體忽然破棺而出涣楷,到底是詐尸還是另有隱情,我是刑警寧澤抗碰,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布总棵,位于F島的核電站,受9級特大地震影響改含,放射性物質發(fā)生泄漏。R本人自食惡果不足惜迄汛,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一捍壤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鞍爱,春花似錦鹃觉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沉填,卻和暖如春疗隶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翼闹。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工斑鼻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猎荠。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓坚弱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親关摇。 傳聞我的和親對象是個殘疾皇子荒叶,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內容