作者 夏至,歡迎轉載,但請保留這段申明
blog.csdn.net/u011418943/…
說到 MVP ,大家應該都不陌生了,由於其高度解耦等優點,越來越多的專案使用這個設計模式;然而,優點雖在,缺點也不少,其中一個就是類多了很多,而且 V 與 P 直接要專案通訊,那麼 P 就得持有 V 得例項,但如果 activity 掛掉了,如果沒有對 V 進行釋放,又有導致記憶體溢位得問題,而且,那麼多的介面函式,看得人眼花繚亂,也使得很多人在使用這個模式的時候望而卻步。
如果對 MVP 不瞭解,可以看我以前的這兩篇文章:
MVP 設計模式理解,實戰理解MVP
MVP+多執行緒+斷電續傳 實現app線上升級庫 (手把手教你打造自己的lib)
迴歸原題,最近把一個專案改成 MVP 的模式,用以前的方式,寫完之後,那麼多介面類,除了很多重複了之外,和別人共同開發也是個問題;那麼,封裝一些 base 基類就顯得尤為重要了。當然,一千個人中有一千個哈姆雷特,這裡就提供一下我的思路,大家可以結合自己的理解參考一下。
專案型別: 類似一個 雷豹清理大師的清理工具,先上傳個簡單的 效果圖吧,這樣會比較好理解一下:
1、封裝 BaseView
BaseView 的封裝中,我們把每個子類都會用到的方法放在這裡,由於是清理工具類的專案,那麼就有一個掃描的過程,還有載入失敗,所以,baseview 如下:
public interface BaseView {
void fail(String errorMsg);
// 掃描狀態更新,根據需要更新
void scanStatusShow(int current,int maxsize,long size,String path);
}複製程式碼
當然,這裡的 baseview 只是我這個專案的,實際上,你還可以在上面新增 夜間模式切換,顯示隱藏載入進度條等公有方法。
2、封裝 BasePresenter
上面中,我們提到,presenter 是持有view 的例項的,假如activity崩潰,而persenter繼續持有,那麼勢必會導致記憶體洩漏的問題的;所以,我們希望在activity崩潰時,也把 view 去掉;
那麼,basepresenter 時用抽象類好?還是介面類呢?比如網上很多人這樣寫:
public abstract class BasePresent{
public T view;
public void attach(T view){
this.view = view;
}
public void detach(){
this.view = null;
}
} 複製程式碼
這裡用了一個抽象類來實現,但我們參考 google 的 mvp 原始碼時,它的 basepresenter 是一個介面,就一個 start() 方法,然後再用一個契約類來實現的,如下:
/**
* This specifies the contract between the view and the presenter.
*/
public interface AddEditTaskContract {
interface View extends BaseView {
void showEmptyTaskError();
void showTasksList();
void setTitle(String title);
void setDescription(String description);
boolean isActive();
}
interface Presenter extends BasePresenter {
void saveTask(String title, String description);
void populateTask();
boolean isDataMissing();
}
} 複製程式碼
可以看到,它把相關的 view 和persenter 都放在 contract 這個類中,這樣做的好處在於,方便後期的維護,類的實現方法和介面都在這,方便查詢與除錯,所以,我們封裝的時候,也參考這種模式。但思考一個問題,我們的 basepresenter 是用抽象類好呢?好像介面呢?
答案是抽象,為什麼?
我們知道,介面是一個特殊的抽象類,介面裡面的方法都是抽象方法,如果定義成介面函式,則裡面的方法必須實現,則在 baseactivity 或者 basefragment 繼承時重寫了方法,而在 具體子類的 presenter 的時候也重寫了方法,這樣就多餘了,所以,書上看到這介面和抽象類的總結,覺得還不錯,如下:
- 優先使用介面
- 在既要定義子類的行為,又要為子類提供公共的功能時應選擇抽象類
所以,這裡的 basepresenter 就用 抽象類會好一點。
/**
* T 泛型,指的是baseview,當然也可以是其他的view 型別
* Created by zhengshaorui on 2017/6/22.
*/
public abstract class BasePresenter {
// 獲取 view 的view 例項
T view;
void onAttach(T view){
this.view = view;
};
// 解綁 view 層
void onDetch(){
this.view = null;
};
// view 資料的開始,一般再 oncreate 或者 onresume 中
// void start();
} 複製程式碼
可能你會疑問,這樣的話,我們的契約類怎麼寫?別急,往下看。
3、契約類 contract
回顧一下自己以前寫的 mvp 小demo,我們是不是 v 一個介面,p一個介面,除了結構上給人類很多之外,而且還大部分是重複的,涉及到 v 層資料的更新,p在獲取了v的例項之後,通過觀察者模式,獲取model的資料更新到v,那麼這裡介面就存在重複的,我們可以用基類繼續封裝;
而且mvp中的p,我們說過,它就是個紐帶,那麼就不要新增什麼邏輯性的東西在這裡,保證 p 的輕薄,v 就是更新 UI 的。
ok,首先,先看一下我的 contract 類的封裝:
public interface ContractUtils {
/*=============一鍵減速契約類===============*/
// 一鍵加速contract
interface ISpeedUpView extends SpeedUpBaseInterface {
void getMemory(long availsize,String total);
}
//presenter 的介面類
interface ISpeedUpPresenter extends SpeedUpBaseInterface{
void startSpeedup();
}
/* ==============輕度清理契約類==================*/
interface IClearView extends BaseView {
void scanComplete(List mInfoList);
}
interface IClearPresenter extends BaseView{
void scanComplete(List mInfoList);
void startclear();
}
.....
} 複製程式碼
ok,在 contractUtils 類中,我把v與p的介面都放在這裡,這樣方便查閱,然後關於他們之間更新資料重複的介面,再封裝一次,其中 SppedUpBaseInterface 如下,繼承自baseview:
public interface SpeedUpBaseInterface extends BaseView {
void scanComplete(List datas);
void updateMemory(String msg);
} 複製程式碼
以後在除錯的時候,只要過來檢視這個共有契約類就方便多了。
4、封裝baseactiviy
這個大家在平時都有做的,我們可以在這裡封裝activity之間的啟動動畫,許可權管理,還有共有方法等等,而 MVP 這個設計模式,當然也是所有的 activity 都支援的,所有,封裝如下,這裡只展示 mvp 部分:
public abstract class BaseActivity,T extends BasePresenter> extends Activity {
public T mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO:OnCreate Method has been created, run FindViewById again to generate code
.....
//這裡初始化 mvp
mPresenter = getPresenter();
}
@Override
protected void onResume() {
super.onResume();
if (mPresenter != null) {
mPresenter.onAttach((V) this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.onDetch();
}
}
/**
* 不同activity的佈局介面
* @return
*/
public abstract int getLayoutId();
public abstract T getPresenter();
} 複製程式碼
在所有的 onresume 中,將 view 例項給 presenter ,在 ondestroy 中,銷燬其中的view,而 baseactivity 後面的泛型,就是根據不同的子類來的。最後用一個抽象方法,getPresenter 把子類的 presenter 拿出來,都在 oncreate 中初始化;
5、子類中的呼叫
子類中的呼叫,這裡展示 mvp 部分,比如一鍵加速這裡:
View:
public class SpeedUpActivity extends BaseActivity
implements ContractUtils.ISpeedUpView {
....
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initdata();
}
@Override
public int getLayoutId() {
return R.layout.activity_speedup;
}
@Override
public SpeedUpPresenter getPresenter() {
mSpeedUpPresenter = new SpeedUpPresenter(this,this);
return mSpeedUpPresenter;
}
@Override
protected void onResume() {
super.onResume();
}
private List mlist = new ArrayList<>();
public void speedup(View view) {
mSpeedUpPresenter.startSpeedup();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void getMemory(long availsize, String total) {
mCircleRunView.setMemeryMsg(availsize," / "+total);
}
@Override
public void updateMemory(String msg) {
if (msg.equals("0")){
Toast.makeText(this, "您的電視非常流暢", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "共為您節省了: "+msg, Toast.LENGTH_SHORT).show();
}
}
@Override
public void fail(String errormsg) {
lg.d("speedupviewerror: "+errormsg);
}
@Override
public void scanStatusShow(int current, int maxsize, long size,String path) {
mLoadingText.setText(getString(R.string.loading,current,maxsize));
}
} 複製程式碼
注意子類整合 baseactivity 的 view 和 presenter,還有抽象方法 getPresenter 即可,基本沒啥區別
至於 presenter 的實現,則大家修改一下自己的就行了,比如我的:
public class SpeedUpPresenter extends BasePresenter implements
ContractUtils.ISpeedUpPresenter{
private ISpeedUpModel mSpeedUpModel;
private ContractUtils.ISpeedUpView mISpeedUpView;
private Context mContext;
public SpeedUpPresenter(ContractUtils.ISpeedUpView iSpeedUpView, Context context) {
mContext = context;
mISpeedUpView = iSpeedUpView;
mSpeedUpModel = new SpeedUpModel(context,this);
mSpeedUpModel.startScanRunInfo();
}
.....
} 複製程式碼
關於model的思考
上面,我們對 view 和 presenter 都進行了封裝,那model是否也該封裝呢?這個不好說,根據自己的專案來,不過一般很多封裝,畢竟這個資料邏輯處理的,都是單獨出來的;
不過,model 我一般把用單例的,而且比如專案中的深度清理,model 的掃描和資料的獲取是在presenter 中去初始化後再去掃描嗎?
No No No,我們說過 p 只是一個觀察者,它只負責你model的資料更新後實時更新給v,那麼你 model 的怎麼做,我就不管了,我只要你的資料就行,想深度掃描這種耗時的,我是在 application裡面初始化的;但記得的一點是,不要在application 裡做過多的操作,不然會拖慢app的啟動的。
封裝後與以前的區別
- v 與 p 的介面,我們都可以用契約類來實現了,除了方便查閱除錯之外,還減少了很多類
- 封裝了 v 與 p 公有的方法,減少累贅程式碼
- 關於model的思考
好,以上就是這是我對 MVP 封裝的初步理解,如有錯誤,也歡迎大家留言指正,謝謝。