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的方法,就可以用這個小工具了。