MVP架构中Presenter处理业务逻辑后将数据传递给View,通过View将数据展示出来,而此时View可能已经销毁了,结果程序崩溃了。那Presenter如何才能安全地调用View对象的方法呢?
这种现象多发生在异常调用的场景,View已经销毁,而异步调用还未结束;当异步调用结束回调View时,View已经销毁。解决思路当然就是异常返回时不再调用View方法,而方案可能有多种,如
- 使用RxJava,在异步调用时存储Disposable,页面结束后调用dispose()取消
- 使用OkHttp,在异步调用时存储Call对象,页面结束后调用call.cancel()方法取消
- 异步回调时判断View是否已经销毁,再调用View的方法
以上两个方案都可以解决此问题的,但使用起用都比较麻烦。前两种方案需要保存异常请求的对象,而后一种每个异步请求的处理都要添加判断语句。那有没有方案可以即不存储异步请求对象,也不写判断语句的方案,而又不会引用Crash的方案呢?当然是有的,方案就是使用View的代理。
通过View的代理来判断View是否已经销毁,如果未销毁则正常调用View的方法;当View已经销毁时,直接忽略调用。那代理View如何来实现呢?是否创建创建一个代理类并实现View的接口,在代理类时添加View是否销毁的判断呢?当然不是,这般操作只会让程序更复杂,这里可以利用Java语言特性,动态代理来实现,根据View接口生成一个View的动态代理类,实现是不是特别简单,即
Proxy.newInstance(view.getClassLoader(), new Class[IDetailView.class], new InvocationHandler(){});
但是每个Presenter都这样写的话,也是一件重复的工作,需要将这段逻辑下沉到BasePresenter层,这样Presenter的子类就不用处理代理逻辑了。这里有个难点就是如何获取View的接口类型,基类通过泛型定义的View,但并不清楚子类是如何定义的,如
class BasePresenter<T extends IView>{
private T mView;
}
View类可能是这样定义的,它实现了多个接口,而我们需要拿到的是IDetailView接口,用它来生成View的代理对象
//IDetailView的实现类
class DetailActivity extends BaseView<IDetailPresenter> implements IDetailView,OnClickListener{
}
//View的接口类定义,继承了IView接口
interface IDetailView extends IView{
}
要想拿到IDetailView接口的类型,就需要解析泛型了。这里最关键的一步是通过getGenericInterfaces()方法获取对象实现的接口列表,将View接口解析出来后, 再使用Proxy生成一个代理对象,在动态代理的方法调用处进行判断是否断续调用View相关的方法。
//获取View的代理对象
private T getViewProxy(Object view){
Class clazz = (Class)getViewType(view);
Object proxy = Proxy.newProxyInstance(view.getClass().getClassLoader(), new Class[]{clazz}, new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用的可能是Object方法,此时并不需要进行代理,直接调用即可
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
Object result = null;
//View没有销毁就进行调用
if (view.isAlive())
result = method.invoke(view, args);
if (result == null)
;//忽略调用后需要返回一个正确类型的返回值
return result;
}
return (T) proxy;
}
}
//获取View的接口类型
private Type getViewType(Object view){
Class clazz = view.getClass();
while (clazz != null){
//获取类型实现的接口列表
Type[] types = clazz.getGenericInterfaces();
if (types != null && types.length > 0){
for(Type type : types){
if (type instanceof ParameterizedType){
type = ((ParameterizedType) type).getRawType();
}
if (!(type instanceof Class)) {
continue;
}
//判断是否是IView的子类,以此在断定是定义的View接口
if (IView.class.isAssignableFrom((Class<?>) type)) {
return type;
}
}
}
//获取View的父类。可能父类实现了接口,当前类只是继承了一下而已
clazz = clazz.getSuperclass();
}
}