Rxjava解决内存泄露技术演进

        从Rxjava开始在Android中使用时,内存泄露的问题一直都存在,在我过往的几个项目中,都用了不同的方式去解决相关内存泄露的问题,下面我自己总结关于Rxjava内存泄露的相关解决思路。


知识点汇总:

一:Rxjava什么场景会导致内存泄露 

二:Rxjava解决内存泄露的实现方案 

2.1、Disposable解决内存泄露(单个逐一解绑) 

2.2、CompositeDisposable解决内存泄露(统一解绑) 

2.3、RxLifecycle解决内存泄露 

2.4、autoDisposable解决内存泄露 

2.5、Rxlife解决内存泄露 

 三:扩展阅读


一:Rxjava什么场景会导致内存泄露原因

      使用RxJava发布一个订阅后,当页面被finish,此时订阅逻辑还未完成,如果没有及时取消订阅,就会导致Activity/Fragment无法被回收,从而引发内存泄漏。    

考虑项目中实际使用场景:

1、Activity中执行异步任务 

2、Presenter或ViewModel中执行异步任务 

3、自定义控件中执行异步任务    

4、Manager相关类中执行异步任务   

5、自定义Adapter中执行异步任务


二:Rxjava解决内存泄露的实现方案

2.1、Disposable解决内存泄露 

接入步骤:

1、每次异步任务都执行dispose()函数进行解除绑定。

        代码实例:

        Disposable disposable = Observable

        .interval(0, 1, TimeUnit.SECONDS)  //开启一个定时器

        .subscribe(aLong -> {

        });

//Activity/Fragment销毁时,中断RxJava管道

        if (disposable != null && !disposable.isDisposed()) {

        disposable.dispose();

        }

扩展场景:Presenter或ViewModel中执行异步任务,自定义控件,Manager相关类,自定义Adapter中执行异步任务应该如何防止内存泄露?


2.2、CompositeDisposable解决内存泄露(统一解绑)

接入步骤:

1、在BaseActivity中实现代码

private CompositeDisposable mCompositeDisposable = new CompositeDisposable();

public void addDisposable(Disposable disposable) {

        mCompositeDisposable.add(disposable);

        }

@Override

protected void onDestroy() {

        super.onDestroy();

        mCompositeDisposable.dispose();

        }

        2、在每次Rxjava异步任务时,把相关的disposable对象传入,onDestroy中统一解绑。

        实例代码:

        addDisposable(Observable.fromCallable(() -> UserManager.getInstance().getUid())

        .subscribeOn(Schedulers.io())

        .subscribe(uid -> UserManager.getInstance().updateUserInfo(uid), throwable -> Logger.d(TAG, throwable)));

        在Activity的onDestory()生命周期时,自动解除订阅,以防止因生命周期组件的生命周期而导致的RxJava内存泄漏事件。


2.3、RxLifecycle解决内存泄露

        项目地址:https://github.com/trello/RxLifecycle(7.5k start)

        RxLifecycle的原理:

        1、在Activity中,定义一个Observable(Subject),在不同的生命周期发射不同的事件。

        2、通过compose操作符(内部实际上还是依赖takeUntil操作符),定义了上游数据,当其接收到Subject的特定事件时,取消订阅。

        3、Subject的特定事件并非是ActivityEvent,而是简单的boolean,它已经内部通过combineLast操作符进行了对应的转化。

接入步骤:

        1、继承RxAppCompatActivity和RxFragment(不想继承,也可一直拿出源码使用)

        2、在Rxjava链式调用时执行compose(this.<Long>bindToLifecycle())或

        compose(this.<Long>bindUntilEvent(ActivityEvent.DESTROY))

        bindToLifecycle()函数解析:

        解析:

        1、使用compose(this.<Long>bindToLifecycle())方法绑定Activity的生命周期,在onStart方法中绑定,在onStop方法被调用后就会解除绑定,以此类推。

        2、有一种特殊情况,如果在onPause/onStop方法中绑定,那么就会在它的下一个生命周期方法(onStop/onDestory)被调用后解除绑定。

        bindUntilEvent(ActivityEvent.DESTROY)函数解析:

        解析:在自己执行的生命周期中解除绑定。

代码实例:

        Observable.interval(1, TimeUnit.SECONDS)

        .doOnDispose(new Action() {

@Override

public void run() throws Exception {

        Log.i(TAG, "Unsubscribing subscription from onCreate()");

        }

        })

        .subscribeOn(Schedulers.io())

        .observeOn(AndroidSchedulers.mainThread())

        .compose(this.<Long>bindUntilEvent(ActivityEvent.DESTROY))  //或bindToLifecycle()函数

        .subscribe(new Consumer<Long>() {

@Override

public void accept(Long num) throws Exception {

        Log.i(TAG, "Started in onCreate(), running until onPause(): " + num);

        }

        });

