MVP中Presenter複用思路和Inject-mvp

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

此外,這裏生成的代碼只是對presenter做了attach和detach的操作,並沒有做IOC的工作,因爲Dagger2已經是很優秀的輪子了。配合Dagger2,可以簡化很多工作。只要你使用註解的類有attach和detach的方法,就可以用這個小工具了。

demo 地址:https://github.com/yueshaojun/inject-mvp.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章