Android開發(fā)—MVPO框架模式(一)

前言

??在淡到Android開發(fā)的框架模式時德召,不外乎MVC白魂、MVVM和MVP三種。而今天上岗,題主想介紹一種新的框架模式——MVPO福荸。這是題主在MVP的基礎(chǔ)上擴展出來的一種模式,新的模式當然是為了解決新的問題肴掷,而本人相信敬锐,在Android開發(fā)中困擾著題主的一些問題,同樣也困擾著大家呆瞻。

MVP存在的缺陷

  1. View和Presenter對應(yīng)關(guān)系引發(fā)的問題台夺。
    ??我們往往一個View(Activity或Fragment)對應(yīng)一個Presnter,然后Presenter里面封裝了若干個網(wǎng)絡(luò)請求和結(jié)果處理痴脾。那么問題來了颤介,某個網(wǎng)絡(luò)請求可能會在不同的Presenter間重復(fù)。比如有個接口”獲取短信驗證碼“赞赖,我們在RegisterPresenter(注冊)和ResetPwdPresenter(重置密碼)中都存在一模一樣的代碼滚朵,但這兩個Presenter又無法整合成一個,因為它們分別對應(yīng)不同的功能頁面薯定,這兩個頁面除了獲取驗證碼始绍,其它操作都可能是不同的瞳购,強行整合成一個Presenter顯然也不合理话侄。
    ??由此,我們遇到了第一個因擾我們的問題:多個Presenter間可能存在重復(fù)的代碼学赛,卻無法優(yōu)化年堆。

  2. MVP無法滿足越來越復(fù)雜的App開發(fā)。
    ??在開發(fā)單機App(沒有網(wǎng)絡(luò)請求的App)時代盏浇,我們覺得MVC很好用变丧。但現(xiàn)在很少有單機App,很多App靠非常多且復(fù)雜的網(wǎng)絡(luò)請求來支撐視圖的展示绢掰,于是我們用MVP痒蓬,通過Presenter把網(wǎng)絡(luò)請求分離出來童擎,同時處理一些存取數(shù)據(jù)等輔助操作,目的就是為了讓View層更輕更薄攻晒。然后到了現(xiàn)在顾复,當App變得更加復(fù)雜時,比如題主最近開發(fā)的App鲁捏,一個交互中往往穿插有網(wǎng)絡(luò)請求和藍牙操作芯砸,于是,為了處理這些復(fù)雜的操作给梅,我們只得在Presenter中寫入更多的邏輯假丧,P層開始多了很多網(wǎng)絡(luò)請求以外的重要邏輯。
    ??由此动羽,我們遇到了第二個困擾我們的問題:當開發(fā)中夾雜著網(wǎng)絡(luò)請求及其它同等重要的操作時包帚,P層又會變得臃腫起來。

解決之道

??上面提到了兩個問題运吓,那么解決這兩個問題婴噩,就是題主寫這兩篇文章的目的。在第一篇文章中羽德,我不會直接講MVPO几莽,而是先針對第一個問題,通過改進MVP來提出解決方案宅静。而改進后的MVP章蚣,本身又會被用到后面的MVPO模式中,因此如果有看官比較急姨夹,不妨跳過這一章纤垂,等待下個一篇章。
??好了磷账,我們先看看如何解決第一個問題:多個Presenter間存在重復(fù)的代碼時峭沦,該如何優(yōu)化?

更合理的View和Presenter對應(yīng)方式

??對此逃糟,題主的解決辦法是:一個Presenter不再對應(yīng)一個View吼鱼,而是對應(yīng)一個功能模塊。比如有三個頁面:登錄绰咽、注冊菇肃、重置密碼。按傳統(tǒng)的MVP會有三個Presenter(LoginPresenter取募、RegisterPresenter琐谤、ResetPwdPresenter),現(xiàn)在通通都用同一個Presenter(AccountPresenter)取而代之玩敏。
??可能有些看官要打臉了斗忌,那你這個AccountPresenter豈不是包含了三個頁面的所有網(wǎng)絡(luò)請求质礼,雖然的確不會再有冗余和重復(fù)的代碼,但一來织阳,這個AccountPresenter也太不專一且臃腫了几苍;二來,這個Presenter還怎么跟專屬的某個Activity對應(yīng)陈哑?
??廢話不多說妻坝,還是直接上代碼吧。

