MVP中Presenter複用的思考
MVP模式早已經不是什麼新鮮詞了,這裡不再贅述。最近在重構程式碼的過程中,發現了一件及其痛苦的事情:很多時候,model層在應用中是很薄的,大多數的業務邏輯都在Presenter層,但是由於模版 程式碼,Activity(View)->P是一一對應繫結的關係,那這樣,相當於只是把原來在Activity中的邏輯轉移到了Presenter中,雖然一定程度上解耦了,方便單測了,但是事實上Presenter很難複用。先貼一下使用的MVP模版類: BaseMvpActivity.java
/** * MVP模式的Activity基類 */ public abstract class BaseMvpActivity<P extends BasePresenter> extends Activity{ /** * 使用Dagger2 注入presenter */ @Inject protected P presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectMethod(); if (presenter != null) { presenter.attachView(this); } } @Override protected void onDestroy() { super.onDestroy(); if (presenter == null) { return; } presenter.detachView(); } /** * 獲取Presenter物件 */ protected P getPresenter() { return presenter; } /** * 子類利用Dagger注入 */ protected abstract void injectMethod(); }
BaseView.java
/**
* MVP模式的View基類
*/
public interface BaseView {
//繫結lifecircle,防止記憶體洩漏以及空指標
<T> AutoDisposeConverter<T> bindAutoDisposable();
}
BasePresenter.java
public abstract class BasePresenter <V extends BaseView>{ private WeakReference<V> viewRef; public void attachView(V view) { viewRef = new WeakReference<>(view); } public void detachView() { if (viewRef != null) { viewRef.clear(); viewRef = null; } } protected V getView() { return viewRef == null ? null : viewRef.get(); } }
這個模版是MVP使用比較多的一中,可以看到BaseMVPActivity中只有一個presenter,這意味著一個繼承了它的Activity只能有這一個地方處理邏輯。
舉個例子,應用中A頁面有幾個action是檢查許可權、接單,B頁面是請求配置項、檢查許可權、更新個人資訊。這兩個頁面都有“檢查許可權”這個action,那麼程式碼的寫法可能是: APresenter.java
public class APresenter extends BasePresenter<IAView>{ public void checkAuth(){ // 檢查許可權 getView().checkResult(); ... } public void acceptOrder(){ // 接單 getView().acceptOrderSuccess(); ... } }
BPresenter.java
public class BPresenter extends BasePresenter<IBView>{
public void checkAuth(){
// 檢查許可權
getView().checkResult();
...
}
public void requestConfig(){
// 請求配置項
...
}
public void updateMyInfo(){
//更新個人資訊
...
}
}
這兩個Presenter中都有checkAuth()這個方法,但是卻沒辦法抽出來複用,因為我們的模版程式碼中,用泛型指定了Presenter只能對應一個View,一個View反過來也只能對應一個Presenter。
這樣的程式碼並不靈活,也很冗餘。當幾個頁面公共邏輯比較多的時候,只能copy程式碼。
MVP模版做了什麼?
要解決presenter不能複用的問題,首先要知道為什麼Presenter被綁死了。BaseMVPActivity要做的事情,是提供一個presenter成員,並管理presenter和activity的繫結關係,但是presenter成員並不是它提供的,真正例項這個presenter的是它的子類,所以***BaseMVPActivity只有指定泛型來告訴子類應該例項什麼型別的Presenter***。如果想要多個Presenter同時用這種方法例項,BaseMVPActivity是不支援的,因為它只能指定一個泛型。
注意加粗和斜體的字,既然由於BaseMVPActivity是因為要做這麼一件事,導致Presenter和Activity綁死,那麼不用泛型,由實際的Activity自己去宣告、例項化和管理presenter是不是就可以了?
當然可以了!因為這就是MVP最初最乾淨的模型(虛擬碼):
public class AActivity extends Activity{
PresenterA presenterA = new PresenterAImpl();
PresenterCheckAuth presenterCheckAuth = new PresenterCheckAuthImp();
....
onCreate(){
presenterA.attatchView(this);
presenterCheckAuth.attatchView(this);
}
onDestroy(){
presenterA.dettachView();
presenterCheckAuth.dettachView();
}
}
這樣當然沒問題,但是每次要使用MVP,都要宣告、例項化、管理繫結關係,這些活兒太繁瑣了,這也是為啥要有模版程式碼BaseMVPActivity這個東西。
這時候好像繞了一圈繞回去了。
解決思路
毫無疑問,要有個東西可以替代BaseMVPActivity的管理作用,但是它不應該將presenter和activity綁死。有沒有辦法,把上面onCreate和onDestroy裡面的東西代替了? 想到用編譯時註解,生成如下的程式碼:
public class AActivity_BinderWrapper implements BinderWrapper<AActivity> {
@Override
public void bindMember(final AActivity target) {
target.presenterA.attachView(target);
target.presenterCheckAuth.attachView(target);
}
@Override
public void unbind(final AActivity target) {
target.presenterA.detachView();
target.presenterCheckAuth.detachView();
}
}
然後在onCreate和onDestory處呼叫AActivity_BinderWrapper的bindMember(final AActivity target) 和 unbind(final AActivity target) 。 有註解框架生成程式碼,自然比自己手動寫要省事了。可是還是有問題啊,還是需要手動在onCreate和onDestory處呼叫bindMember(final AActivity target) 和 unbind(final AActivity target)啊,無非是以前可能要attach 和 detach很多次,現在只要一次而已,但是仍然需要手動干預。
進一步演進
AActivity_BinderWrapper 的bindMember(final AActivity target) 和unbind(final AActivity target)如果放到基類中,這個事情不就解決了?但是麻煩的是,在基類中的bindMember和unBind方法,並不知道當前自己的例項到底是誰。比如把它們放在了一個叫BaseNewMVPActivity的類裡,有AActivity和BActivity繼承了它,只有執行的時候,才能知道,要bindMember或者unBind的類是AActivity還是BActivity。編譯期沒辦法簡單的用bindMember(this) 和unbind(this)達到效果。
怎麼辦呢?既然執行時才知道BaseNewMVPActivity的例項是誰,那我執行時再bindMember和unBind唄?那編譯期要做什麼呢?那就是確定執行時要用哪個BinderWrapper。依然利用編譯時註解,生成如下程式碼:
public final class ActivityBinder implements AndroidBinder<Activity> {
private HashMap<Class<? extends Activity>, BinderWrapper<? extends Activity>> classBinderWrapperHashMap;
private BinderWrapper<AActivity> bindWrapper_AActivity;
private ActivityBinder() {
classBinderWrapperHashMap = new HashMap<>();
bindWrapper_AActivity = new AActivity_BinderWrapper();
classBinderWrapperHashMap.put(AActivity.class,bindWrapper_AActivity);
}
public static ActivityBinder create() {
return new ActivityBinder();
}
@Override
public void bind(Activity instance) {
BinderWrapper<Activity> wrapper = (BinderWrapper<Activity>) classBinderWrapperHashMap.get(key);
if(wrapper != null){
wrapper.bindMember(instance);
}
}}
@Override
public void unbind(Activity instance) {
BinderWrapper<Activity> wrapper = (BinderWrapper<Activity>) classBinderWrapperHashMap.get(key);
if(wrapper != null){
wrapper.unbind(instance);
}
}}
}
這裡用一個HashMap儲存對應關係,用Class作為key,對應的BindWrapper作為value,這是在編譯期生成的,執行時,拿到當前Activity的class,從HashMap中拿出對應的BindWrapper,進行繫結和解綁。
到這裡,在基類Activity中管理Presenter的繫結就解決了。
more
上面這些僅僅是解決了問題,但是如果每個Activity都去持有一個ActivityBinder,顯然是不合適的,因為這樣會持有很多和它無關的BindWrapper,導致記憶體的浪費。更優雅的,將ActivityBinder讓Application持有,成為全域性的,然後activity.getApplication()拿到BindWrapper,在對應的onCreate和onDestroy中使用。