Android MVP 模式解析與基本實(shí)現(xiàn)方式

文章已同步至 CSDN:http://blog.csdn.net/qq_24867873/article/details/79459856

前言

記得自己接手的第二個(gè)項(xiàng)目采用的是 MVP 模式進(jìn)行開(kāi)發(fā)的,當(dāng)時(shí)架構(gòu)已經(jīng)設(shè)計(jì)好派桩,我看了幾篇關(guān)于 MVP 的文章构诚,對(duì)其有了基本的了解之后,便照貓畫(huà)虎進(jìn)行了開(kāi)發(fā)铆惑,之后便再也沒(méi)接觸過(guò) MVP范嘱。

最近空閑的時(shí)候讀了一篇 MVP 相關(guān)的文章送膳,受益匪淺。于是打算寫(xiě)一篇關(guān)于它的文章丑蛤,一方面是作為自己的學(xué)習(xí)筆記方便查看叠聋,另一反面希望能給沒(méi)有接觸過(guò) MVP 模式的新人提供幫助,以便可以快速入門(mén)受裹。

什么是 MVC

在講 MVP 之前碌补,我們先來(lái)了解一下 MVC。

MVC 結(jié)構(gòu)圖

MVC 模式是經(jīng)典的三層架構(gòu)一種具體的實(shí)現(xiàn)方式棉饶,全稱為 Model(模型層) 厦章、View(視圖層)、Controller(控制器)照藻。下面介紹一下它們各自的職責(zé):

  • Model 層:用來(lái)定義實(shí)體對(duì)象袜啃,處理業(yè)務(wù)邏輯,可以簡(jiǎn)單地理解成 Java 中的實(shí)體類幸缕。
  • View 層:負(fù)責(zé)處理界面的顯示群发,在 Android 中對(duì)應(yīng)的就是 xml 文件。
  • Controller 層:對(duì)應(yīng)的是 Activity/Fragment 发乔,當(dāng)加載完成 xml 布局之后熟妓,我們需要找到并設(shè)置布局中的各個(gè) View,處理用戶的交互事件列疗,更新 View 等滑蚯。

下面我們通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明這三者是如何交互的。

首先是 View 層抵栈,布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              android:padding="16dp">

    <EditText
        android:id="@+id/et_height"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="身高cm"/>

    <EditText
        android:id="@+id/et_weight"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="體重kg"/>

    <Button
        android:id="@+id/btn_cal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>

</LinearLayout>

然后是 Controller 層:

public class MVCActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText mEtHeight;
    private EditText mEtWeight;
    private Button mBtnCal;

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

        // Controller 訪問(wèn)了 View 的組件
        mEtHeight = findViewById(R.id.et_height);
        mEtWeight = findViewById(R.id.et_weight);
        mBtnCal = findViewById(R.id.btn_cal);
        // 這個(gè)點(diǎn)擊事件屬于 View,它是 View 的監(jiān)聽(tīng)器
        mBtnCal.setOnClickListener(this);

        // Controller 調(diào)用了 Model
        String btnText = User.instance().getBtnText();
        // 然后 Controller 更新了 View 的屬性
        mBtnCal.setText(btnText);
    }

    @Override
    public void onClick(View v) {
        int height = Integer.parseInt(mEtHeight.getText().toString());
        float weight = Float.parseFloat(mEtWeight.getText().toString());
        // Controller 更新了 Model 中的數(shù)據(jù)
        User.instance().setHeight(height);
        User.instance().setWeight(weight);
        // 這里 View 又訪問(wèn)了 Model 的數(shù)據(jù)坤次,并呈現(xiàn)在 UI 上
        String valueBMI = String.valueOf(User.instance().getBMI());
        Toast.makeText(this, "BMI: " + valueBMI, Toast.LENGTH_LONG).show();
    }
}

最后是 Model 層:

public class User {

    private int height;
    private float weight;

    private static User mUser;