扩展场景:Presenter或ViewModel中执行异步任务,自定义控件,Manager相关类,自定义Adapter中执行异步任务应该如何防止内存泄露?

RxLifecycle的局限性:

        一:需要继承父类(RxActivity或RxAppCompatActivity / RxFragment等)

        对于设计来讲,组合的灵活度大多数情况都优于继承,而RxLifecycle在父类中声明了一个PublishSubject,用来发射生命周期事件,这是导致其局限性的原因之一。

        二:如何处理绑定生命周期?

        自定义Adapter中执行异步任务:

        RecyclerView的Adapter中订阅了Observable,这意味着,想要进行Observable的生命周期绑定,在RecyclerView的Adapter中,我必须要通过将Activity作为依赖,注入到Adapter中:

        new ListAdapter(RxActivity activity);

        Presenter或ViewModel中执行异步任务:

        在MVP的架构或者MVVM架构中,我的Presenter或者我的ViewModel无法直接获取RxActivity的引用(作为View层,更应该抽象为一个接口与Presenter进行交互)。

        而对于Presenter,我需要对View抽象接口进行instanceof 的判断:

        if (view instanceof RxActivity) {

        .... 省略部分代码

        .compose(((RxActivity) mView).bindToLifecycle())

        ...

        }

        或者

        Activity的对象传到Presenter中,然后执行.compose(mActivity.bindToLifecycle());


2.4、autoDisposable解决内存泄露

项目地址:https://github.com/uber/AutoDispose(3k)

原理:AutoDispose获取了Activity(LifecycleOwner)对象,并定义了一个新的Observable,在Activity的不同生命周期中,发射对应的事件,和RxLifecycle很类似的是,AutoDispose在被订阅时,获取到Activity当前的生命周期,并找到对应需要结束订阅的生命周期事件。

        也就是说,在我们的ObservableA订阅时,就已经知道了自己在Activity的哪个生命周期让AutoDispose内部自定义的ObservableB自动发射事件,ObservableA监听到这个事件时且未dispose,解除订阅避免内存泄漏。

接入步骤:

1、直接在Rxjava链式调用时执行to(autoDisposable(AndroidLifecycleScopeProvider.from(this)))

this参数解析:

    这个this直观的讲,就是Activity本身,当然它也可以是Fragment,这个参数对象只有一个要求,就是必须实现LifecycleOwner接口。

   LifecycleOwner接口是Google Android官方架构组件:Lifecycle的一个重要组件,在v7包中,FragmentActivity和Fragment都实现了这个接口,实现了这个接口的对象都拥有生命周期(Lifecycle)。

   这意味着,不仅是AppCompatActiviy(FragmentActivity的子类)和Fragment,只要是实现了LifecycleOwner的类,都可以作为参数传给AutoDispose,用以控制Observable和组件生命周期的绑定。

实际使用中建议:

一:封装Util类

首先封装Util类,将职责赋予RxLifecycleUtils:

public class RxLifecycleUtils {

    private RxLifecycleUtils() {

        throw new IllegalStateException("Can't instance the RxLifecycleUtils");

    }

    public static <T> AutoDisposeConverter<T> bindLifecycle(LifecycleOwner lifecycleOwner) {

        return AutoDispose.autoDisposable(

                AndroidLifecycleScopeProvider.from(lifecycleOwner)

        );

    }

}

二:面向LifecycleOwner接口

        现在,只要持有LifecycleOwner对象,Observable都可以通过RxLifecycleUtils.bindLifecycle(LifecycleOwner)进行绑定。

        比如我们在BaseActivity/BaseFragment中添加代码:

public abstract class BaseActivity extends AppCompatActivity implements IActivity {

    //...忽略其他细节

    protected <T> AutoDisposeConverter<T> bindLifecycle() {

        return RxLifecycleUtils.bindLifecycle(this);

    }

}

在Activity中使用实例:

        Observable.interval(1, TimeUnit.SECONDS)

        .doOnDispose(new Action() {

@Override

public void run() throws Exception {

        Log.i(TAG, "Unsubscribing subscription from onCreate()");

        }

        })

        .subscribeOn(Schedulers.io())

        .observeOn(AndroidSchedulers.mainThread())

        .to(bindLifecycle())

        .subscribe(new Consumer<Long>() {

@Override

public void accept(Long num) throws Exception {

        Log.i(TAG, "Started in onCreate(), running until onPause(): " + num);

        }

        });