AccountPresenter

    /**
     * 登錄
     */
    public static void login(final String mobile, String pwd, BaseObserver<LoginResult> observer) {
        observer.addParam("mobile", mobile)
                .addParam("password", pwd)
                .post(AppConfig.UrlConfig.LOGIN);
    }

    /**
     * 發(fā)送短信驗證碼
     */
    public static void snedSmsCode(String mobile, String sign, String imageCode, BaseObserver observer) {
        observer.addParam("mobile", mobile)
                .addParam("image_code", imageCode)
                .post(AppConfig.UrlConfig.FETCH_SMS_CODE);
    }

    /**
     * 注冊新用戶
     */
    public static void register(String mobile, String smsCode, String pwd, BaseObserver observer) {
        observer.addParam("mobile", mobile)
                .addParam("sms_code", smsCode)
                .addParam("password", pwd)
                .post(AppConfig.UrlConfig.REGISTER);
    }

    /**
     * 重置密碼
     */
    public static void resetPwd(String mobile, String smsCode, String pwd, BaseObserver observer) {
        observer.addParam("mobile", mobile)
                .addParam("sms_code", smsCode)
                .addParam("password", pwd)
                .post(AppConfig.UrlConfig.FORGET_PASSWORD);
    }

是不是有點反常識惊窖,我們居然把登錄刽宪、注冊、重置密碼三個頁面的所有網(wǎng)絡(luò)請求都整合進同一個Presenter里界酒,也就是說圣拄,我們?nèi)齻€Activity都可以用同一個Presenter,而且還能一一對應(yīng)毁欣,不信接著看代碼

LoginActivity

    private void doLogin() {
        AccountPresenter.login(mMobile, mPwd, new BaseObserver<LoginResult>(this) {
            @Override
            public void onSuccess(LoginResult loginResult) {
                startActivity(new Intent(LoginActivity.this, MainActivity.class));
                finish();
            }

            @Override
            public void onError(String code, String msg) {
                super.onError(code, msg);
            }
        });
    }

RegisterActivity

    private void doRegister() {
        AccountPresenter.register(mPhone, mSMSCode, mPwd, new BaseObserver(this) {
            @Override
            public void onSuccess(Object o) {
                ToastUtil.show("注冊成功");
            }

            @Override
            public void onError(String code, String msg) {
                super.onError(code, msg);
            }
        });
    }

ResetPwdActivity

    private void doResertPwd() {
        AccountPresenter.resetPwd(mPhone, mSMSCode, mPwd, new BaseObserver(this) {
            @Override
            public void onSuccess(Object o) {
               ToastUtil.show("重置密碼成功");
            }

            @Override
            public void onError(String code, String msg) {
                super.onError(code, msg);
            }
        });
    }

講解
??可能有些看官已經(jīng)發(fā)現(xiàn)了庇谆,Presenter里面的方法都是靜態(tài)的,每個Activity按需調(diào)用其中某個或若干個方法即可凭疮。這種情況下饭耳,P層有點類似于工具類,不再和特定的View綁死执解,而是可以服務(wù)于多個視圖頁面寞肖。但問題來了,我們習慣讓P層幫我們處理好Loading動畫衰腌,比如網(wǎng)絡(luò)請求開始時自動開始轉(zhuǎn)菊花新蟆,網(wǎng)絡(luò)請求結(jié)束后讓Loading消失。最重要的是右蕊,我們希望Activity結(jié)束時琼稻,能自動取消未完成的網(wǎng)絡(luò)請求。上面提到的這些饶囚,題主都是有實現(xiàn)的帕翻,不過換了種方式,放到了BaseObserver里處理坯约。
??沒錯熊咽,BaseObserver才是和View層綁定的重要元素。大家可以看到闹丐,每次new BaseObserver,構(gòu)造函數(shù)中都會傳入this被因,這個this其實是一個接口(ILoadingView)卿拴,而我們的Activity繼承的BaseActivity本身實現(xiàn)了這個接口衫仑,從而建立綁定關(guān)系。不妨來看一下這幾者的代碼堕花。

ILadingView

public interface ILoadingView {
    /**
     * 展示Loading動畫
     */
    void showLoadingDialog(String msg);

    /**
     * 讓Loading動畫消失
     */
    void hideLoadingDialog();

