一种全方案解决内存泄漏的MVP架构

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版,欢迎购买。点击进入详情

关于MVP

关于MVP的介绍,我们可以参考之前的文章:
Android App开发架构之:MVP

我们再来重温一下MVP架构的特点:

  1. V层指的是Activity、Fragment、自定义的View,或者某一个细分了的功能module;
    V层职责是专门用来处理UI更新;
    注:功能module会带有view,所以也能够处理UI更新;
  2. P层用来处理业务逻辑;
    比如调用方法前的检测、调用方法前的数据整理,获取数据后的数据处理等;
  3. M层用来获取数据;
    比如网络请求的数据获取、本地数据库的数据获取、通过计算获取数据等;

解决方案

那么我们该如何打造一款属于自己的MVP框架呢?

常规方法

按照MVP层级的划分,那么我们需要构建每一个层级,也就是每一个层级需要有一个实例。
以Activity为例,Activity实例系统已经为我们创建好,那我们就需要创建P层实例和M层实例;

根据MVP层级关系,M层只跟P层对接,那么可以在M层中创建P层实例;
P层只跟M层对接,那么可以在P层中创建M层实例;

各层之间通过各层interface接口定义的函数进行相互调用。

以V层和P层为例:

public class Activity extends AppCompatActivity implements ContractView{
private Presenter mPresenter;
@Override
    public void onCreate(Bundle savedInstanceState) {
		......
		mPresenter = new Presenter();
		......
		mPresenter.attachView(this);
		......
		mPresenter.sendMsg();
	}
	@Override
    public void updateMsg(Boolean bool) {
		......
	}
}

V层创建了P层的实例,并且通过P层实现的接口attachView将自己注入P层,这样P层可以在获取数据后,调用V层的接口updateMsg,将数据返还给V层。

这就是MVP实现的全过程。

优化之泛型

通过上面的方法,我们实现了一个简单的MVP架构。
虽然可以用,但是这个架构不是并不完美,还是存在可以优化的地方。

我们知道,MVP对应每一个业务,比如登录有一个MVP,用户信息处理有一个MVP,一个项目下来可能存在几十处这样的MVP,而且我们看到,P层和M层都需要手工创建,那么我们想,P层和M是否可以自动生成实例,而不需要手工new呢?

我们想到了泛型。

public class Test<T> {
	......
}

T是一个类型参数,我们可以根据已知的类型参数,再通过newInstance获取这个类型参数的实例。
那么我们把P层和M层类型作为泛型的类型参数带入,那是不是就可以创建出P层和M层的实例了呢?

public class TestActivity<P, M> {
	......
}

P和M的实例,我们可以通过函数来自动创建,这样我们就实现了通过泛型<P, M>的声明,能够自动创建其对应的实例,无需手工new实例。

进一步,我们可以考虑将这些每一个具体Activity都涉及到的操作抽取出来,放入一个抽象类BaseActivity中,由BaseActivity处理创建P层和M层实例的工作。

public abstract class BaseActivity<P, M> {
	......
}

具体的业务Activity只要继承它既可:

public class TestActivity extends BaseActivity<P, M> {
	......
}

优化之减少接口

MVP架构每个层之间通过接口交互,这样就会导致接口过多,代码复杂度也随之提升,并且带来了阅读和调试的不便。

为了解决这样的不便,我们考虑减少接口的数量。
总体来看,接口是显示层和数据层之间的桥梁,P层和M层总体上都是为V层提供数据的。所以我们决定做以下条件的优化:

  1. 为了减少接口的泛滥,只在V层和P层之间定义接口;
  2. P层和M层之间只存在P层对M层的单向调用;
  3. M层专门用来获取数据,包括异步操作和耗时操作;
  4. M层返回Observable类型给P层;

举例来看,P层对M层的调用:

public class DisposablePresenter extends DisposableContract.Presenter {
    @Override
    void sendMsg() {
        addSubscribe(
                getModel().executeMSg()
                        .compose(RxUtil.applySchedulers())
                        .subscribe(bool -> getView().updateMsg(bool))
        );
    }
}

M层的处理:

public class DisposableModel implements DisposableContract.Model {

    @Override
    public Observable<Boolean> executeMSg() {
        return Observable.create(emitter -> {
                    try {
                        Thread.sleep(2000); // 假设此处是耗时操作
                    } catch (Exception e) {
                        e.printStackTrace();
                        emitter.onError(new RuntimeException());
                    }
                    emitter.onNext(true);
                }
        );
    }
}

优化之生命周期

通过泛型的优化,我们看到了一个使用更加便捷的MVP。
但是是不是可以放心使用高枕无忧呢?答案并不是这样的。

我们知道MVP的特点是各层分而治之,各层之间通过接口连接。

有个明显的问题是,以Activity这个V层为例,Activity是存在生命周期的,也就是Activity可能已经被系统回收掉了,但是P层仍然持有V层的引用,这样就导致了内存泄漏

如果解决这个内存泄漏的问题呢?
我们通过以下4种解决方案来一一解决。

1. 弱引用

内存泄漏是因为P层对V层的强引用导致的。那么我们就可以通过P层对V层的弱引用来解决。

private Reference<V> mRefView;

	@Override
    public void attachView(V view) {
        mRefView = new WeakReference<>(view);
    }

    @Override
    public void detachView() {
        if (mRefView != null) {
            mRefView.clear();
            mRefView = null;
        }
    }

