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

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 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 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));

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

扫码获取!!(备注:Android)**

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

[外链图片转存中…(img-VQJwPa9a-1712861230253)]

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值