Android---我所理解的MVP模式

前言

兩年前第一次接觸MVP模式,就被各種接口各種分層給弄的云里霧里爷速。相信大多數(shù)的朋友第一次接觸MVP的實例就是網(wǎng)上泛濫的那個登錄模型挖胃,沒錯,就是這個沒有任何卵用的模型讓我認(rèn)識到最初的MVP模式持搜。隨著這兩年頻繁的接觸MVP,對這種質(zhì)壁分離的模式也有了些小小的見解焙矛,下面就來分析一下葫盼。

模型簡介

MVP模式簡單的理解可以概括為將一個業(yè)務(wù)拆分成Model,View村斟,Presenter三層結(jié)構(gòu)贫导。

Model層主管數(shù)據(jù)處理,包括網(wǎng)絡(luò)數(shù)據(jù)蟆盹,數(shù)據(jù)庫數(shù)據(jù)脱盲,文件IO讀寫等;
View層主管UI更新和與UI相關(guān)的簡單邏輯日缨;
Presenter就是連接Model層和View層之間的橋梁,溝通Model和View進(jìn)行各種交流掖看,其作用一方面是獲取Model層數(shù)據(jù)匣距,二就是通過拿到的數(shù)據(jù),進(jìn)行部分邏輯處理后哎壳,交由View層進(jìn)行UI更新毅待。

這篇文章需要有一定的MVP基礎(chǔ),不然你可能會懵逼

乞丐版MVP

我這里將未經(jīng)封裝的MVP模式簡稱為乞丐版MVP归榕,這種MVP只是簡單的使用了MVP的思想尸红,粗暴的寫出來M,V刹泄,P三層結(jié)構(gòu)外里,未經(jīng)封裝,存在大量的代碼冗余和部分內(nèi)存泄漏的風(fēng)險特石,所以乞丐版MVP只是用來理解MVP模式盅蝗,不能直接放在項目中使用。



簡單模擬一個場景:假設(shè)在一個Activity中有一個網(wǎng)絡(luò)請求姆蘸,拿到網(wǎng)絡(luò)數(shù)據(jù)后在P層進(jìn)行相關(guān)邏輯處理后墩莫,在V層進(jìn)行相應(yīng)數(shù)據(jù)更新芙委。
我們從M層先寫,一步一步倒追到V層狂秦,首先M層有一個網(wǎng)絡(luò)請求:

public class DemoModel {
    public void onRequestData(final CallBack<DemoBean> callBack) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模擬網(wǎng)絡(luò)請求
                SystemClock.sleep(5000);
                //網(wǎng)絡(luò)請求成功
                callBack.onSuccess(new DemoBean("哈哈"));
            }
        }).start();
    }
}

根據(jù)MVP的思想灌侣,V層和M層不能有直接的交流,那么肯定就是P層來進(jìn)行調(diào)用M層的代碼:

public class DemoPresenter {

    private IDemoView mView;
    private DemoModel mModel;

    public DemoPresenter(IDemoView view) {
        mView = view;
        mModel = new DemoModel();
    }


    public void onRequestData() {
        mModel.onRequestData(new CallBack<DemoBean>() {
            @Override
            public void onSuccess(DemoBean data) {
                mView.updateUI(data);
            }
        });
    }
}

V層和P層直接是通過接口的形式IDemoView進(jìn)行交互裂问,當(dāng)然P層和M層也可以使用接口交互:

public interface IDemoView {

    void updateUI(DemoBean data);
}

V層也就是相當(dāng)于我們的Activity:

public class DemoActivity extends AppCompatActivity implements IDemoView{

    private DemoPresenter mPresenter = new DemoPresenter(this);

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter.onRequestData();
    }

    @Override
    public void updateUI(DemoBean data) {
        Toast.makeText(this, data.str, Toast.LENGTH_SHORT).show();
    }
}

這就是一個最基本的MVP結(jié)構(gòu)侧啼,V層和M層阻隔,P作為中樞系統(tǒng)愕秫,將M層拿到的數(shù)據(jù)通過一定的邏輯處理后慨菱,交由V層進(jìn)行UI更新。
乞丐版MVP有以下幾個缺點:

1.V中使用P都需要new一個出來戴甩,不同的V使用的P不同符喝,但創(chuàng)建形式相同,代碼存在冗余甜孤;
2.P中使用M也是在構(gòu)造器中new一個M协饲,M作為網(wǎng)絡(luò)請求,可能會存在多種場景復(fù)用的情況缴川,這種寫法阻礙了代碼的復(fù)用茉稠;
3.V層被干掉,P層卻沒有收到相應(yīng)關(guān)閉的通知把夸,存在一定的內(nèi)存泄漏而线;

