1. 程式人生 > >MVP中Presenter複用的思考

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中使用。