可以看到,在将V层通过attachView注入到P层时,使用WeakReference实现P层对V层的弱引用。
并且在页面销毁的时候通过detachView主动解除弱引用。

但是这里有一个问题,就是如果V层被系统回收了,P层也解除了对V层的引用。也不存在内存泄漏问题。但是,P层处理完后又开始调用V层,那么这个时候就会遇到V层为null,如果没有处理这个null,app就会崩溃。这也是我们不愿意见到的。

那是否在每一个使用到V层的地方都加一个null判断呢?理论上是这样,但是这样会增加很多代码量,而且代码阅读起来也不美观。
那么我们的解决方案是,在调用V的地方抛出一个异常,然后在自定义的CrashHandler里面对这个专门的异常不进行处理,这也app就不会有感知。

protected V getView() {
        if (!isViewAttached()) {
            throw new IllegalStateException("mvp's view is not attached, please check again!");
        }
        return mRefView.get();
    }

扔出IllegalStateException异常。

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private Context mContext;
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    private static CrashHandler INSTANCE = new CrashHandler();

    public static CrashHandler getInstance() {
        return INSTANCE;
    }

    public void init(Context context) {
        mContext = context.getApplicationContext();
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 获取系统默认的UncaughtException处理器
        Thread.setDefaultUncaughtExceptionHandler(this);// 设置该CrashHandler为程序的默认处理器
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        if (mDefaultHandler != null) {
            if (Constant.EXCEPTION_MVP_VIEW_NOT_ATTACHED.equals(e.getMessage())) {// for MVP when presenter calls view but view has already destroyed
                return;
            }
            mDefaultHandler.uncaughtException(t, e);// 退出程序
        }
    }
}

自定义CrashHandler中,遇到这个异常直接return;

2. RxJava之Disposable

每一个RxJava调用返回disposable,然后通过CompositeDisposable对disposable进行管理。
通过CompositeDisposable添加disposable类,并且通过CompositeDisposable切断所有订阅事件,解除上下游之间的引用关切,并且切断上下游之间的数据传递。

在BasePresenter里面,我们可以添加如下功能:

private CompositeDisposable mCompositeDisposable;

    public void addSubscribe(Disposable disposable){
        if(mCompositeDisposable == null){
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(disposable);
    }

    private void unSubscribe() {
        if(mCompositeDisposable != null){
            mCompositeDisposable.dispose();//dispose防止下游(订阅者)收到观察者发送的消息
        }
    }

在业务的Presenter里面,使用RxJava的时候,外层加上addSubscribe( … );
在detachView函数中,加上unSubscribe()函数,这样在页面销毁的时候会切断上下游:

@Override
    public void detachView() {
        if (mRefView != null) {
            mRefView.clear();
            mRefView = null;
        }
        unSubscribe();
    }

通过disposable方式处理内存泄漏有个不是特别方面的地方就是,虽然unSubscribe()会自动执行,但是每次使用RxJava的时候,必须加上addSubscribe(),代码上面会感觉不美观,不符合RxJava链式调用的特点。

下面我们要提供的两种解决方案继承了RxJava链式调用的特点。

3. RxJava之RxLifecycle

引用:

api ‘com.trello.rxlifecycle2:rxlifecycle-components:2.1.0’

Activity和Fragment需要做一些调整:

  1. 原本的Activity需要继承RxAppCompatActivity或RxActivity或RxFragmentActivity类;
  2. 原本的Fragment需要继承RxFragment或RxDialogFragment、RxPreferenceFragment、RxAppCompatDialogFragment类;

使用的时候通过compose

getModel().executeMSg()
                .compose(RxUtil.applySchedulers())
                .compose(getView().bindToLife())
                .subscribe(bool -> getView().updateMsg(bool));

以RxAppCompatActivity为例,这里的bindToLife是调用了RxAppCompatActivity提供的bindToLifecycle()方法。

RxLifecycle虽然保留了RxJava链式调用的风格,但是需要对Activity或这个Fragment的基类进行替换,仍然会有一些代码的改造。

4. RxJava之AutoDispose(推荐)

可以看到,上面两种方案都有不足的地方。那么有没有一种方案直接在RxJava的链式调用中加一个操作符就能解决问题的呢?有的,这就是AutoDispose。

引用:

api ‘com.uber.autodispose:autodispose-android-archcomponents:1.0.0-RC2’

void sendMsg() {
        getModel().executeMSg()
                .compose(RxUtil.applySchedulers())
                .as(AutoDispose.<Boolean>autoDisposable(AndroidLifecycleScopeProvider.from((LifecycleOwner) getView(), Lifecycle.Event.ON_DESTROY)))
                .subscribe(bool -> getView().updateMsg(bool));
    }

可以看到,我们只需要增加

.as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from((LifecycleOwner)getView(), Lifecycle.Event.ON_DESTROY)))

这样的语句就可以解决内存泄漏问题。Lifecycle.Event.ON_DESTROY表示在Activity处理onDestroy时阻断上下游关系。

我们看到LifecycleOwner这个接口,AutoDispose只要求Activity或者Fragment实现了这个接口即可,而SupportActivity和Fragment均已实现。

github地址

https://github.com/ddnosh/android-tiny-mvp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值