封裝后的MVP框架

針對于乞丐版MVP,這里我做了以下的幾點封裝:

1.創(chuàng)建V恋日,P膀篮,M層對應(yīng)的基類,如:BaseMVPActivity岂膳,BasePresenter誓竿,BaseModel;
2.使用注解的形式谈截,在BaseMVPActivity中自動創(chuàng)建出我們所需要的P筷屡,減少代碼冗余;
3.創(chuàng)建一個ModelManager管理類簸喂,使用反射的形式創(chuàng)建出Model毙死,減少P和M之間的耦合,增強(qiáng)復(fù)用性喻鳄;
4.P層中增加其生命周期方法规哲,綁定于對應(yīng)的V層,V層被干掉的情況下诽表,保證P也會結(jié)束掉相應(yīng)的工作唉锌,減少內(nèi)存泄漏隅肥;
5.使用Loader自動管理P,防止在屏幕狀態(tài)改變的情況下袄简,創(chuàng)建多個P腥放。

先看一個結(jié)構(gòu)圖:
MVP框架流程圖.jpg

結(jié)構(gòu)圖上可以清晰的看到調(diào)用情況,V層通過注解的形式創(chuàng)建P绿语,P通過IBaseView通知V秃症,P通過Token類加載ModelManager獲取對應(yīng)的M對象,ModelManager通過CallBack回調(diào)來通知P拿取數(shù)據(jù)吕粹,ModelManager使用反射的形式管理眾多M种柑,這樣就成功的將M層單獨抽離成一個大模塊。

廢話不多說匹耕,首先先看下使用情況:
V層:

@CreatePresenter(MainPresenter.class)
public class MainActivity extends BaseMVPActivity<MainPresenter> implements IMainView {

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

    @Override
    protected void onPresenterCreate() {
        mPresenter.show();
    }

    @Override
    public void show() {
        Log.d("MainActivity", "呵呵");
    }
}

P層:

public class MainPresenter extends BasePresenter<IMainView> {

    public void show() {
        HttpParam param = HttpParam.obtain();
        param.put(APIParamKey.PHONE, "13112345678");

        ModelManager.getModel(Token.MAIN_MODE)
                .setParam(param)
                .execute(new CallBack<MainBean>() {
                    @Override
                    public void onSuccess(MainBean data) {
                           mView.show();
                    }

                    @Override
                    public void onFailure(String msg) {

                    }
                });
    }
}

M層:

public class MainModel extends BaseModel<MainBean> {

    @Override
    public void execute(final CallBack<MainBean> callBack) {
        super.execute(callBack);
        HttpParam param = getParam();
        String phone = param.get(APIParamKey.PHONE);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                callBack.onSuccess(new MainBean());
            }
        }, 4000);
    }
}

封裝后的結(jié)構(gòu)比之前要清晰許多聚请,代碼也少了一部分,M層和P層也實現(xiàn)了完全的解耦合稳其。
那么我們來先看看M層和P層是怎么樣實現(xiàn)完全的解耦合的呢:

M層主要封裝:

ModelManager:

public class ModelManager {

    private static ArrayMap<String, IBaseModel> mCache = new ArrayMap<>();

    /**
     * 傳遞類驶赏,反射獲得該類對象
     *
     * @param token 類引用字符串
     */
    public static IBaseModel getModel(Class<? extends BaseModel> token) {
        return getModel(token.getName());
    }