三:使用Google官方Lifecycle组件

首先让我们的IPresenter接口实现LifecycleObserver接口:

public interface IPresenter extends LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)

    void onCreate(@NotNull LifecycleOwner owner);

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)

    void onDestroy(@NotNull LifecycleOwner owner);

    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)

    void onLifecycleChanged(@NotNull LifecycleOwner owner, @NotNull Lifecycle.Event event);

}

然后在BasePresenter中管理LifecycleOwner:

public class BasePresenter<V extends IView, M extends IModel> implements IPresenter {

    @Getter

    protected V mRootView;

    @Getter

    protected M mModel;

    private LifecycleOwner lifecycleOwner;

    public BasePresenter(V rootView, M model) {

        this.mRootView = rootView;

        this.mModel = model;

    }

    protected <T> AutoDisposeConverter<T> bindLifecycle() {

        if (null == lifecycleOwner)

            throw new NullPointerException("lifecycleOwner == null");

        return RxLifecycleUtils.bindLifecycle(lifecycleOwner);

        public void onCreate(@NotNull LifecycleOwner owner) {

            this.lifecycleOwner = owner;

        }

        public void onDestroy(@NotNull LifecycleOwner owner) {

            if (mModel != null) {

                mModel.onDestroy();

                this.mModel = null;

            }

            this.mRootView = null;

        }

    }

在BaseActivity中相关代码:

public abstract class BaseActivity<P extends IPresenter> extends AppCompatActivity implements IActivity {

    @Inject

    protected P presenter;

    @Override

    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(getLayoutId());

        //...忽略其他代码,比如ButterKnife、Dagger2等

        getLifecycle().addObserver(presenter);

    }

    protected <T> AutoDisposeConverter<T> bindLifecycle() {

        return RxLifecycleUtils.bindLifecycle(this);

    }

}


2.5、Rxlife解决内存泄露

项目地址:https://github.com/liujingxing/rxjava-RxLife(0.25k)

   先来介绍下RxLife,相较于trello/RxLifecycle、uber/AutoDispose,具有如下优势:

1、直接支持在主线程回调

2、支持在子线程订阅观察者

3、简单易用,学习成本低

4、性能更优,在实现上更加简单

接入步骤:

1、在Activity/Fragment/View中,无需做任何准备工作就可以直接使用RxLife.as(this)。

2、在ViewModel及任意类,需要分别继承ScopeViewModel或BaseScope类才可以使用RxLife.as(this)。

原理:看到RxLife.as(this)这行代码的身影,那这个as方法接收的是什么类型的参数呢?我们看看源码:

看几个方法:

1、as(LifecycleOwnerowner owner) 方法,接收的是一个LifecycleOwner接口对象,简单介绍下这个接口,这个接口对象能使我们自定义的类感知Activity/Fragment的生命周期回调。我们常见的Activity/Fragment就实现了这个接口,所以我们就能够在Activity/Fragment中调用此as方法

2、as(View view) 这个方法就很直观了,直接接收一个View对象,我们在View上调用的就是这个方法。

3、as(Scope scope) 方法接收一个Scope接口对象,后面会对这个接口介绍,这里可以告诉大家的是,在上面的ViewModel及任意类中继承的ScopeViewModel、BaseScope类都实现了Scope接口,所以我们在ViewModel及任意类中调用的就是这个as方法。

一:在Activity/Fragment上如何使用

//在Activity/Fragment上

        Observable.interval(1, 1, TimeUnit.SECONDS)

        .as(RxLife.as(this)) //这里this 为LifecycleOwner接口对象

        .subscribe(aLong -> {

        Log.e("LJX", "onNext aLong=" + aLong);

        });

        我们只需要将RxLife.as(this)传入RxJava的as操作符即可。此时当Activity/Fragment销毁,就会自动关闭RxJava管道,避免内存泄漏。

        二:View中使用

        接着来看看在View上如何使用,如下:

        Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒发送一条消息

        .as(RxLife.as(this)) //这里this 为View对象

        .subscribe(aLong -> {

        Log.e("LJX", "onNext aLong=" + aLong);

        });

    是的,代码一模一样,但是在这我们传入的this是一个View对象。此时当View从窗口中移除时(执行了onDetachedFromWindow方法),就会自动关闭RxJava管道,避免内存泄漏。

三:ViewModel中使用

    ViewModel是Google Jetpack里面的组件之一,由于它能自动感知Activity/Fragmeng的销毁,所以RxLife单独为它做了适配。在ViewModel中使用RxLife,需要继承RxLife的 ScopeViewModel 类,然后就可以跟上面一样,优雅的使用RxLife.as(this),如下:

