人之所以能,是相信能。
說到MVP,大家應該都不陌生了,由於其高度解等等優點,越來越多的專案使用這個設計模式。然而,優點雖在,缺點也不少,其中一個就是類多了很多,而且V與P直接要專案通訊,那麼P就得持有V得例項,但如果活動掛掉了,如果沒有對V進行釋放,還有導致記憶體溢位得問題,而且,那麼多的介面函式,看得到人眼花繚亂,也使得很多人在使用這個模式的時候望而尚步。
迴歸正題,最近在進行程式碼重構,決定採用MVP模式進行開發。如果我們不進行封裝,單純地簡單使用MVP來開發,這要就會出現如上的問題,介面和類多而且重複。和別人協同開發也存在問題。那麼對MVP模式進行封裝就顯得很重要了。當然,一千個人中有一千個哈姆雷特,這裡提供一下我的思路,供大家參考。
什麼是MVP模式
MVP模式相當於在MVC模式中又加了一個Presenter用於處理模型和邏輯,將View和Model完全獨立開,在android開發中的體現就是activity僅用於顯示介面和互動,activity不參與模型結構和邏輯。
使用MVP模式會使得程式碼多出一些介面但是使得程式碼邏輯更加清晰,尤其是在處理複雜介面和邏輯時,我們可以對同一個activity將每一個業務都抽離成一個Presenter,這樣程式碼既清晰邏輯明確又方便我們擴充套件。當然如果我們的業務邏輯本身就比較簡單的話使用MVP模式就顯得,沒那麼必要。所以我們不需要為了用它而用它,具體的還是要要業務需要。
其實,簡而言之:view就是UI,model就是資料處理,而persenter則是他們的紐帶。
使用MVP的結構
再對比下MVC
MVP模式還是存在一些不足之處的,最大的不足就是類的快速增多,但相對於MVC的臃腫、MVP的高度解耦來說,類的增多可能就灑灑水啦~~~
封裝思路
上圖介紹:
Contract:契約類,一個功能模組中View介面、Model介面和請求資料回撥統一在對應模組的Contract中定義,便於管理。
ViewInterface: view層介面,定義了view中的UI操作
ModelInterface: model層介面,定義了model負責的資料操作方法,如請求介面,運算元據庫等
CallbackInterface: model層運算元據完成後的回撥
BasePersenter: Persenter父類,主要是對相關view的獲取,銷燬等操作
View: view層實現類,主要就是Activity或Fragment,負責UI展示和事件響應
Model: model層實現類,就是依據業務,請求對應介面或資料庫,並將結果返給回撥CallBack
Persenter: persenter層類,負責業務邏輯處理,view將響應傳給persenter,persenter負責呼叫model,並將結果返回給view供其展示
框架封裝
1、Presenter的封裝
/**
* Description: Presenter的根父類
* Created by jia on 2016/10/27.
* 人之所以能,是相信能
*/
public abstract class BasePresenter<T> {
//View介面型別的軟引用
protected Reference<T> mViewRef;
public void attachView(T view) {
//建立關係
mViewRef = new SoftReference<T>(view);
}
protected T getView() {
return mViewRef.get();
}
public boolean isViewAttached() {
return mViewRef != null && mViewRef.get() != null;
}
public void detachView() {
if (mViewRef != null) {
mViewRef.clear();
}
}
}
複製程式碼
先來觀察下這個base類:
先設定一泛型T,T為與presenter相關的view。BasePresenter中持有一個view的軟引用。
在關聯方法中將view物件傳入,並存入軟引用中,建立獲取、取消關聯和判斷方法。
至於使用軟引用,是為了防止所持的view都銷燬了,但presenter一直持有,導致記憶體洩漏。
2、view的封裝
view的封裝,主要是BaseActivity和BaseFragment的封裝。
2.1、BaseActivity
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends FragmentActivity {
public String TAG = getClass().getSimpleName() + "";
protected T mPresenter;
public Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
initActivityView(savedInstanceState);
mContext = BaseActivity.this;
//建立presenter
mPresenter = createPresenter();
// presenter與view繫結
if (null != mPresenter) {
mPresenter.attachView((V) this);
}
findViewById();
getData();
}
/**
* 關於Activity的介面填充的抽象方法,需要子類必須實現
*/
protected abstract void initActivityView(Bundle savedInstanceState);
/**
* 載入頁面元素
*/
protected abstract void findViewById();
/**
* 建立Presenter 物件
*
* @return
*/
protected abstract T createPresenter();
protected abstract void getData();
@Override
protected void onDestroy() {
super.onDestroy();
...
if (null != mPresenter) {
mPresenter.detachView();
}
}
}
複製程式碼
BaseActivity設定兩個泛型——V和P,明顯地,分別代表對應的View和Presenter。
其持有一個BasePresenter,在onCreated方法中,使用createPresenter方法返回對應的BasePresenter的子類,我們就可以使用了。
這裡注意一下view和presenter的處理:在onCreated中建立Presenter物件,但其內部的view軟引用還是空;在onResume中關聯view,此時presenter已經持有view的軟引用;當然,還需要在onDestroy中取消關聯。
至於其他的封裝就不再介紹了,相信大家肯定還有更優的封裝方法。
2.2 BaseFragment
public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {
public String TAG = getClass().getSimpleName() + "";
private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";
protected T mPresenter;
//定義一個View用來儲存Fragment建立的時候使用打氣筒工具進行的佈局獲取物件的儲存
protected View view;
/**
* 當Fragment進行建立的時候執行的方法
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();//建立presenter
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden());
}
/**
* 這個方法是關於Fragment完成建立的過程中,進行介面填充的方法,該方法返回的是一個view物件
* 在這個物件中封裝的就是Fragment對應的佈局
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = initFragmentView(inflater);
return view;
}
/**
* 這個方法當onCreateView方法中的view建立完成之後,執行
* 在inflate完成view的建立之後,可以將對應view中的各個控制元件進行查詢findViewById
*/
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
initFragmentChildView(view);
}
/**
* 這個方法是在Fragment完成建立操作之後,進行資料填充操作的時候執行的方法
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initFragmentData(savedInstanceState);
}
/**
* 完成打氣筒操作
*/
protected abstract View initFragmentView(LayoutInflater inflater);
/**
* 進行findViewById的操作
*
* @param view 打氣筒生成的View物件
*/
protected abstract void initFragmentChildView(View view);
/**
* 網路資料填充的操作
*
* @param savedInstanceState
*/
protected abstract void initFragmentData(Bundle savedInstanceState);
/**
* 建立Presenter物件
*/
protected abstract T createPresenter();
@Override
public void onResume() {
super.onResume();
if (null != mPresenter) {
mPresenter.attachView((V) this);
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (null != mPresenter) {
mPresenter.detachView();
}
}
}
複製程式碼
BaseFragment與BaseA類似就不再累贅。
3、Contract契約類
契約,顧名思義,規範定義,定義功能和模板。
在契約類中定義View的介面,Model的介面。因為Model將資料返給Presenter是使用回撥方式,所以還需要再契約類中定義對應的回撥。
具體看示例吧。
實戰
這裡我們以登入功能模組為例:
1、契約類
/**
* Description:
* Created by jia on 2017/12/20.
* 人之所以能,是相信能
*/
public class LoginContract {
public interface LoginView{
void onCheckFormatSuccess();
void onCheckFormatFail(String info);
void onLoginSuccess(Login login);
void onLoginFail(String errorInfo);
}
public interface LoginModel{
void login(String name,String password,LoginCallBack callBack);
}
public interface LoginCallBack{
void onSuccess(Login login);
void onFail(String errorInfo);
}
}
複製程式碼
這裡定義了登入頁面的view介面、model介面和對應的回撥。
在view中,只定義與UI展示的相關方法,如檢查賬號密碼格式成功(失敗)、登入成功(失敗)等。
model負責資料請求,所以在介面中只定義了登入的方法。
回撥定義了登入成功還是失敗的方法。
2、Model實現類
/**
* Description: 登入 Model實現類
* Created by jia on 2017/12/20.
* 人之所以能,是相信能
*/
public class LoginModelImpl implements LoginContract.LoginModel {
/**
* 登入方法
* @param name
* @param password
* @param callBack
*/
@Override
public void login(String name, String password, final LoginContract.LoginCallBack callBack) {
LoginNetUtils.getInstance().login(name, password, new BaseSubscriber<Login>() {
@Override
public void onSuccess(Login login) {
callBack.onSuccess(login);
}
@Override
public void onFail(String info) {
callBack.onFail(info);
}
});
}
}
複製程式碼
建立Model實現類,重寫其登入方法既可,將登入介面交給回撥。
3、Presenter
/**
* Description: 登入主持類
* Created by jia on 2017/12/20.
* 人之所以能,是相信能
*/
public class LoginPresenter extends BasePresenter<LoginContract.LoginView> {
private LoginModelImpl model;
public LoginPresenter() {
model = new LoginModelImpl();
}
/**
* 檢查格式
*
* @param name
* @param password
*/
public void checkFormat(String name, String password) {
if (TextUtils.isEmpty(name)) {
getView().onCheckFormatFail("請輸入使用者名稱");
} else if (TextUtils.isEmpty(password)) {
getView().onCheckFormatFail("請輸入密碼");
} else if (password.length() < 6 || password.length() > 18) {
getView().onCheckFormatFail("密碼格式不正確");
} else {
getView().onCheckFormatSuccess();
login(name, password);
}
}
/**
* 登入
*
* @param name
* @param password
*/
public void login(String name, String password) {
model.login(name, password, new LoginContract.LoginCallBack() {
@Override
public void onSuccess(Login login) {
getView().onLoginSuccess(login);
}
@Override
public void onFail(String errorInfo) {
getView().onLoginFail(errorInfo);
}
});
}
}
複製程式碼
LoginPresenter整合BasePresenter,傳入LoginView最為泛型T。
內部持有Model實現類物件。
建立兩個方法,一個是檢查格式,一個是登入。兩個方法就是業務的處理。
如登入方法,登入返回後,在回撥中得到資料,也可以再進行一些邏輯判斷,將結果交給view的對應的方法。
注意這裡使用getView()方法就可以,因為在父類中getView方法直接返回的對應的view例項。
4、View
/**
* 登入介面
*/
public class LoginActivity extends BaseActivity<LoginContract.LoginView, LoginPresenter>
implements LoginContract.LoginView, View.OnClickListener {
...
@Override
protected void initActivityView(Bundle savedInstanceState) {
setContentView(R.layout.activity_login);
}
@Override
protected void findViewById() {
...
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter();
}
@Override
protected void getData() {
}
@Override
public void onCheckFormatSuccess() {
loading.show();
}
@Override
public void onCheckFormatFail(String info) {
RxToast.error(mContext, info).show();
}
@Override
public void onLoginSuccess(Login login) {
...
}
@Override
public void onLoginFail(String errorInfo) {
...
}
@Override
public void onClick(View view) {
...
}
...
}
複製程式碼
這裡的LoginActivity就是登入功能模組的view,其整合BaseActivity,傳入view和presenter泛型。
實現LoginView介面,重寫介面定義好的UI方法。
在createPresenter方法中建立LoginPresenter物件並返回。這樣,就可以使用mPresenter直接操作邏輯了。
再看下整個功能模組的事件流和資料流
大致就是這樣了,有不足的地方大家多提意見。^_^
更多精彩內容,關注我的微信公眾號——Android機動車