    /**
     * 保存當前頁面所有網(wǎng)絡(luò)請求文狱,在頁面結(jié)束時以便取消網(wǎng)絡(luò)請求
     */
    void addNetRequest(Disposable d);
}

BaseActivity

public abstract class BaseActivity extends AppCompatActivity implements ILoadingView {
    protected Dialog mLoadingDialog;
    protected List<Disposable> mRequestList;
    
    @Override
    public void showLoadingDialog(String msg) {
        mLoadingDialog.show(msg);
    }

    @Override
    public void hideLoadingDialog() {
        mLoadingDialog.dismiss();
    }

    @Override
    public void addNetRequest(Disposable d) {
        if (null == mRequestList) {
            mRequestList = new ArrayList<>();
        }
        mRequestList.add(d);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        RetrofitFactory.cancel(mRequestList);
    }
}

BaseObserver

public abstract class BaseObserver<T> implements Observer<BaseResponse> {
    private ILoadingView mLoadingView;

    public BaseObserver(ILoadingView loadingView) {
        this.mLoadingView = loadingView;
    }
    
    @Override
    public void onSubscribe(Disposable d) {
        if (null != mLoadingView) {
            mLoadingView.addNetRequest(d);
        }
        onStart(d);
    }

    public void onStart(Disposable d) {
        mLoadingView.showLoadingDialog("加載中");
    }

    public void onError(String code, String msg) {
        ToastUtil.show(msg);
    }

    public void onFinish() {
        mLoadingView.hideLoadingDialog();
    }
}

最后總結(jié)一下吧

  1. 至此基本已經(jīng)解釋清楚了,我們的Activity和Fragment實現(xiàn)了ILadingView這個接口缘挽,并且實現(xiàn)了里面的方法瞄崇,比如showLoading會轉(zhuǎn)菊花,addNetRequest會把網(wǎng)絡(luò)請求存下來以便頁面退出時結(jié)束掉壕曼。在new BaseObserver時再把實現(xiàn)的ILoadingView傳入其中苏研,從而建立綁定關(guān)系。
  2. 由于P層綁定視圖的任務(wù)已經(jīng)轉(zhuǎn)移到BaseObserver中腮郊,因此Presenter可以直接做成靜態(tài)工具類摹蘑,從而可以服務(wù)于多個頁面,而不會相互干擾轧飞。

未完待續(xù)

回到開篇提的第一個問題:多個Presenter間存在重復(fù)的代碼時衅鹿,該如何優(yōu)化?通過以上改進后的MVP框架过咬,我們很好解決了這個問題大渤,讓Presenter間不再有重復(fù)冗余的代碼。但第二個問題:如果App項目在網(wǎng)絡(luò)請求中夾雜了藍牙等非常多的重度操作掸绞,該如何優(yōu)化兼犯,這問題還是沒解決。所以集漾,如果你也被第二個問題所困擾切黔,不妨期待接下來的第二個篇章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末具篇,一起剝皮案震驚了整個濱河市纬霞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驱显,老刑警劉巖诗芜,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異埃疫,居然都是意外死亡伏恐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門栓霜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翠桦,“玉大人,你說我怎么就攤上這事∠眨” “怎么了丛晌?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斗幼。 經(jīng)常有香客問我澎蛛,道長,這世上最難降的妖魔是什么蜕窿? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任谋逻,我火速辦了婚禮,結(jié)果婚禮上桐经,老公的妹妹穿的比我還像新娘毁兆。我一直安慰自己,他們只是感情好次询,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布荧恍。 她就那樣靜靜地躺著,像睡著了一般屯吊。 火紅的嫁衣襯著肌膚如雪送巡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天盒卸,我揣著相機與錄音骗爆,去河邊找鬼。 笑死蔽介,一個胖子當著我的面吹牛摘投,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虹蓄,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼犀呼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了薇组?” 一聲冷哼從身側(cè)響起外臂,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎律胀,沒想到半個月后宋光,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡炭菌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年罪佳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黑低。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡赘艳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情第练,我是刑警寧澤阔馋,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布玛荞,位于F島的核電站娇掏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勋眯。R本人自食惡果不足惜婴梧,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望客蹋。 院中可真熱鬧塞蹭,春花似錦、人聲如沸讶坯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辆琅。三九已至漱办,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婉烟,已是汗流浹背娩井。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留似袁,地道東北人洞辣。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像昙衅,于是被迫代替她去往敵國和親扬霜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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