本文簡述了在實際專案中使用MVP架構遇到的問題和相應處理,最終整理出升級版的MVP架構。
0 Android架構系列文章
該系列文章會不斷更新Android專案開發中一些好的架構和小技巧
系列一 Android架構系列-基於MVP建立適合自己的架構
系列二 Android架構系列-如何優美的寫Intent
系列三 Android架構系列-開發規範
系列四 Android架構系列-封裝自己的okhttp
系列五 Android架構系列-MVP架構的實際應用
1 原有的MVP架構
在系列文章的第一篇文章中介紹了使用MVP架構。詳細可以回看該文章
MVP的結構如下圖:
2 實際專案中應用出現的問題
MVP是一種程式碼的分層思想,其實沒有用到任何庫,只是告訴了你如何規整的放置程式碼。使各個層次的程式碼各司其職,增加易讀性和可測試性。
但是真實開發中發現,MVP是一種模組中高內聚的模式,Presenter層接管了Activity中的邏輯實現。相應出現了以下幾個問題:
2.1 Presenter生命週期的問題
Presnter層和View層是一一對應的,所以Presnter層和View層生命週期是一致的。
但是現在所有邏輯寫在Presenter層中,如果其他地方需要呼叫就只能通過靜態方法呼叫,不能再次new 一個 Presenter例項
2.2 跨模組呼叫
實際開發中經常會有在B模組呼叫A模組的部分邏輯。
比如發帖時要判斷使用者是否登入,並且獲取當前登入使用者資訊。即在發帖模組要獲取使用者模組的資料和邏輯。
如果邏輯寫在Presenter中,則其他模組只能直接讀取當前使用者快取,然後在自己模組解析。還是增加了模組間的耦合。
3 優化的MVP分層
在這裡將Model層命名為Interactor。我們將每個模組內部的原子邏輯(一個功能而不是一系列邏輯功能)都寫在interactor中,Presenter層只負責接收view事件,呼叫interactor功能,再回饋view。
在此,一個Presenter可以持有多個模組的Interactor,這樣就可以訪問相應功能邏輯和資料。並且不需要在自己模組對其他模組資料進行解析處理。
該優化後的分層和普通的MVP最大的區別在於,將Presenter層解放出來,裡面不再放具體邏輯,直接呼叫邏輯。
分析各個層:
3.1 View層
- 只持有和自己一一對應的Presenter例項,通過實現介面方式呼叫
- 負責頁面的控制元件初始化, 重新整理顯示頁面, 監聽元素事件
- 不應該出現狀態, 邏輯等程式碼(除非只跟頁面相關的很小的邏輯,比如一個欄位標識密碼是否可見)
3.2 Presenter層
- 持有和自己一一對應的View例項,可以持有多個模組的Interactor層。通過實現介面方式呼叫。
- 作為View和Interactor層的Glue層, 接收view操作, 呼叫模組中方法, 返回資料給view。
3.3 Interactor層
- 本模組中原子性邏輯封裝,非一個系列的邏輯,這樣保證其他地方可以方便的呼叫。
- Interactor層中不應該出現其他模組的引用
- Interactor層的返回。如果是同步直接返回資料, 如果是非同步在contract.interactor中定義callback。
4 一個程式碼示例
下面簡述一個Sample 登入程式碼
LoginContract層:
public interface LoginContract {
interface View extends BaseView {
/**
* 跳轉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();
/**
* 獲取當前登入使用者
* @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() {
//跳轉Home頁面
ToastUtils.showShort(GlobalApp.getInstance().getContext(), "登入成功, 跳轉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) {
//模擬非同步網路請求登入
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層。下面展示其他模組如何呼叫Login模組的邏輯或者資料。
假如Home頁面點選發帖,需要判斷當前登入狀態。則在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()) {
// 跳轉登入
// TODO: 16/8/30
return;
}
//跳轉發帖頁面
// TODO: 16/8/30
}
}複製程式碼
具體的程式碼在Github專案:BaseAndroidProject
5 結尾
無論是MVP還是什麼架構,最終的目的都是寫出易讀性和測試性強的程式碼。所以不要對於架構鑽牛角尖,過度設計不可取。在實際開發中架構自然會跟著升級。謹記!均衡合理!!
更多文章關注我的公眾號