    public static User instance(){
        if (mUser == null) {
            synchronized (User.class) {
                if (mUser == null) {
                    mUser = new User();
                }
            }
        }
        return mUser;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public float getWeight() {
        return weight;
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }

    public String getBtnText() {
        // 在這里古劲,我們可以從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)
        // 或者訪問(wèn)網(wǎng)絡(luò)獲取數(shù)據(jù)
        return "計(jì)算BMI";
    }

    public float getBMI() {
        // 通過(guò)已有的屬性計(jì)算出新的屬性,也屬于業(yè)務(wù)邏輯的操作
        return weight / (height * height) * 10000;
    }
}

從上面的代碼中缰猴,我們可以看到 View 層的職責(zé)是非常簡(jiǎn)單的产艾,向用戶呈現(xiàn) xml 文件中的布局,并且響應(yīng)用戶的觸摸事件滑绒。

而 Controller 層的職責(zé)邏輯則復(fù)雜很多闷堡。它對(duì)于 View 層,需要將從 Model 中獲取到的數(shù)據(jù)及時(shí)地呈現(xiàn)在 UI 上疑故。而對(duì)于 Model 層杠览,當(dāng) app 的生命周期發(fā)生變化或者接收到某些響應(yīng)時(shí),需要對(duì) Model 的數(shù)據(jù)進(jìn)行 CRUD纵势。在這個(gè)例子中踱阿,用戶點(diǎn)擊按鈕的時(shí)候管钳,首先獲取 View 層用戶的輸入,然后更新 Model 層的屬性软舌,最后獲取到 Model 層計(jì)算得出的新數(shù)據(jù)并顯示在 UI 上才漆。

對(duì)于 Model 來(lái)說(shuō),它不僅僅是個(gè)簡(jiǎn)單的實(shí)體類佛点,還應(yīng)該包括數(shù)據(jù)處理與業(yè)務(wù)邏輯的操作醇滥,比如說(shuō)對(duì)數(shù)據(jù)庫(kù)的操作、網(wǎng)絡(luò)請(qǐng)求等超营,但是很多情況下鸳玩,我們很少把這些操作寫(xiě)在實(shí)體類中。

demo 運(yùn)行效果如下:

運(yùn)行效果

在 MVC 模式中糟描,Controller 層扮演著重要的角色怀喉,它不僅要處理 UI 的顯示與事件的響應(yīng),還要負(fù)責(zé)與 Model 層的通信船响,同時(shí) Model 層與 View 層也會(huì)通信躬拢,三者的耦合度很大。

作為 Android 開(kāi)發(fā)中默認(rèn)使用的架構(gòu)模式见间,MVC 易于上手聊闯,適合快速開(kāi)發(fā)一些小型項(xiàng)目。但是隨著業(yè)務(wù)邏輯的復(fù)雜度越來(lái)越大米诉,Activity/Fragment 會(huì)越來(lái)越臃腫菱蔬,因?yàn)樗瑫r(shí)承擔(dān)著 Controller 與 View 的角色,這對(duì)于項(xiàng)目后期的更新維護(hù)與測(cè)試交接都是非常不方便的史侣,大大提高了生產(chǎn)成本拴泌。這么一來(lái),它就違背了 “提高生產(chǎn)力” 的初衷惊橱,于是 MVP 模式就應(yīng)運(yùn)而生了蚪腐。

什么是 MVP

MVP 結(jié)構(gòu)圖

MVP 是 MVC 的一種升級(jí)進(jìn)化,全稱為 Model(模型層)税朴、View(視圖層)回季、Presenter(主持者)。從結(jié)構(gòu)圖中正林,我們可以看到它與 MVC 的區(qū)別:Presenter 代替了 Controller泡一,去除了 View 與 Model 的關(guān)聯(lián)與耦合。

  • Model 層:和 MVC 模式中的 Model 層是一樣的觅廓,這里不再說(shuō)了鼻忠。
  • View 層:視圖層。在 MVP 中哪亿,它不僅僅對(duì)應(yīng) xml 布局了粥烁,Activity/Fragment 也屬于視圖層贤笆。View 層現(xiàn)在不僅作為 UI 的顯示,還負(fù)責(zé)響應(yīng)生命周期的變化讨阻。
  • Presenter 層:主持者層芥永,是 Model 層與 View 層進(jìn)行溝通的橋梁,處理業(yè)務(wù)邏輯钝吮。它響應(yīng) View 層的請(qǐng)求從 Model 層中獲取數(shù)據(jù)埋涧,然后將數(shù)據(jù)返回給 View 層。

在 MVP 的架構(gòu)中奇瘦,最大的特點(diǎn)就是 View 與 Model 之間的解耦棘催,兩者之間必須通過(guò) Presenter 來(lái)進(jìn)行通信,使得視圖和數(shù)據(jù)之間的關(guān)系變得完全分離耳标。但是 View 和 Presenter 兩者之間的通信并不是想怎么調(diào)用就可以怎么調(diào)用的醇坝,下面講一下 MVP 模式最基本的實(shí)現(xiàn)方式。

