前言
??在淡到Android開發(fā)的框架模式時德召,不外乎MVC白魂、MVVM和MVP三種。而今天上岗,題主想介紹一種新的框架模式——MVPO福荸。這是題主在MVP的基礎(chǔ)上擴展出來的一種模式,新的模式當然是為了解決新的問題肴掷,而本人相信敬锐,在Android開發(fā)中困擾著題主的一些問題,同樣也困擾著大家呆瞻。
MVP存在的缺陷
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)化年堆。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é)一下吧
- 至此基本已經(jīng)解釋清楚了,我們的Activity和Fragment實現(xiàn)了ILadingView這個接口缘挽,并且實現(xiàn)了里面的方法瞄崇,比如showLoading會轉(zhuǎn)菊花,addNetRequest會把網(wǎng)絡(luò)請求存下來以便頁面退出時結(jié)束掉壕曼。在new BaseObserver時再把實現(xiàn)的ILoadingView傳入其中苏研,從而建立綁定關(guān)系。
- 由于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)化兼犯,這問題還是沒解決。所以集漾,如果你也被第二個問題所困擾切黔,不妨期待接下來的第二個篇章。