android:MVP架構(gòu)模式的優(yōu)雅封裝

簡(jiǎn)介

關(guān)于Android程序的構(gòu)架, 主流的不外乎以下幾種:MVC议泵、MVP和MVVM驮配。
MVC:相對(duì)于較為落后,耦合度太高同诫、職責(zé)不明確粤策,不易于維護(hù)。
MVVM:使用DataBinding误窖,普及性不如MVP叮盘。

此外,Google官方提供了Sample代碼來(lái)展示MVP模式的用法霹俺,因此主流還是選擇MVP架構(gòu)柔吼。

因本文主要講的是MVP模式的優(yōu)雅封裝,MVVM模式在此就不作贅述丙唧,后續(xù)文章會(huì)講到愈魏。

MVC:

提到MVP就不得不提到MVC,關(guān)于MVC架構(gòu)想际,可以看下面這張圖

image.png
MVC工作原理:

MVC即Model View Controller培漏,簡(jiǎn)單來(lái)說(shuō)就是通過(guò)controller的控制去操作model層的數(shù)據(jù),并且返回給view層展示胡本,具體見(jiàn)上圖牌柄。當(dāng)用戶出發(fā)事件的時(shí)候,view層會(huì)發(fā)送指令到controller層侧甫,接著controller去通知model層更新數(shù)據(jù)珊佣,model層更新完數(shù)據(jù)以后直接顯示在view層上,這就是MVC的工作原理披粟。

這種原理就會(huì)造成一個(gè)一個(gè)致命的缺陷:當(dāng)我們把很多業(yè)務(wù)邏輯寫(xiě)在activity中時(shí)咒锻,activity既充當(dāng)了View層,又充當(dāng)了Controller層僻爽。因此虫碉,耦合性極高,各種業(yè)務(wù)邏輯代碼和View代碼混合在一起你中有我我中有你胸梆,如果要修改一個(gè)需求,改動(dòng)的地方可能相當(dāng)多,維護(hù)起來(lái)十分不便坤次。

作為一個(gè)追求優(yōu)雅的程序猿取董,這種架構(gòu)必然要被拋棄。

MVP:

image.png
概念

MVP即Model、View、Presenter
View:負(fù)責(zé)視圖部分展示、視圖事件處理甜奄。Activity、Fragment窃款、Dialog课兄、ViewGroup等呈現(xiàn)視圖的組件都可以承擔(dān)該角色。
Model:負(fù)責(zé)數(shù)據(jù)的請(qǐng)求晨继、解析烟阐、過(guò)濾等數(shù)據(jù)層操作。
Presenter:View和Model交互的橋梁紊扬。

優(yōu)勢(shì)

單一職責(zé)
Model蜒茄、View、Presenter只處理某一類(lèi)邏輯

解耦

Model層修改和View層修改互不影響
面向接口編程餐屎,依賴(lài)抽象
Presenter和View互相持有抽象引用檀葛,對(duì)外隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)

可能存在的問(wèn)題

1、Model進(jìn)行異步操作腹缩,獲取結(jié)果通過(guò)Presenter回傳到View時(shí)屿聋,出現(xiàn)View引用的空指針異常
2、Presenter和View互相持有引用庆聘,解除不及時(shí)造成的內(nèi)存泄漏胜臊。

因此勺卢,在進(jìn)行MVP架構(gòu)設(shè)計(jì)時(shí)需要考慮Presenter對(duì)View進(jìn)行回傳時(shí)伙判,View是否為空?
Presenter與View何時(shí)解除引用即Presenter能否和View層進(jìn)行生命周期同步?

好了黑忱,說(shuō)了這么多廢話宴抚,總之一句話,MVP好甫煞。下面我們來(lái)看看具體如何優(yōu)雅的實(shí)現(xiàn)MVP的封裝菇曲。

MVP架構(gòu)優(yōu)雅的封裝

1、首先抚吠,我們定義一個(gè)BaseView

/**
 * 視圖基類(lèi)
 */
public interface BaseView {
    
}

上面說(shuō)過(guò)常潮,

如果Presenter與View不及時(shí)解除引用關(guān)系,那么內(nèi)存泄漏乃至內(nèi)存溢出就是必然楷力。

具體來(lái)說(shuō)喊式,

當(dāng)Presenter對(duì)象持有一個(gè)或多個(gè)大型Activity的引用孵户,如果該對(duì)象(P)不能被系統(tǒng)回收,那么當(dāng)這些Activity不再使用時(shí)岔留,這個(gè)Activity也不會(huì)被系統(tǒng)回收夏哭,這樣一來(lái)便出現(xiàn)了內(nèi)存泄漏的情況。在應(yīng)用中內(nèi)出現(xiàn)一次兩次的內(nèi)存泄漏或許不會(huì)出現(xiàn)什么影響献联,但是在應(yīng)用長(zhǎng)時(shí)間使用以后竖配,若是這些占據(jù)大量?jī)?nèi)存的Activity無(wú)法被GC回收的話,最終會(huì)導(dǎo)致OOM的出現(xiàn)里逆,就會(huì)直接Crash應(yīng)用进胯。