MVP 基本的實(shí)現(xiàn)方式

  • 創(chuàng)建 IPresenter 接口(接口或類名自己定義次坡,一般有約定成俗的寫(xiě)法)呼猪,把所有業(yè)務(wù)邏輯的接口都放在這里,并創(chuàng)建它的實(shí)現(xiàn)類 PresenterImpl砸琅。
  • 創(chuàng)建 IView 接口宋距,把所有視圖邏輯的接口都放在這里,其實(shí)現(xiàn)類是Activity/Fragment症脂。
  • 在 Activity/Fragment 中包含了一個(gè) IPresenter 的實(shí)例谚赎,而 PresenterImpl 里又包含了一個(gè) IView 的實(shí)例并且依賴了 Model。Activity/Fragment 只保留對(duì) IPresenter 的調(diào)用诱篷,當(dāng) View 層發(fā)生某些請(qǐng)求響應(yīng)或者生命周期發(fā)生變化壶唤,則會(huì)迅速的向 Presenter 層發(fā)起請(qǐng)求,讓 Presenter 做出相應(yīng)的處理棕所。
  • Model 并不是必須有的视粮,但是一定會(huì)有 View 和 Presenter。

我們還是以上面的功能為例橙凳,用 MVP 模式具體實(shí)現(xiàn)它。

IPresenter 接口:

public interface IPresenter {

    /**
     * 調(diào)用該方法表示 Presenter 被激活了
     */
    void start();

    void onBtnClick(int height, float weight);

    /**
     * 調(diào)用該方法表示 Presenter 要結(jié)束了
     * 為了避免相互持有引用而導(dǎo)致的內(nèi)存泄露
     */
    void destroy();

}

IView 接口:

public interface IView {

    /**
     * 用來(lái)更改按鈕的文本
     *
     * @param text
     */
    void updateBtnText(String text);

    /**
     * 用來(lái)彈出吐司顯示 BMI
     *
     * @param bmi
     */
    void showToast(float bmi);

}

IPresenter 接口的實(shí)現(xiàn)類 PresenterImpl:

public class PresenterImpl implements IPresenter {

    private IView mView;

    public PresenterImpl(IView mView) {
        this.mView = mView;
    }

    @Override
    public void start() {
        String text = User.instance().getBtnText();
        mView.updateBtnText(text);
    }

    @Override
    public void onBtnClick(int height, float weight) {
        User.instance().setHeight(height);
        User.instance().setWeight(weight);
        float bmi = User.instance().getBMI();
        mView.showToast(bmi);
    }

    @Override
    public void destroy() {
        mView = null;
    }
}

IView 接口的實(shí)現(xiàn)類 MVPActivity:

public class MVPActivity extends AppCompatActivity implements IView, View.OnClickListener {

    private EditText mEtHeight;
    private EditText mEtWeight;
    private Button mBtnCal;

    private IPresenter mPresenter;

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

