RxJava内存泄漏的一种解决方案

作者: juexingzhe | 来源:发表于2017-11-13 20:22 被阅读517次

    RxJava大家应该都用过或者听过,在用RxJava的时候当被观察者(Observable)和观察者(Observer)产生订阅关系后没有及时释放这种subscription就很容易产生内存泄漏,一个典型的场景就是使用RxJava发起网络请求,此时应用程序被杀掉,这种订阅关系就没有得到及时释放。当然这种情况在onDestroy中手动进行判断也行。如果是这种场景,发起的网络请求还没成功返回,此时应用进入后台,这时候就算请求成功返回也不应该更新UI,这种情况在使用RxJava的情况下怎么处理?

    我们遇到的情况早有大神提供了解决方案,就是RxLifecycle开源库,官方的定义:

    RxLifecycle
    
    This library allows one to automatically complete sequences based on a second lifecycle stream.
    
    This capability is useful in Android, where incomplete subscriptions can cause memory leaks.
    

    简单来讲就是可以用来处理RxJava产生的内存泄漏。今天我们主要看下RxLifecycle的几种使用方式。后面有机会再分析下源码。

    主要有下面几种使用方式:

    1 bindToLifecycle
    2 bindUntilEvent
    3 LifecycleProvider

    首先需要在模块的build.gradle中添加依赖:

    compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
    compile 'com.trello.rxlifecycle2:rxlifecycle-navi:2.1.0'
    

    1.bindToLifecycle

    这种方式可以自动根据Activity或者Fragment的生命周期进行解绑,用起来也很方便,Avtivity需要继承RxActivity, Fragment则需要继承RxFragment

    public class MainActivity extends BaseActivity{
        @Override
        protected void onStart() {
            super.onStart();
            Observable.interval(1, TimeUnit.SECONDS)
                    .subscribeOn(Schedulers.io())
                    .compose(this.<Long>bindToLifecycle())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<Long>() {
                        @Override
                        public void accept(Long num) throws Exception {
                            Timber.tag(TAG).d("onStart, running num : " + num);
                        }
                    });
        }
    }
    

    这里在onStart中进行绑定,如果Activity进入onStop生命周期的时候就会停止Observable,看一下日志:

    bindToLifecycle.PNG
    bindToLifecycle就是会自动绑定生命周期,我们看下Activity生命周期,很明显在onCreate中bindToLifecycle就会在onDestroy中进行解绑,其他的一一对应就是。
    /**
     * Lifecycle events that can be emitted by Activities.
     */
    public enum ActivityEvent {
    
        CREATE,
        START,
        RESUME,
        PAUSE,
        STOP,
        DESTROY
    
    }
    

    Fragment也有对应的生命周期,也是对称对应的。

    /**
     * Lifecycle events that can be emitted by Fragments.
     */
    public enum FragmentEvent {
    
        ATTACH,
        CREATE,
        CREATE_VIEW,
        START,
        RESUME,
        PAUSE,
        STOP,
        DESTROY_VIEW,
        DESTROY,
        DETACH
    
    }
    

    2.bindUntilEvent

    见名知意,就是可以和制定的生命周期进行绑定,这种方式比上面的灵活,比如可以在一个按钮中绑定onStart的事件,而不必要一定要卸载onStart中。

    我在上面的Activity中添加一个按钮,点击事件在getData(View view)中,看下代码:

    public void getData(View view) {
        Observable observable = Observable.interval(1, TimeUnit.SECONDS).
                subscribeOn(Schedulers.io()).compose(this.bindUntilEvent(ActivityEvent.PAUSE));
        observable.observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long num) throws Exception {
                        Timber.tag(TAG).d("getData, running until num : " + num);
                    }
                });
    }
    

    按我们的预期,就是在Activity进入onPause时,Observable会停止发送数据,看下打印的日志是不是这样的:


    bindUntilEvent.PNG

    基本上面这两种方式就够用了,下面的方式LifecycleProvider在MVP的模式中用处就比较大了。我们接着往下看。

    3.LifecycleProvider

    使用方式就是,首先继承NaviActivity,然后在Activity中加上这句话

    LifecycleProvider<ActivityEvent> provider = NaviLifecycle.createActivityLifecycleProvider(this);
    

    这样就可以通过provider监听生命周期了。我这里在初始化presenter的时候传递过去

    @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ......
            //初始化Presenter
            presenter = new Presenter(provider);
        }
    

    在Activity中添加一个按钮,通过延时3秒模拟网络请求,请求成功后更新UI。这里通过 provider发送生命周期事件,然后在onNext中判断事件类型,如果已经Activity已经进入onPause onStop onDestroy中的其中一个,就不再更新UI了,并且通过Disposable断开连接。

    FactoryModel.getModel(Token.STRING_MODEL).params(params).execute(new CallBack<String>() {
                @Override
                public void onSuccess(final String data) {
                    provider.lifecycle().subscribe(new Observer<ActivityEvent>() {
                        @Override
                        public void onSubscribe(Disposable d) {
                            disposable = d;
                        }
    
                        @Override
                        public void onNext(ActivityEvent activityEvent) {
                            Timber.tag("Presenter_TAG").i("received activityEvent, activityEvent = %s" , activityEvent.name());
                            if (null != disposable && disposable.isDisposed()){
                                Timber.tag("Presenter_TAG").i("disposable isDisposed");
                                return;
                            }
                            if (activityEvent == ActivityEvent.PAUSE || activityEvent == ActivityEvent.STOP || activityEvent == ActivityEvent.DESTROY){
                                Timber.tag("Presenter_TAG").e("do not refresh UI, activityEvent = %s", activityEvent.name());
                                onComplete();
                                return;
                            }
                            if (isViewAttached()) {
                                Timber.tag("Presenter_TAG").i("refresh UI, activityEvent = %s" , activityEvent.name());
                                view.showData(data);
                            }
                        }
    
                        @Override
                        public void onError(Throwable e) {
    
                        }
    
                        @Override
                        public void onComplete() {
                            if (null != disposable && !disposable.isDisposed()){
                                disposable.dispose();
                                Timber.tag("Presenter_TAG").d("LifecycleProvider disposed");
                            }
                        }
                    });
                }
            });
    

    看一下正常请求成功的日志,和预期一样,正常更新UI了。


    请求成功.PNG

    下面我们来搞一下破坏,在刚发起网络请求后,按home按键将APP切到后台,这样Activity就进入onStop的生命周期,那么即使网络请求成功也应该再更新UI了,看下日志是不是这样的:

    破坏.PNG
    最终UI也是没有更新的。我们在下面的逻辑中切断了订阅关系,那么下次再次点击按钮发起网络请求是还能正常使用provider吗?
    if (activityEvent == ActivityEvent.PAUSE || activityEvent == ActivityEvent.STOP || activityEvent == ActivityEvent.DESTROY){
         Timber.tag("Presenter_TAG").e("do not refresh UI, activityEvent = %s", activityEvent.name());
         onComplete();
         return;
    }
    

    其实是可以的,看一下Demo的日志:

    切断订阅关系后再次监听生命周期.PNG

    那么是怎么回事?其实就在下面这个代码中:

    provider.lifecycle()
    

    在获取Observable时会清楚原先的特征,我们看下源码:

    private final BehaviorSubject<ActivityEvent> lifecycleSubject = BehaviorSubject.create();
    
    @Override
    @NonNull
    @CheckResult
    public Observable<ActivityEvent> lifecycle() {
        return lifecycleSubject.hide();
    }
    
    

    再跟进去BehaviorSubject,其中以后一句注释Hides the identity of this Observable and its Disposable,最后会返回一个新的Observable :

    /**
         * Hides the identity of this Observable and its Disposable.
         * <p>Allows hiding extra features such as {@link io.reactivex.subjects.Subject}'s
         * {@link Observer} methods or preventing certain identity-based
         * optimizations (fusion).
         * <dl>
         *  <dt><b>Scheduler:</b></dt>
         *  <dd>{@code hide} does not operate by default on a particular {@link Scheduler}.</dd>
         * </dl>
         * @return the new Observable instance
         *
         * @since 2.0
         */
        @CheckReturnValue
        @SchedulerSupport(SchedulerSupport.NONE)
        public final Observable<T> hide() {
            return RxJavaPlugins.onAssembly(new ObservableHide<T>(this));
        }
    

    4.总结

    RxLifecycle的侵入性还是比较低的,基本不需要改动原来的代码就可以实现生命周期的监听,也提供了防止RxJava订阅关系内存泄漏的另外一种解决方案,还是很不错的。

    今天的分享就到这了,后面有机会和大家分享下RxLifecycle的源码。

    平时工作也比较忙,写博客真的是需要耐力,如果对大家有帮助欢迎关注和点赞哈。

    最后感谢@右倾倾的理解和支持哈。

    以上!

    相关文章

      网友评论

        本文标题:RxJava内存泄漏的一种解决方案

        本文链接:https://www.haomeiwen.com/subject/qnazmxtx.html