我們當(dāng)然不會(huì)坐視這種情況的發(fā)生,解決的思路就是原押,

我們將Presenter的生命周期和View層的生命周期綁定在一起龄减,給Presenter定義兩個(gè)方法,一個(gè)綁定View層班眯,一個(gè)解綁View層希停,在需要的時(shí)候進(jìn)行綁定,不需要的時(shí)候進(jìn)行解綁就可以了署隘。

于是就有了下面這個(gè)定義宠能。

2、將Presenter的生命周期和View層的生命周期綁定

/**
 * 控制器接口:
 * 定義P層生命周期與 V層同步
 */
public interface IPresenter<V extends BaseView> {

    void onMvpAttachView(V view, Bundle savedInstanceState);

    void onMvpStart();

    void onMvpResume();

    void onMvpPause();

    void onMvpStop();

    void onMvpSaveInstanceState(Bundle savedInstanceState);

    void onMvpDetachView(boolean retainInstance);

    void onMvpDestroy();
}

為了代碼的優(yōu)雅性磁餐,我們對(duì)它進(jìn)行一次封裝

/**
 * 控制器基類(lèi):
 * Presenter生命周期包裝违崇、View的綁定和解除,P層實(shí)現(xiàn)的基類(lèi)
 */
public class BasePresenter<V extends BaseView> implements IPresenter<V> {

    private WeakReference<V> viewRef;

    protected V getView() {
        return viewRef.get();
    }

    protected boolean isViewAttached() {
        return viewRef != null && viewRef.get() != null;
    }

    private void _attach(V view, Bundle savedInstanceState) {
        viewRef = new WeakReference<V>(view);
    }

    @Override
    public void onMvpAttachView(V view, Bundle savedInstanceState) {
        _attach(view, savedInstanceState);
    }

    @Override
    public void onMvpStart() {

    }

    @Override
    public void onMvpResume() {

    }

    @Override
    public void onMvpPause() {

    }

    @Override
    public void onMvpStop() {

    }

    @Override
    public void onMvpSaveInstanceState(Bundle savedInstanceState) {

    }

    private void _detach(boolean retainInstance) {
        if (viewRef != null) {
            viewRef.clear();
            viewRef = null;
        }
    }

    @Override
    public void onMvpDetachView(boolean retainInstance) {
        _detach(retainInstance);
    }

    @Override
    public void onMvpDestroy() {

    }
}

3诊霹、對(duì)于View層羞延,我們一般都會(huì)寫(xiě)一個(gè)BaseActivity

/**
 * @description 在此類(lèi)中添加自己的基類(lèi)功能
 */

 public class BaseActivity extends FragmentActivity {

   protected void openActivity(String action) {
          openActivity(action, null);
   }

   public void showEnsureDialog(String message) {
       
        
   }
}

4、我們?cè)賹?xiě)一個(gè)綁定生命周期的BaseMvpActivity包裝類(lèi)

/**
 * MVP的Activity基類(lèi):
 * 純粹的 MVP 包裝脾还,不要增加任何View層基礎(chǔ)功能
 * 如果要添加基類(lèi)功能伴箩,請(qǐng)?jiān)趝@link BaseActivity} 中添加
 */
public abstract class BaseMvpActivity<P extends IPresenter> extends BaseActivity implements BaseView {

    protected P mPresenter;

    protected abstract P createPresenter();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
        if (mPresenter == null) {
            throw new NullPointerException("Presenter is null! Do you return null in createPresenter()?");
        }
        mPresenter.onMvpAttachView(this, savedInstanceState);
    }

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

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

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

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

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mPresenter != null) {
            mPresenter.onMvpSaveInstanceState(outState);
        }
    }

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

}

5、我們以登錄為例鄙漏,定義一個(gè)契約類(lèi)

/**
 * 契約接口類(lèi):
 * P層與 V層接口定義
 */
public class LoginContract {

    public interface ILoginView extends BaseView {
        /**
         * 登錄成功
         */

        void LoginSuccess();

        /**
         * 登錄失敗
         *
         * @param msg
         */
        void LoginFailed(String msg);
    }

    public interface ILoginPresenter extends IPresenter<ILoginView> {

        /**
         * 登錄
         */
        void login(String username, String password);
    }
}

6嗤谚、我們?cè)俣x一個(gè)登錄的Presenter的實(shí)現(xiàn)類(lèi),在這個(gè)類(lèi)中怔蚌,完成互相訪問(wèn)巩步。

/**
 * 控制器實(shí)現(xiàn)類(lèi)
 */
public class LoginPresenterImpl extends BasePresenter<LoginContract.ILoginView> implements LoginContract.ILoginPresenter {

    @Override
    public void login(String username, String password) {
        //先進(jìn)行非空判斷
        if (isViewAttached()) {
            handleLogin(getView(), username, password);
        }
    }