        // 實(shí)例化 PresenterImpl
        mPresenter = new PresenterImpl(this);
        // View 的相關(guān)初始化
        mEtHeight = findViewById(R.id.et_height);
        mEtWeight = findViewById(R.id.et_weight);
        mBtnCal = findViewById(R.id.btn_cal);
        mBtnCal.setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mPresenter.start();
    }

    @Override
    public void onClick(View v) {
        int height = Integer.parseInt(mEtHeight.getText().toString());
        float weight = Float.parseFloat(mEtWeight.getText().toString());
        mPresenter.onBtnClick(height, weight);
    }

    @Override
    public void updateBtnText(String text) {
        mBtnCal.setText(text);
    }

    @Override
    public void showToast(float bmi) {
        Toast.makeText(this, "BMI: " + bmi, Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onDestroy() {
        if (mPresenter != null) {
            mPresenter.destroy();
            mPresenter = null;
        }
        super.onDestroy();
    }
}

Model 層的代碼與 MVC 例子中的相同笑撞,這里就不再帖代碼了岛啸。

看完代碼可能有的人會(huì)發(fā)現(xiàn),相對(duì)于 MVC 模式來(lái)說(shuō)茴肥,代碼不僅沒(méi)有減少坚踩,反而還增加了許多接口,看起來(lái)有些暈瓤狐。但我們仔細(xì)觀察可以發(fā)現(xiàn)瞬铸,雖然增加了許多接口批幌,但是 MVP 的結(jié)構(gòu)是非常清晰的,也是有很大的好處的嗓节,下面我們仔細(xì)分析一下荧缘。

MVPActivity 實(shí)現(xiàn)了IView 接口,并實(shí)現(xiàn)了 updateBtnText(..)showToast(..) 這兩個(gè)方法拦宣,但是這兩個(gè)方法看起來(lái)好像都沒(méi)有被調(diào)用截粗,只是在 onCreate() 的時(shí)候創(chuàng)建了一個(gè) PresenterImpl 對(duì)象,在 onStart() 的時(shí)候調(diào)用了 mPresenter.start() 方法鸵隧,然后在 onDestroy() 的時(shí)候調(diào)用了 mPresenter.destroy() 方法绸罗,而當(dāng)按鈕的點(diǎn)擊事件響應(yīng)的時(shí)候又調(diào)用了 mPresenter.onBtnClick(..) 方法,那么既沒(méi)有回調(diào)也沒(méi)有直接調(diào)用豆瘫,那 IView 中的兩個(gè)接口方法又是何時(shí)何地被調(diào)用的呢珊蟀?接下來(lái)我們將繼續(xù)分析 Presenter 層的實(shí)現(xiàn)代碼。

在 PresenterImpl 中實(shí)現(xiàn)了 IPresenter 接口并實(shí)現(xiàn)了 start() onBtnClick(..) destroy() 方法外驱,在構(gòu)造方法中有一個(gè)IView的參數(shù)育灸,這個(gè)對(duì)象是 IView 的引用,這個(gè)對(duì)象可以是 Activity 或者是 Fragment 也可以是 IView 接口的任何一個(gè)實(shí)現(xiàn)類略步,但對(duì)于 PresenterImpl 而言具體的 IView 到底是誰(shuí)并不知道描扯。在 PresenterImpl 中,在 start()onBtnClick() 方法中除了調(diào)用 Model 外都調(diào)用了 IView 的方法:mView.updateBtnText(..)
mView.showToast(..)趟薄,以此來(lái)對(duì) View 層的 UI 呈現(xiàn)以及交互提醒做出相應(yīng)的響應(yīng)绽诚。而最后的 destroy() 方法則是用于釋放對(duì) IView 的引用。

由此我們可以得出幾個(gè)結(jié)論:

對(duì)于 View 而言:

  • 我需要一位主持者杭煎,當(dāng)出現(xiàn)視圖相關(guān)事件的響應(yīng)或者生命周期的變化時(shí)恩够,我需要告訴這位主持者,我想要做些什么羡铲。
  • 我會(huì)提供一系列通用接口蜂桶,以便于當(dāng)主持者完成我的請(qǐng)求后,調(diào)用相應(yīng)的接口告訴我這件事的結(jié)果也切。
  • 我所有的請(qǐng)求都發(fā)給主持者扑媚,讓他幫我做決定,但是這件事是怎么做的雷恃,我并不知道也不關(guān)心疆股,我只是需要結(jié)果。

對(duì)于 Presenter 而言:

  • 我接收到 View 的請(qǐng)求后找 Model 尋求幫助倒槐,等 Model 做完事情后通知我了旬痹,我在把結(jié)果告訴 View。
  • 我只知道指揮 Model做事、告訴 View 顯示數(shù)據(jù)两残,但我不干活永毅。
  • 我相當(dāng)于一座橋,連接著 View 和 Model人弓,他們誰(shuí)也不認(rèn)識(shí)誰(shuí)沼死,想要通信必須要通過(guò)我,如果沒(méi)有我票从,他們兩永遠(yuǎn)都不會(huì)認(rèn)識(shí)漫雕。沒(méi)錯(cuò),我就是這么重要峰鄙。

由于有 Presenter 的存在浸间,View 層的代碼看起來(lái)是非常清晰的,每一個(gè)方法都有它自己的功能職責(zé)吟榴,彼此之間并不會(huì)相互耦合魁蒜。而 Presenter 中的代碼也是如此,每一個(gè)方法都只處理一件事吩翻,并不會(huì)做其他無(wú)相關(guān)的事情兜看。另外我們觀察到,在 MVPActivity 中并沒(méi)有直接對(duì) PresenterImpl 進(jìn)行持有狭瞎,而是持有了一個(gè) IPresenter 對(duì)象细移;同樣的在 PresenterImpl 也并沒(méi)有直接持有 MVPActivity 而是持有了一個(gè) IView 對(duì)象。也就是說(shuō)熊锭,凡是實(shí)現(xiàn)了 IPresenter 便是 Presenter 層弧轧,凡是實(shí)現(xiàn)了 IView 便是 View 層,這樣就能很方便地變更業(yè)務(wù)邏輯或者進(jìn)行單元測(cè)試碗殷。下面就講一講 MVP 的優(yōu)勢(shì)與不足精绎。

MVP 的優(yōu)勢(shì)與不足

優(yōu)勢(shì):

  • 解耦,抽這么多接口出來(lái)就是為了解耦锌妻,非常適合多人協(xié)同開(kāi)發(fā)代乃。
  • 各模塊分工明確,結(jié)構(gòu)清晰仿粹。在 MVC 模式中搁吓,Activity/Fragment 兼顧著 Controller 與 View 的作用,雜亂且難以維護(hù)吭历,而 MVP 模式大大減少了 Activity/Fragment 的代碼擎浴,容易看懂、容易維護(hù)和修改毒涧。
  • 方便地變更業(yè)務(wù)邏輯。比如有三個(gè)功能,它們的 View 層完全一致契讲,只是各自的業(yè)務(wù)邏輯不同仿吞,那么我們可以分別創(chuàng)建三個(gè)不同的 PresenterImpl (當(dāng)然他們都要實(shí)現(xiàn) IPresenter 接口),然后在 Activity 中創(chuàng)建 IPresenter 對(duì)象的時(shí)候捡偏,就可以根據(jù)不同的外部條件創(chuàng)建出不同的 PresenterImpl唤冈,這樣就能方便的實(shí)現(xiàn)它們各自的業(yè)務(wù)。
  • 方便進(jìn)行單元測(cè)試银伟。由于業(yè)務(wù)邏輯都是在 IPresenter 中實(shí)現(xiàn)的你虹,那么我們可以創(chuàng)建一個(gè) PresenterTest 實(shí)現(xiàn) IPresenter 接口,然后把 MVPActivity 中對(duì) PresenterImpl 的創(chuàng)建改成 PresenterTest 的創(chuàng)建彤避,然后就可以對(duì) IView 的方法隨意進(jìn)行測(cè)試了傅物。如果想要測(cè)試 IPresenter 中的方法,那就新建一個(gè) ViewTest 類實(shí)現(xiàn) IView 接口琉预,然后將其傳入 PresenterImpl董饰,便可以自由的測(cè)試 IPresenter 中的方法是否有效。
  • 避免 Activity 內(nèi)存泄露圆米。Activity 是有生命周期的卒暂,用戶隨時(shí)可能切換 Activity,當(dāng) APP 的內(nèi)存不夠用的時(shí)候娄帖,系統(tǒng)會(huì)回收處于后臺(tái)的 Activity 的資源以避免 OOM也祠。采用傳統(tǒng)的模式,一大堆異步任務(wù)都有可能保留著對(duì) Activity 的引用近速,比如說(shuō)許多圖片加載框架诈嘿。這樣一來(lái),即使 Activity 的 onDestroy() 已經(jīng)執(zhí)行数焊,這些 異步任務(wù)仍然保留著對(duì) Activity 實(shí)例的引用永淌, 所以系統(tǒng)就無(wú)法回收這個(gè) Activity 實(shí)例了,結(jié)果就是 Activity Leak佩耳。Android 的組件中遂蛀,Activity 對(duì)象往往是在堆里占最多內(nèi)存的,所以系統(tǒng)會(huì)優(yōu)先回收 Activity 對(duì)象干厚, 如果有 Activity Leak李滴,APP很容易因?yàn)閮?nèi)存不夠而 OOM。采用 MVP 模式蛮瞄,只要在當(dāng)前的 Activity 的 onDestroy() 里所坯,分離異步任務(wù)對(duì) Activity 的引用,就能避免 Activity Leak挂捅。

