前言
通過一個小的登入功能子產品案例,幫助大家了解MVP。最終實作一個結合Rxjava2,Retrofit 的MVP通用架構。代碼放到github上。(如有錯誤之處,請在評論區指出,謝謝。如果感覺寫的不錯,請點贊,關注,謝謝。)
目錄:
Android MVP-程式設計思想1(什麼是MVC-MVP-MVVM模式?)
Android MVP-程式設計思想2(代碼實作初級版)
Android MVP-程式設計思想3(記憶體洩露問題處理,基類封裝,有沒有必要再使用軟引用?)
Android MVP-程式設計思想4(AOP思想-動态代理運用,反射建立M層執行個體對象)
Android MVP-程式設計思想5(如何處理多個P層的問題?)
Android MVP-程式設計思想6(依賴注入多個P層優化—注解,反射)
Android MVP-程式設計思想7(為什麼使用代理類抽取通用方法而不是工具類?,基類BaseMvpFragment)
未完待續--------
Android MVP-程式設計思想8(內建Rxjava2,Retrofit)
MVP 實戰
那麼我們下面就要将這個類中的代碼改寫為 MVP 的寫法,回顧上面提及的 MVP 架構的思想,它是将 View 層與 Model 層徹底隔離,意味着 View 和 Model 都不再持對方的引用,它們通過一個第三者 Presenter 來代理事物的傳遞,是以 Presenter 層會持有 Model 與 View 層的引用,這是第一步。
第二步,是将它們之間的聯系抽象出來,以接口的方式互相調用,是以 Model 、View、Presenter 各自擁有自己的接口和抽象方法,是以這就會無形的多出了三個接口類,這也就是 MVP 的缺點之一。是以,為了較少的建立接口類,我們就給這三層接口定義了一個契約接口,把它們更加緊密的結合在一起,方法檢視,例如代碼這樣寫:參考谷歌官方mvp案例todo-mvp
下面我們以登入功能為例行了解 MVP 架構的代碼寫法。
第一步
首先我們參考谷歌todo-mvp 寫一個契約類,定義互動行為。契約類的目的是把MVP的行為定義放在一起,友善閱讀和維護
public class LoginContract {
/**
* V 定義View的行為,調用者是P
*/
interface IView {
//登入成功跳轉到主界面
void goToMainActivity();
//請求網絡顯示等待框
void showProgress(boolean isShow);
//顯示提示框-登入失敗,或其他提示資訊
void showToast(String string);
}
/**
* P(中轉站和資料處理者) 定義View與Model的互動行為,調用者是V。P做中轉
*/
interface IPresenter {
void onClickLogin(String username, String pwd);
}
/**
* M 資料提供者
*/
interface IModel {
void getUserInfo(String username, String pwd, ICallBack callBack);
}
}
第二步,定義MVP行為接口的實作類
輔助類ICallBack,用來回調http請求的資料
public interface ICallBack<T> {
void success(T data);
void error(String error);
}
M層的實作類LoginModel,提供資料,網絡資料,本地資料
public class LoginModel implements LoginContract.IModel {
@Override
public void getUserInfo(String username, String pwd, final ICallBack callBack) {
//todo 模拟 http請求
Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
callBack.success("成功");
}
};
handler.postDelayed(runnable, 2000);
}
}
V層的實作類LoginActivity,控制視圖顯示,要調用P層接口,是以要持有P對象的引用
public class LoginActivity extends AppCompatActivity implements LoginContract.IView {
LoginContract.IPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPresenter = new LoginPresenter(this);
findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mPresenter.onClickLogin("xxx", "xxx");
}
});
}
@Override
public void goToMainActivity() {
runOnUiThread(new Runnable() {
@Override
public void run() {
//todo 登入成功跳轉到主界面
Log.i("mvp_", "登入成功跳轉到主界面");
}
});
}
@Override
public void showProgress(boolean isShow) {
if (isShow) {
Log.i("mvp_", "顯示等待框");
} else {
Log.i("mvp_", "隐藏等待框");
}
}
@Override
public void showToast(String str) {
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
}
}
P層實作類LoginPresenter,是M和V互動的協調者,是以要持有M,V對象的引用
注意點:網絡請求是耗時操作,我們 Presenter 層持有了 View 層的引用,也就是 Activity 的引用,在網絡請求過程中,Activity被使用者或者系統關閉,這時 View 相當于被摧毀了,如果不進行判斷 View 引用是否為空,當資料傳回時,就會造成空指針異常,是以每次都要進行判空才合理。
public class LoginPresenter implements LoginContract.IPresenter {
private LoginContract.IModel mModel;
private LoginContract.IView mView;
public LoginPresenter(LoginContract.IView view) {
this.mModel = new LoginModel();
this.mView = view;
}
@Override
public void onClickLogin(String username, String pwd) {
mView.showProgress(true);
mModel.getUserInfo(username, pwd, new ICallBack<String>() {
@Override
public void success(String data) {
if (mView != null) {
mView.showToast("登入成功");
mView.showProgress(false);
mView.goToMainActivity();
}
}
@Override
public void error(String error) {
if (mView != null) {
mView.showProgress(false);
mView.showToast("登入失敗");
}
}
});
}
}
上面使用最簡單的方式,示範了MVP的設計思想。目的是讓大家了解,MVP模式是如何把業務邏輯,資料與V層進行分離的。再回想對比MVC是不是覺得MVP模式,職責分離的更清楚。
運作,點選登入按鈕,列印日志如下:---------------------
2019-12-11 14:34:06.990 6376-6376/chongchong.wei.rx_retrofit_mvp I/mvp_: 顯示等待框
2019-12-11 14:34:09.002 6376-6376/chongchong.wei.rx_retrofit_mvp I/mvp_: 隐藏等待框
2019-12-11 14:34:09.002 6376-6376/chongchong.wei.rx_retrofit_mvp I/mvp_: 登入成功跳轉到主界面
當然上面的代碼隻是幫助大家了解MVP模式的思想,一些細節問題沒有處理。下一節我們對代碼進行優化,抽離,封裝。