    private void handleLogin(LoginContract.ILoginView view, String username, String password) {
        if (username.isEmpty() || password.isEmpty()) {
            view.LoginFailed("賬號(hào)和密碼不能為空");
        } else if (password.length() < 6 || password.length() > 20) {
            view.LoginFailed("密碼須在6-20位之間");
        } else {
            if (username.equals("mvp")) {
                if (password.equals("123456")) {
                    view.LoginSuccess();
                } else {
                    view.LoginFailed("密碼錯(cuò)誤");
                }
            } else {
                view.LoginFailed("用戶名錯(cuò)誤");
            }
        }
    }

    @Override
    public void onMvpAttachView(LoginContract.ILoginView view, Bundle savedInstanceState) {
        super.onMvpAttachView(view, savedInstanceState);
    }

    /**
     * 重寫(xiě)P層需要的生命周期,進(jìn)行相關(guān)邏輯操作
     */
    @Override
    public void onMvpResume() {
        super.onMvpResume();
    }

}

7桦踊、到這里椅野,我們的封裝基本完成,我們現(xiàn)在來(lái)看看我們的LoginActivity是怎么樣的。

public class LoginActivity extends BaseMvpActivity<LoginContract.ILoginPresenter> implements LoginContract.ILoginView {
    @BindView(R.id.et_username)
    EditText etUsername;
    @BindView(R.id.et_password)
    EditText etPassword;
    @BindView(R.id.btn_login)
    Button btnLogin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp);
        ButterKnife.bind(this);
    }

    @Override
    protected LoginContract.ILoginPresenter createPresenter() {
        return new LoginPresenterImpl();
    }

    @OnClick({R.id.btn_login})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_login:
                String username = etUsername.getText().toString().trim();
                String password = etPassword.getText().toString().trim();
                mPresenter.login(username, password);
                break;
        }
    }

    @Override
    public void LoginSuccess() {
        Toast.makeText(this, "LoginSuccess", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void LoginFailed(String msg) {
        Toast.makeText(this, "LoginFailed", Toast.LENGTH_SHORT).show();
    }
}

這樣竟闪,是不是看起來(lái)声离,清爽多了。

8瘫怜、最后术徊,總結(jié):

Mvp模式很好的將View層和Presenter解耦,View層和Presenter層的修改互不影響鲸湃,并且符合軟件設(shè)計(jì)原則之單一職責(zé)赠涮,提高了代碼的靈活性和可擴(kuò)展性。最后二者之間通過(guò)抽象進(jìn)行關(guān)聯(lián)暗挑,使之可以互相訪問(wèn)笋除。
從MVC到最簡(jiǎn)單的MVP架構(gòu),我們解決了MVC的數(shù)據(jù)層和視圖層耦合的問(wèn)題炸裆;隨之而來(lái)的是內(nèi)存泄露的問(wèn)題垃它,通過(guò)設(shè)置對(duì)應(yīng)的綁定解綁方法來(lái)解決這個(gè)問(wèn)題;之后又是代碼冗余的問(wèn)題烹看,于是利用Java的多態(tài)性国拇,我們將重復(fù)性工作交由基類(lèi)去完成,子類(lèi)繼承基類(lèi)重寫(xiě)對(duì)應(yīng)方法即可惯殊。而實(shí)際上我們只需要修改上面Presenter中的構(gòu)造代碼酱吝,不需要在構(gòu)造中傳遞V層了,然后再寫(xiě)一個(gè)綁定和解綁的方法土思,最后修改Activity創(chuàng)建Presenter時(shí)進(jìn)行綁定务热,在onDestroy中進(jìn)行解綁。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末己儒,一起剝皮案震驚了整個(gè)濱河市崎岂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闪湾,老刑警劉巖冲甘,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異响谓,居然都是意外死亡损合,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)娘纷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人跋炕,你說(shuō)我怎么就攤上這事赖晶。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵遏插,是天一觀的道長(zhǎng)捂贿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)胳嘲,這世上最難降的妖魔是什么厂僧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮了牛,結(jié)果婚禮上颜屠,老公的妹妹穿的比我還像新娘。我一直安慰自己鹰祸,他們只是感情好甫窟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蛙婴,像睡著了一般粗井。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上街图,一...
    開(kāi)封第一講書(shū)人閱讀 50,021評(píng)論 1 291
  • 那天浇衬,我揣著相機(jī)與錄音,去河邊找鬼餐济。 笑死径玖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颤介。 我是一名探鬼主播梳星,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼滚朵!你這毒婦竟也來(lái)了冤灾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤辕近,失蹤者是張志新(化名)和其女友劉穎韵吨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體移宅,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡归粉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漏峰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糠悼。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖浅乔,靈堂內(nèi)的尸體忽然破棺而出倔喂,到底是詐尸還是另有隱情铝条,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布席噩,位于F島的核電站班缰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悼枢。R本人自食惡果不足惜埠忘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馒索。 院中可真熱鬧莹妒,春花似錦、人聲如沸双揪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渔期。三九已至运吓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疯趟,已是汗流浹背拘哨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留信峻,地道東北人倦青。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像盹舞,于是被迫代替她去往敵國(guó)和親产镐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

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