不足:

  • 有點(diǎn)笨重芹助,不適合短期小型的項(xiàng)目開(kāi)發(fā)。你一個(gè) Activity 就能搞定的事,非要用 MVP 干嘛状土。
  • 雖然 Activity 變得輕松了无蜂,但是 Presenter 的業(yè)務(wù)越來(lái)越復(fù)雜。
  • 提高了學(xué)習(xí)成本蒙谓,由于 MVP 的變種非常多斥季,需要自己在實(shí)戰(zhàn)中慢慢摸索。

補(bǔ)充

  1. 關(guān)于 MVP 的分包結(jié)構(gòu)累驮,有的人習(xí)慣按照下面這種方式分包:

將所有的 Model/View/Presenter 的代碼分別放在同一個(gè)包下酣倾,這樣業(yè)務(wù)多了會(huì)很亂。也有人喜歡按照模塊分包谤专,將同一個(gè)功能模塊的 Model/View/Presenter 放在一個(gè)模塊包下躁锡。具體的分包方式還是要按照具體的項(xiàng)目和自己的喜好來(lái)定。

  1. 在使用上述 MVP 模式進(jìn)行開(kāi)發(fā)的過(guò)程中毒租,還遇到了空指針的問(wèn)題稚铣。當(dāng) Presenter 中通過(guò)異步方式獲取數(shù)據(jù)然后需要更新 View 的時(shí)候,這個(gè)時(shí)候 View 有可能已經(jīng)消失了墅垮,極度容易引起 NullPointerException惕医。比如下面的示例代碼:
