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