本文簡述了在實際項目中使用MVP架構(gòu)遇到的問題和相應(yīng)處理,最終整理出升級版的MVP架構(gòu)寂曹。
0 Android架構(gòu)系列文章
該系列文章會不斷更新Android項目開發(fā)中一些好的架構(gòu)和小技巧
系列一 Android架構(gòu)系列-基于MVP創(chuàng)建適合自己的架構(gòu)
系列二 Android架構(gòu)系列-如何優(yōu)美的寫Intent
系列三 Android架構(gòu)系列-開發(fā)規(guī)范
系列四 Android架構(gòu)系列-封裝自己的okhttp
系列五 Android架構(gòu)系列-MVP架構(gòu)的實際應(yīng)用
1 原有的MVP架構(gòu)
在系列文章的第一篇文章中介紹了使用MVP架構(gòu)腹备。詳細(xì)可以回看該文章
MVP的結(jié)構(gòu)如下圖:
2 實際項目中應(yīng)用出現(xiàn)的問題
MVP是一種代碼的分層思想衬潦,其實沒有用到任何庫,只是告訴了你如何規(guī)整的放置代碼植酥。使各個層次的代碼各司其職别渔,增加易讀性和可測試性。
但是真實開發(fā)中發(fā)現(xiàn)惧互,MVP是一種模塊中高內(nèi)聚的模式,Presenter層接管了Activity中的邏輯實現(xiàn)喇伯。相應(yīng)出現(xiàn)了以下幾個問題:
2.1 Presenter生命周期的問題
Presnter層和View層是一一對應(yīng)的喊儡,所以Presnter層和View層生命周期是一致的。
但是現(xiàn)在所有邏輯寫在Presenter層中稻据,如果其他地方需要調(diào)用就只能通過靜態(tài)方法調(diào)用艾猜,不能再次new 一個 Presenter實例
2.2 跨模塊調(diào)用
實際開發(fā)中經(jīng)常會有在B模塊調(diào)用A模塊的部分邏輯买喧。
比如發(fā)帖時要判斷用戶是否登錄,并且獲取當(dāng)前登錄用戶信息匆赃。即在發(fā)帖模塊要獲取用戶模塊的數(shù)據(jù)和邏輯淤毛。
如果邏輯寫在Presenter中,則其他模塊只能直接讀取當(dāng)前用戶緩存算柳,然后在自己模塊解析低淡。還是增加了模塊間的耦合。
3 優(yōu)化的MVP分層
在這里將Model層命名為Interactor瞬项。我們將每個模塊內(nèi)部的原子邏輯(一個功能而不是一系列邏輯功能)都寫在interactor中蔗蹋,Presenter層只負(fù)責(zé)接收view事件,調(diào)用interactor功能囱淋,再回饋view猪杭。
在此,一個Presenter可以持有多個模塊的Interactor妥衣,這樣就可以訪問相應(yīng)功能邏輯和數(shù)據(jù)皂吮。并且不需要在自己模塊對其他模塊數(shù)據(jù)進(jìn)行解析處理。
該優(yōu)化后的分層和普通的MVP最大的區(qū)別在于税手,將Presenter層解放出來蜂筹,里面不再放具體邏輯,直接調(diào)用邏輯冈止。
分析各個層:
3.1 View層
- 只持有和自己一一對應(yīng)的Presenter實例狂票,通過實現(xiàn)接口方式調(diào)用
- 負(fù)責(zé)頁面的控件初始化, 刷新顯示頁面, 監(jiān)聽元素事件
- 不應(yīng)該出現(xiàn)狀態(tài), 邏輯等代碼(除非只跟頁面相關(guān)的很小的邏輯,比如一個字段標(biāo)識密碼是否可見)
3.2 Presenter層
- 持有和自己一一對應(yīng)的View實例熙暴,可以持有多個模塊的Interactor層闺属。通過實現(xiàn)接口方式調(diào)用。
- 作為View和Interactor層的Glue層, 接收view操作, 調(diào)用模塊中方法, 返回數(shù)據(jù)給view周霉。
3.3 Interactor層
- 本模塊中原子性邏輯封裝掂器,非一個系列的邏輯,這樣保證其他地方可以方便的調(diào)用俱箱。
- Interactor層中不應(yīng)該出現(xiàn)其他模塊的引用
- Interactor層的返回国瓮。如果是同步直接返回數(shù)據(jù), 如果是異步在contract.interactor中定義callback。
4 一個代碼示例
下面簡述一個Sample 登錄代碼
LoginContract層:
public interface LoginContract {
interface View extends BaseView {
/**
* 跳轉(zhuǎn)Home
*/
void goHome();
}
interface Presenter extends BasePresenter {
/**
* login
* @param phone
* @param password
*/
void onLogin(String phone, String password);
}
interface Interactor {
/**
* do login
* @param phone
* @param password
* @param callback
*/
void doLogin(String phone, String password, LoginCallback callback);
interface LoginCallback {
void onSuccess(UserInfo user_info);
void onFailure(String msg);
}
/**
* 是否登錄
* @return
*/
boolean isLogin();
/**
* 獲取當(dāng)前登錄用戶
* @return
*/
UserInfo getLoginUser();
}
}
LoginActivity:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
mPresenter = new LoginPresenter(this);
}
@OnClick(R.id.btnLogin)
public void onLogin() {
mPresenter.onLogin(editPhone.getText().toString(), editPassword.getText().toString());
}
@OnClick(R.id.txtRegister)
public void goRegister() {
ToastUtils.showShort(GlobalApp.getInstance().getContext(), "goRegister");
}
@OnClick(R.id.txtForgetPwd)
public void goForgetPwd() {
ToastUtils.showShort(GlobalApp.getInstance().getContext(), "goForgetPwd");
}
@Override
public void showToast(String msg) {
ToastUtils.showShort(GlobalApp.getInstance().getContext(), msg);
}
@Override
public void goHome() {
//跳轉(zhuǎn)Home頁面
ToastUtils.showShort(GlobalApp.getInstance().getContext(), "登錄成功, 跳轉(zhuǎn)Home頁面");
Intent intent = new Intent(this, HomeActivity.class);
startActivity(intent);
finish();
}
LoginPresnter:
public class LoginPresenter implements LoginContract.Presenter {
private LoginContract.View mView;
private LoginContract.Interactor mInteractor;
public LoginPresenter(LoginContract.View view) {
mView = view;
mInteractor = new LoginInteractor();
}
@Override
public void start() {
}
@Override
public void onLogin(String phone, String password) {
if(StringUtils.isEmpty(phone)) {
mView.showToast("Empty phone");
return;
}
if(StringUtils.isEmpty(password)) {
mView.showToast("Empty password");
return;
}
mInteractor.doLogin(phone, password, new LoginContract.Interactor.LoginCallback() {
@Override
public void onSuccess(UserInfo user_info) {
mView.goHome();
}
@Override
public void onFailure(String msg) {
mView.showToast(msg);
}
});
}
}
LoginInteractor:
public class LoginInteractor implements LoginContract.Interactor {
private MyOkHttp mApi;
private ACache mCache;
//緩存key
private final String CACHE_KEY_USERINFO = "CACHE_KEY_USERINFO";
public LoginInteractor() {
mApi = MyOkHttp.get();
mCache = ACache.get(GlobalApp.getInstance().getContext());
}
@Override
public void doLogin(String phone, String password, final LoginCallback callback) {
//模擬異步網(wǎng)絡(luò)請求登錄
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
UserInfo userInfo = new UserInfo();
userInfo.uid = "1212121";
userInfo.userName = "tsy12321";
userInfo.token = "wqw13w12312wsqw12";
//存入緩存
mCache.put(CACHE_KEY_USERINFO, userInfo);
callback.onSuccess(userInfo);
}
}, 2000);
}
@Override
public boolean isLogin() {
UserInfo userInfo = (UserInfo) mCache.getAsObject(CACHE_KEY_USERINFO);
if(!StringUtils.isEmpty(userInfo.uid) && !StringUtils.isEmpty(userInfo.token)) {
return true;
}
return false;
}
@Override
public UserInfo getLoginUser() {
return (UserInfo) mCache.getAsObject(CACHE_KEY_USERINFO);
}
}
由以上示例可以看出狞谱,具體的邏輯都放在了Interactor層乃摹。下面展示其他模塊如何調(diào)用Login模塊的邏輯或者數(shù)據(jù)。
假如Home頁面點擊發(fā)帖跟衅,需要判斷當(dāng)前登錄狀態(tài)孵睬。則在HomePresenter中同時持有LoginInteractor的實例。
public class HomePresenter implements HomeContract.Presenter {
private HomeContract.View mView;
private HomeContract.Interactor mInteractor;
private LoginContract.Interactor mLoginInteractor;
public HomePresenter(HomeContract.View view) {
mView = view;
mInteractor = new HomeInteractor();
mLoginInteractor = new LoginInteractor();
}
@Override
public void start() {
}
@Override
public void onPost() {
//判斷用戶有沒有登錄
if(!mLoginInteractor.isLogin()) {
// 跳轉(zhuǎn)登錄
// TODO: 16/8/30
return;
}
//跳轉(zhuǎn)發(fā)帖頁面
// TODO: 16/8/30
}
}
具體的代碼在Github項目:BaseAndroidProject
5 結(jié)尾
無論是MVP還是什么架構(gòu)伶跷,最終的目的都是寫出易讀性和測試性強(qiáng)的代碼掰读。所以不要對于架構(gòu)鉆牛角尖秘狞,過度設(shè)計不可取。在實際開發(fā)中架構(gòu)自然會跟著升級蹈集。謹(jǐn)記烁试!均衡合理!拢肆!
更多文章關(guān)注我的公眾號