public class MyViewModel extends ScopeViewModel {

    public MyViewModel() {

        Observable.interval(1, 1, TimeUnit.SECONDS)

                .as(RxLife.as(this)) //这里的this 为Scope接口对象

                .subscribe(aLong -> {

                    Log.e("LJX", "onNext aLong=" + aLong);

                });

    }

}

       此时当Activity/Fragmeng销毁时,就会自动关闭RxJava管道,避免内存泄漏。

               注意:要想ViewModel对象感知Activity/Fragment销毁事件,就不能使用new 关键字创建对象,必须要通过ViewModelProviders类获取ViewModel对象。

               //在Activity/Fragment上

               MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class)

四:任意类

   相信大家对MVP都非常的熟悉了,在P层,我们一般都有发送Http请求的需求,

   此时,我们也希望,在Activity/Fragment销毁时,能自动将Http关闭,所以RxLife对任意类做了点适配工作。在任意类中,我们需要继承RxLife的BaseScope类,然后就优雅的使用RxLife.as(this)了。

public class Presenter extends BaseScope {

    public Presenter(LifecycleOwner owner) {

        super(owner);

        Observable.interval(1, 1, TimeUnit.SECONDS)

                .as(RxLife.as(this)) //这里的this 为Scope接口对象

                .subscribe(aLong -> {

                    Log.e("LJX", "onNext aLong=" + aLong);

                });

    }

}

   RxLife类里面的as系列方法,皆适用于Observable、Flowable、ParallelFlowable、Single、Maybe、Completable这6个被观察者对象,道理都一样,这里不在一一讲解。

                另外,在Activity/Fragment上,如果你想在某个生命周期方法中断管道,可使用as操作符的重载方法。

                在Activity/Fragment上:

                Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒发送一条消息

                .as(RxLife.as(this, Event.ON_STOP)) //在onStop方法中断管道

                .subscribe(aLong -> {

                Log.e("LJX", "accept=" + aLong);

                });

                此时如果你还想在主线程回调观察者,使用asOnMain方法即可。

                在Activity/Fragment上:

                Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒发送一条消息

                .as(RxLife.asOnMain(this, Event.ON_STOP)) //在onStop方法中断管道,并在主线程回调观察者

                .subscribe(aLong -> {

                Log.e("LJX", "accept=" + aLong);

                });

                等同于:

                Observable.interval(1, 1, TimeUnit.SECONDS)  //隔一秒发送一条消息

                .observeOn(AndroidSchedulers.mainThread())

                .as(RxLife.as(this, Event.ON_STOP)) //在onStop方法中断管道,并在主线程回调观察者

                .subscribe(aLong -> {

                Log.e("LJX", "accept=" + aLong);

                });


总结:

   其实trello/RxLifecycle、uber/AutoDispose、RxLife三者的原理都是一样的,都是拿到最低层观察者的Disposable对象,然后在某个时机,调用该对象的Disposable.dispose()方法中断管道,以达到目的,原理都一样,然而实现却大不相同。

1、trello/RxLifecycle (3.0.0版本) 内部只有一个管道,但却有两个事件源,一个发送生命周期状态变化,一个发送正常业务逻辑,最终通过takeUntil操作符对事件进行过滤,当监听到符合条件的事件时,就会将管道中断,从而到达目的。

2、uber/AutoDispose(1.2.0版本) 内部维护了两个管道,一个是发送生命周期状态变化的管道,我们称之为A管道,另一个是业务逻辑的管道,我们称至为B管道,B管道持有A管道的观察者引用,故能监听A管道的事件,当监听到符合条件的事件时,就会将A、B管道同时中断,从而到达目的。

3、RxLife内部只有一个业务逻辑的管道,通过自定义观察者,拿到Disposable对象,暴露给Scope接口,Scope的实现者就可以在合适的时机调用Disposable.dispose()方法中断管道,从而到达目的。


三:扩展阅读

1、https://juejin.im/post/5cf3e1235188251c064815f1(RxLife 史上最优雅的管理RxJava生命周期)

2、https://blog.csdn.net/kong_gu_you_lan/article/details/74469041(Android 使用RxLifecycle解决RxJava内存泄漏)

3、https://blog.csdn.net/mq2553299/article/details/79418068(Android架构中添加AutoDispose解决RxJava内存泄漏)

4、https://blog.danlew.net/2017/08/02/why-not-rxlifecycle/(Why Not RxLifecycle?)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,444评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,867评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,157评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,312评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,673评论 3 289
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,802评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,470评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,696评论 2 250
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,187评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,538评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,188评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,127评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,889评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,741评论 2 274