使用Rxjava2导致的内存泄露问题

Rxjava是个异步库,其链式的api调用使用起来非常简洁,优雅,但是不做处理的话很容易出现内存泄露

内存泄露例子:

有个MainActivity,代码如下:

class MainActivity : AppCompatActivity(), View.OnClickListener {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_start_second.setOnClickListener(this)

    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.btn_start_second->{startActivity(Intent(this,SecondActivity::class.java))}
        }
    }
}

MainActivity有个按钮,打开SecondActivity,SecondActivity代码如下:

class SecondActivity : AppCompatActivity(), View.OnClickListener {
    val TAG = this.javaClass.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        ds = CompositeDisposable()
        btn_start_count.setOnClickListener(this)
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.btn_start_count -> {
                Observable.interval(1, TimeUnit.SECONDS)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe {
                            tv_count.text = it.toString()
                            loge(TAG, "count:$it")
                        }         

            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        
    }
}

SecondActivity中有个Button,TextView,点击Button,TextView不断输出

观察测试:

运行App,开启SecondActivity,点击Button改变TextView的值,然后关闭SecondActivity,如此循环操作多次,发现即时关闭了SecondActivity,TextView依旧在输出:

01-15 17:29:33.563 26906-26906/com.rain.rxjava2demo E/SecondActivity: count:146
01-15 17:29:33.944 26906-26906/com.rain.rxjava2demo E/SecondActivity: count:144
01-15 17:29:34.471 26906-26906/com.rain.rxjava2demo E/SecondActivity: count:150
01-15 17:29:34.563 26906-26906/com.rain.rxjava2demo E/SecondActivity: count:147
01-15 17:29:34.948 26906-26906/com.rain.rxjava2demo E/SecondActivity: count:145

打开Profile分析器,观察Memory情况:

 操作几次后,手动调用GC后的内存占用,如下图:

 调取当前java堆的快照,发现有多个SecondActivity的引用及SecondActivity#onClick中的泄露

这时SecondActivity的代码做如下更改(只贴关键部分):

 private var d: Disposable? = null
 private lateinit var ds: CompositeDisposable

 override fun onClick(v: View) {
        when (v.id) {
            R.id.btn_start_count -> {
                d = Observable.interval(1, TimeUnit.SECONDS)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe {
                            tv_count.text = it.toString()
                            loge(TAG, "count:$it")
                        }
                ds.add(d!!)

            }
        }
 }

 override fun onDestroy() {
        super.onDestroy()
        // 这样处理发现依然存在内存泄露
        ds.clear()
    }

在onDestroy中,ds.clear(),即所有的disposable调用d.dispose() 方法,这样就不会内存泄露了吗?

验证测试:

依旧打开SecondActivity,并点击按钮,这样操作3次,先看log:

01-15 17:49:02.292 7197-7197/com.rain.rxjava2demo E/SecondActivity: count:0
01-15 17:49:05.384 7197-7197/com.rain.rxjava2demo E/SecondActivity: count:0
01-15 17:49:07.482 7197-7197/com.rain.rxjava2demo E/SecondActivity: count:0

发现subscribe中的代码确实不执行了,

看内存堆:

内存占用在65M左右,但是发现依旧存在SecondActivity的引用,先别急,过一段时间再看一次内存占用情况,会发现SecondActivity的引用没有了,虽然手动强制了GC,但是并不一定会立即进行回收,这点要注意。

有文章说d.dispose()只是切除了上下游的数据传递,并没有切断上下游的引用关系,代码做如下修改:

 d = Observable.interval(1, TimeUnit.SECONDS)
                        .onTerminateDetach()// 当执行了d.dispose()方法后将解除上下游的引用
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe {
                            tv_count.text = it.toString()
                            loge(TAG, "count:$it")
                        }
                ds.add(d!!)

代码修改的部分,调用 onTerminateDetach()操作符,看下源码的注释:

Nulls out references to the upstream producer and downstream Observer if
 the sequence is terminated or downstream calls dispose().

如果序列终止或者下游调用dispose(),那么将解除上下游的引用

有文章说onTerminateDetach操作符要和subscription.unsubscribe() 结合使用,因为不执行subscription.unsubscribe()的话,onTerminateDetach就不会被触发。这时如果我们对当前的stream再进一步操作,比如使用map操作符,那么需要再次调用onTerminateDetach。

但是我并没有发现二者在内存上的表现有什么不同

结论:

1. 在onDestroy方法中调用dispose()方法

第三方库的使用

1. 使用Rxlifecycle库,缺点是Activity、Fragment要继承RxActivity、RxFragment

简单使用方法如下:

引入库:

implementation "com.trello.rxlifecycle3:rxlifecycle:$rxlifecycle"
implementation "com.trello.rxlifecycle3:rxlifecycle-components:$rxlifecycle"

 Activity、Fragment继承RxActivity、RxFragment等,调用时候绑定对应的生命周期,详细用法请查阅官方文档

Observable.interval(1, TimeUnit.SECONDS)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .compose(this.bindUntilEvent(ActivityEvent.DESTROY))
                        .subscribe {
                            tv_count.text = it.toString()
                            loge(TAG, "count:$it")
                        }

2. 使用autodispose库,

引入:

implementation "com.uber.autodispose:autodispose-android-archcomponents-ktx:$autodispose_version"
implementation "com.uber.autodispose:autodispose-lifecycle-ktx:$autodispose_version"

调用如下:

 Observable.interval(1, TimeUnit.SECONDS)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                .autoDisposable(AndroidLifecycleScopeProvider.from(this,Lifecycle.Event.ON_DESTROY))
                        .subscribe {
                            tv_count.text = it.toString()
                            loge(TAG, "count:$it")
                        }

 这里都是演示最基本的用法

Demo

参考文章:

https://blog.csdn.net/johnny901114/article/details/67640594

https://blog.csdn.net/u011291205/article/details/73044256

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值