    /**
     * 傳遞類引用字符串,反射獲得該類對象
     *
     * @param token 類引用字符串
     */
    public static IBaseModel getModel(String token) {
        IBaseModel model = mCache.get(token);
        try {
            if (model == null) {
                model = (IBaseModel) Class.forName(token).newInstance();
                mCache.put(token, model);
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return model;
    }
}

主要是使用反射來獲取對應(yīng)的Model類既鞠,增加mCache緩存實現(xiàn)容器類單例煤傍。這里我實現(xiàn)了兩個方法,一個是傳遞Class類嘱蛋,一個是傳遞字符串蚯姆。我個人建議是使用傳遞字符串的方式來創(chuàng)建Model,通過一個類Token洒敏,封裝所有的Model信息蒋失,使P和M直接只通過ModelManager的形式來交互,也便于M的復(fù)用桐玻。

ModelManager第二個方法是傳遞一個類的引用字符串,我把所有要引用的字符串寫到一個類里荆萤,便于管理镊靴,起名叫Token:

public interface Token {
    //    String MAIN_MODE = "com.zdu.mvpdemo.MainModel";
    String MAIN_MODE = MainModel.class.getName();
}

我寫了兩種方式來引用字符串,推薦使用第二種链韭,使用XXXModel.class.getName()的方式來獲取當(dāng)前類的引用偏竟。讓Token和各個Model直接建立起聯(lián)系,便于查找敞峭,同時也能防止混淆時Model類因為沒有任何引用而被認(rèn)為是無效類從而被刪掉的問題踊谋。

BaseModel類中主要實現(xiàn)了4個方法:

參數(shù)的set和get方法:

 @Override
    public BaseModel<T> setParam(HttpParam param) {
        this.mParam = param;
        mParam.setUse(true);
        return this;
    }

    @Override
    public HttpParam getParam() {
        if (mParam == null) {
            throw new NullPointerException("入?yún)榭?);
        }
        return mParam;
    }

HttpParam類是我封裝的一個ArrayMap集合,內(nèi)部加入了緩沖池的概念旋讹。優(yōu)點就是可以重復(fù)利用殖蚕,缺點就是無法動態(tài)的指定Map集合的大小轿衔。至于怎么實現(xiàn)的緩存池,請看我寫的另一篇文章: Android 模擬Message.obtain()睦疫,構(gòu)建自己的緩存池害驹。

兩個執(zhí)行方法 excute(CallBack<T>),和空參excute()

/**
     * 執(zhí)行異步操作
     *
     * @param callBack<T> 回調(diào)
     */
    @Override
    public void execute(@Nullable CallBack<T> callBack) {
        recycleParam();
    }

    /**
     * 執(zhí)行異步操作蛤育,無需回調(diào)
     */
    @Override
    public void execute() {
        execute(null);
    }
   
    /**
     * 回收
     */
    private void recycleParam() {
        if (mParam != null) {
            mParam.setUse(false);
            mParam.recycle();
        }
    }

兩個方法我都沒設(shè)置成抽象的宛官,簡單實現(xiàn)了下回收的方法,一個帶回調(diào)瓦糕,一個不帶回調(diào)底洗。子類想調(diào)用哪個就實現(xiàn)哪個方法就可以了

V層主要封裝:

V層主要實現(xiàn)BaseMVPActivity,封裝了以下幾個功能:

基類自動與P層進(jìn)行關(guān)聯(lián)咕娄,并進(jìn)行生命周期綁定
注解@CreatePresenter的形式自動創(chuàng)建P
使用Loader管理P的加載亥揖,防止屏幕狀態(tài)改變時,創(chuàng)建多個P

BaseMVPActivity:

public class BaseMVPActivity<P extends BasePresenter> extends BaseActivity implements LoaderManager.LoaderCallbacks<P>, IBaseView {

    private final int LOADER_ID = TAG.hashCode();
    protected P mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportLoaderManager().initLoader(LOADER_ID, savedInstanceState, this);
    }

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

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

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

    @NonNull
    @Override
    public Loader<P> onCreateLoader(int id, @Nullable Bundle args) {
        return new PresenterLoader<>(this, getClass());
    }

    @Override
    public void onLoadFinished(@NonNull Loader<P> loader, P data) {
        if (data != null) {
            mPresenter = data;
            mPresenter.onAttach(this);
            onPresenterCreate();
        }
    }

    @Override
    public void onLoaderReset(@NonNull Loader<P> loader) {
        if (mPresenter != null) {
            mPresenter.onDetach();
        }
        mPresenter = null;
    }

    /**
     * 回調(diào)此方法谭胚,presenter創(chuàng)建完畢
     */
    protected void onPresenterCreate() {

    }

}

首先在onCreate方法初始化Loader徐块,在實現(xiàn)的onCreateLoader方法里加載注解解析器PresenterLoader,加載完成實現(xiàn)方法onLoadFinished進(jìn)行P的賦值和接口的綁定灾而,數(shù)據(jù)被重置時實現(xiàn)onLoaderReset胡控,接觸對P的綁定,對應(yīng)的生命周期方法里也將P綁定在一起旁趟。

注解解析器PresenterLoader:

public class PresenterLoader<P extends BasePresenter> extends Loader<P> {

    private CreatePresenter mCreatePresenter;
    private P mPresenter;

    public PresenterLoader(Context context, Class<?> tClass) {
        super(context);
        mCreatePresenter = tClass.getAnnotation(CreatePresenter.class);
    }

    private P getPresenter() {
        if (mCreatePresenter != null) {
            Class<P> pClass = (Class<P>) mCreatePresenter.value();
            try {
                return pClass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException("Presenter創(chuàng)建失敗!昼激,檢查是否聲明了@CreatePresenter(xx.class)注解");
            }
        }
        return null;
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        if (mPresenter == null) {
            forceLoad();
        } else {
            deliverResult(mPresenter);
        }
    }

    @Override
    protected void onForceLoad() {
        super.onForceLoad();
        mPresenter = getPresenter();
        deliverResult(mPresenter);
    }
}

這個類的作用其一就是綁定Loader,管理P的創(chuàng)建加載情況锡搜,其二就是獲取注解對象橙困,通過反射來創(chuàng)建P。

注解類就很簡單了:

@Retention(RetentionPolicy.RUNTIME)
public @interface CreatePresenter {

    Class<? extends BasePresenter> value();
}

有一定的約束條件耕餐,必須繼承BasePresenter才能通過注解來創(chuàng)建凡傅。

P層的封裝:

V和M都搞定了,作為橋梁的P層就簡單了:
BasePresenter:

public class BasePresenter<V extends IBaseView> implements IBasePresenter<V> {

    protected V mView;

    @Override
    public void onAttach(V view) {
        mView = view;
    }

    @Override
    public V getView() {
        checkViewAttached();
        return mView;
    }

    @Override
    public void onPause() {
    }

    @Override
    public void onResume() {
    }

    @Override
    public boolean isViewAttached() {
        return mView != null;
    }

    @Override
    public void checkViewAttached() {
        if (!isViewAttached()) {
            throw new NullPointerException("View 已為空");
        }
    }

    /**
     * 取消肠缔,置空數(shù)據(jù)夏跷,防止內(nèi)存泄露
     */
    @Override
    public void onDetach() {
        mView = null;
    }

}

自動裝箱:onAttach(),自動拆箱:onDetach()明未,還有各種生命周期槽华,邏輯處理起來就方便了很多。

綜述

通過一定的封裝趟妥,創(chuàng)建P的過程交給基類通過注解的形式來創(chuàng)建猫态,減少了代碼的冗余;給P一定的生命周期方法,在V被干掉的情況下亲雪,P能及時處理對應(yīng)的事件勇凭,減少內(nèi)存泄漏;通過ModelManager統(tǒng)一管理Model匆光,使用Token類作為Model和ModelManager的橋梁套像,徹底的將P和M進(jìn)行解耦,同時分離了M層终息,使M層抽出成一個獨立的模塊夺巩,增加了M的復(fù)用性。
附上Demo的GitHub地址:MVPDemo周崭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柳譬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子续镇,更是在濱河造成了極大的恐慌美澳,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摸航,死亡現(xiàn)場離奇詭異制跟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酱虎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門雨膨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人读串,你說我怎么就攤上這事聊记。” “怎么了恢暖?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵排监,是天一觀的道長。 經(jīng)常有香客問我杰捂,道長舆床,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任嫁佳,我火速辦了婚禮挨队,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脱拼。我一直安慰自己,他們只是感情好坷备,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布熄浓。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赌蔑。 梳的紋絲不亂的頭發(fā)上俯在,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音娃惯,去河邊找鬼跷乐。 笑死,一個胖子當(dāng)著我的面吹牛趾浅,可吹牛的內(nèi)容都是我干的愕提。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼皿哨,長吁一口氣:“原來是場噩夢啊……” “哼浅侨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起证膨,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤如输,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后央勒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體不见,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年崔步,在試婚紗的時候發(fā)現(xiàn)自己被綠了稳吮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡刷晋,死狀恐怖盖高,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眼虱,我是刑警寧澤喻奥,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站捏悬,受9級特大地震影響撞蚕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜过牙,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一甥厦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寇钉,春花似錦刀疙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春疚鲤,著一層夾襖步出監(jiān)牢的瞬間锥累,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工集歇, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留桶略,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓诲宇,卻偏偏與公主長得像际歼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子焕窝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,118評論 25 707
  • 昨天和路兄夜出觀影《一出好戲》蹬挺,甚悅。舒淇美甚它掂。 8月16日未更新巴帮。以下是今天更新的內(nèi)容: 一個不被期待,不被鼓勵...
    小_富_貴閱讀 205評論 0 0
  • 有時候提筆寫字會跟心里想的是不太一致的虐秋,我很是認(rèn)為自己并沒有表達(dá)出我的情感來榕茧,任何一個人在當(dāng)眾剖析自己時,那面臨的...
    鄭重承諾閱讀 293評論 0 1
  • 文人軒記 應(yīng)秦皇之邀客给,東西南北中和驪宮用押,聚華清園下。興瀑泉以溫身心靶剑,居靜閣以求靈魂蜻拨。處少陽之室內(nèi),行頤和之...
    鄧風(fēng)來閱讀 296評論 0 3
  • 相信每一個女孩紙都萌生過開一家咖啡店或甜品店或者書店的想法桩引,我也是……我曾多次幻想將來能夠與魔都的某一優(yōu)雅之地開一...
    Surquee閱讀 115評論 0 1