MVPOptimize MVP模式优化
主要优化P层V层互相持有对象,不能及时回收/销毁问题
如果你看过我的MVP整合教程【android进阶篇】MVP+Retrofit+RxJava框架结合 你可能就会发现,如果页面在请求的时候,网络不好,这时用户跳转到其他页面,就可能会抛出空指针异常/空对象/内存泄露等问题(网上大部分mvp入门教程也存在相同的问题);
尊重原创,转载请注明出处,原文地址: http://blog.csdn.net/qq137722697
内存泄露追踪
手动调用finish();方法销毁当前页面
如果你的业务中在进入下一个页面时,会把当前页面销毁,此时你会调用finish();方法,但是其实是有问题,来看看这张MVP结构图:
M 层与 P 层是互相持有对象的关系;通过代码也可以看出来:
View层持有Presenter层:
public class LoginActivity extends BaseMvpActivity implements LoginContact.ILoginView {
...
private LoginContact.ILoginPresenter presenter;
...
}
Presenter层持有View层对象:
public class LoginPresenterIml implements LoginContact.ILoginPresenter {
...
private LoginContact.ILoginView loginView;
...
}
为什么要表明他们互相持有呢?
因为即使页面finish之后,如果Presenter层还持有View层的引用,jvm不会马上回收,也就是说finish之后页面没有真正意义上的销毁;
那么问题就来了,既然没有销毁,如果这样没有销毁的页面太多,就会造成内存泄露;
解决建议
既然finish();之后不会真正销毁是因为它们互相持有对象,那就把这种关系打破即可,怎么做呢?
1、在页面(activity/fragment)回调onDestroy()方法的时候,通知presenter断开持有View层的引用;并将presenter对象赋值为null,调用System.gc();通知jvm;
2、presenter在收到通知销毁页面的时候,将view赋值为null,并且调用System.gc();通知jvm
- [推荐] 在presenter收到通知销毁页面的时候,有条件的话可以增加一个取消正在执行任务的方法(此方法非必须,继续往下看);
特别说明:
将对象赋值为null,会断开对象的引用;
将presenter赋值为null,会断开View持有Presenter对象的引用
同样的将view赋值为null,会断开presenter持有view对象的引用
调用System.gc()通知jvm可以回收垃圾了,jvm并不会马上回收,待jvm”心情好”的时候会自动回收;
来看看代码实现:(后面有完整代码)
1、view的onDestroy方法通知销毁页面,并设置presenter为null
@Override
public void onDestroy() {
super.onDestroy();
if (presenter != null) {
presenter.onDestroy();
presenter = null;
System.gc();
}
}
2、presenter解除持有view对象
public class LoginPresenterIml implements LoginContact.ILoginPresenter {
...
/**
* 当页面销毁的时候,需要把View=null,
* 然后调用 System.gc();//尽管不会马上回收,只是通知jvm可以回收了,等jvm高兴就会回收
*/
@Override
public void onDestroy() {
loginView = null;
System.gc();
}
}
以上操作是重复代码,考虑抽取,继续往下看
空指针/空对象问题
场景一
通过以上两个操作,jvm会适时回收view层,但是如果presenter层还在继续做耗时操作的话不会马上被回收,此时如果view已经被回收,耗时操作刚好完成要通知view层做更新ui的操作,那么就会出现空指针/空对象的异常;
比如:获取网络数据(耗时操作)成功后,需要展示到ui上,此时会调用view.showData(result)方法,view对象为空,就抛出异常了;
场景二
还有一种情况也会出现空指针/空对象的问题,就是jvm在内存吃紧的时候会回收不可见的view;你可能会说上面不是在页面(activity/fragment)的onDestory()方法调用的时候通知销毁对象嘛!
其实,jvm在回收页面的时候不会保证回调onDestroy方法的,所以就不能及时通知presenter销毁了。
解决建议
这个问题很好解决:presenter中,在所有需要使用view对象之前加一个非空判断,如果为空直接return;不在做任何操作
例子:
public class LoginPresenterIml implements LoginContact.ILoginPresenter {
...
/**
* 开始登录
*
* @param username 用户名
* @param pwd 密码
*/
@Override
public void startLogin(String username, String pwd) {
loginModel.login(username, pwd, new BaseCallbackListener<LoginResult>() {
@Override
public void onStart() {
//拿到结果之后判断v层是否已经销毁,防止空对象
if (loginView == null) {
Log.e("hdltag", "onStart(LoginPresenterIml.java:37):页面已经销毁,不在进行任何操作");
return;
}
loginView.showLoading();
}
@Override
public void onNext(LoginResult result) {
//拿到结果之后判断v层是否已经销毁,防止空对象
if (loginView == null) {
Log.e("hdltag", "onStart(LoginPresenterIml.java:47):页面已经销毁,不在进行任何操作");
return;
}
loginView.closeLoading();
switch (result.getCode()) {
case "0":