@Override
public void login(String phone, String pwd) {
    OkGo.<BaseModal<User>>get(url).tag(this)
            .params(AppInterface.getLoginParams(phone, pwd))
            .execute(new JsonCallback<BaseModal<User>>() {
                @Override
                public void onSuccess(Response<BaseModal<User>> response) {
                    if (mView == null) {
                        return;
                    }
                    mView.showToast("登錄成功");
                }

                @Override
                public void onError(Response<BaseModal<User>> response) {
                    if (mView == null) {
                        return;
                    }
                    mView.showToast("登錄失敗");
                }
            });
}

由上面的代碼可以看出,在 Presenter 進(jìn)行異步回調(diào)后算色,一定要對(duì) mView 進(jìn)行非空判斷抬伺,否則會(huì)出現(xiàn)大面積的 NullPointerException。

總結(jié)

以上就是 MVP 模式基本的實(shí)現(xiàn)方式灾梦,可能示例代碼太簡(jiǎn)單無(wú)法體現(xiàn) MVP 的優(yōu)勢(shì)峡钓,但是真正地理解了它并在項(xiàng)目中實(shí)際使用,你便能體會(huì)到它所帶來(lái)的好處若河。MVP 有很多變種與改進(jìn)能岩,網(wǎng)上也有很多資料,如果想學(xué)的話萧福,可以很方便地找到拉鹃。另外,Google 官方也開(kāi)源了一系列 Andorid 架構(gòu)的使用示例鲫忍,其中就包括了 MVP 模式膏燕,地址:https://github.com/googlesamples/android-architecture

本篇博客示例代碼:https://github.com/ayuhani/mvp_demo

歡迎關(guān)注我的微信公眾號(hào)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末悟民,一起剝皮案震驚了整個(gè)濱河市坝辫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌射亏,老刑警劉巖近忙,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竭业,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡银锻,警方通過(guò)查閱死者的電腦和手機(jī)永品,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)击纬,“玉大人,你說(shuō)我怎么就攤上這事钾麸「瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵饭尝,是天一觀的道長(zhǎng)肯腕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)钥平,這世上最難降的妖魔是什么实撒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮涉瘾,結(jié)果婚禮上知态,老公的妹妹穿的比我還像新娘。我一直安慰自己立叛,他們只是感情好负敏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著秘蛇,像睡著了一般其做。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赁还,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天妖泄,我揣著相機(jī)與錄音,去河邊找鬼艘策。 笑死蹈胡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柬焕。 我是一名探鬼主播审残,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斑举!你這毒婦竟也來(lái)了搅轿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤富玷,失蹤者是張志新(化名)和其女友劉穎璧坟,沒(méi)想到半個(gè)月后既穆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雀鹃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年幻工,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黎茎。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡囊颅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出傅瞻,到底是詐尸還是另有隱情踢代,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布嗅骄,位于F島的核電站胳挎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏溺森。R本人自食惡果不足惜慕爬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屏积。 院中可真熱鬧医窿,春花似錦、人聲如沸肾请。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)铛铁。三九已至隔显,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饵逐,已是汗流浹背菇怀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工恰矩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诗宣,地道東北人囱修。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像薄声,于是被迫代替她去往敵國(guó)和親当船。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容