1. 程式人生 > >AutoDispose代替RxLifecycle優雅的解決RxJava記憶體洩漏問題

AutoDispose代替RxLifecycle優雅的解決RxJava記憶體洩漏問題

使用過Rxjava的小夥伴都知道,在使用RxJava時如果處理不當,很可能會產生記憶體洩漏的問題。
我們使用rxjava最大的原因是響應式程式設計使我們的非同步操作程式碼變得很優雅,在Android中,也使執行緒切換變得很簡單,而產生記憶體洩漏的大部分原因都是在非同步執行耗時操作時,我們關閉了Activity,但是由於rxjava仍然持有Activity的引用,導致Activity無法被記憶體回收。這樣就造成了記憶體洩漏問題。

我們先舉個例子來看看記憶體洩漏產生的過程及結果

記憶體洩漏小例子

佈局很簡單,就是一個按鈕和一個TextView

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context
=".MainActivity">
<TextView android:id="@+id/numTv" android:text="數值" android:padding="10dp" android:gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn" app:layout_constraintTop_toBottomOf
="@id/numTv" android:layout_width="match_parent" android:layout_marginTop="10dp" android:layout_height="wrap_content" android:text="點選" />
</android.support.constraint.ConstraintLayout>

就長這樣

這裡寫圖片描述

新增rxjava依賴,這裡我是用kotlin寫的demo,所以依賴的是rxkotlin,使用java的直接依賴rxjava的sdk即可。

 /*RxJava相關依賴*/
    implementation "io.reactivex.rxjava2:rxkotlin:2.2.0"
    implementation "io.reactivex.rxjava2:rxandroid:2.0.2"

下面我們來看看Activity的程式碼,kotlin寫的,也很簡單。這裡扯點題外話,kotlin寫起來真的很爽,不需要findviewbyid,空安全,型別推斷,擴充套件函式等特性用起來真的很爽,在實際專案中能減少很多程式碼,值得一試。

package com.yzq.autodisposedemo

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.TimeUnit

class MainActivity : AppCompatActivity(), View.OnClickListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn.setOnClickListener(this)
    }
    override fun onClick(view: View?) {
        when (view!!.id) {
            R.id.btn ->
                doSomting()
        }
    }
    private fun doSomting() {
        Observable.interval(1, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())//在主執行緒處理結果
                .subscribeOn(Schedulers.io())//工作執行緒處理邏輯
                .subscribe(Consumer {
                    Log.i("rxjava發射的資料",it.toString())
                    numTv.text = it.toString()

                })
    }

    override fun onDestroy() {
        super.onDestroy()

        Log.i("MainActivity","onDestroy")
    }
}

我們在點選按鈕後,RxJava開始每隔一秒就發一個數值給我們,我們將其更新到textview上。但是當我們關閉應用時,rxjava仍然在繼續傳送資料,並且還持有MainActivity的例項,這就導致了MainActivity 無法被回收,造成了記憶體洩漏。
如下圖所示,我們在多次點選按鈕後,然後關閉應用,再點選記憶體回收按鈕後發現記憶體中的MainActivity例項並沒有被銷燬。記憶體分析具體看下圖

這裡寫圖片描述

在實際專案中我們經常有這種需求,比如網路請求一個介面,然後更新ui等。這些操作都可能產生記憶體洩漏。下面我們來看看解決辦法。

如何解決RxJava記憶體洩漏

1.在ondestory中手動切斷連線不推薦
這種方法是比較原始的方法,在onSubscribe我們將Disposable儲存起來,在onDestory中呼叫disposable.dispose()取消訂閱

如果在實際開發中使用這種方法,我們需要手動的去維護所有RxJava產生的Disposable,費時費力。

package com.yzq.autodisposedemo

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.TimeUnit

class MainActivity : AppCompatActivity(), View.OnClickListener {

    lateinit var disposable: Disposable

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

    override fun onClick(view: View?) {
        when (view!!.id) {
            R.id.btn ->
                doSomting()
        }
    }

    private fun doSomting() {
        Observable.interval(1, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())//在主執行緒處理結果
                .subscribeOn(Schedulers.io())//工作執行緒處理邏輯
                .subscribe(object : Observer<Long> {

                    override fun onSubscribe(d: Disposable) {
                        disposable = d

                    }

                    override fun onNext(t: Long) {
                        Log.i("rxjava發射的資料", t.toString())
                        numTv.text = t.toString()
                    }

                    override fun onError(e: Throwable) {
                    }

                    override fun onComplete() {
                    }

                })
    }


    override fun onDestroy() {
        super.onDestroy()
        disposable.dispose()//取消繫結
        Log.i("MainActivity", "onDestroy")
    }
}

2.使用RxLifecycle自動解綁(不推薦)
首先 RxLifecycle Github地址。
在使用AutoDispose之前,我一直使用的是RxLifecycle去解決RxJava的記憶體洩漏問題。
使用方法很簡單。具體使用方法我這裡就不介紹了,很簡單,可以直接看Github上的文件。
大體使用方法如下

    Observable.interval(1, TimeUnit.SECONDS)
                .doOnUnsubscribe { Log.i(TAG, "Unsubscribing subscription from onCreate()") }
                .bindUntilEvent(this, ActivityEvent.PAUSE)
                .subscribe { num -> Log.i(TAG, "Started in onCreate(), running until onPause(): " + num!!) }

我們可以直接使用bindUntilEvent(this, ActivityEvent.PAUSE)來實現自動解綁的功能,並且可以指定在哪個生命週期去自動解綁,但是前提條件是我們的Activity必須要繼承RxLifecycle提供的RxAppCompatActivity,同樣的我們的Fragment必須要繼承RxLifecycle提供的RxFragment
在Java中,類都是單繼承的,我們的Activity如果需要繼承別的類,那麼我們就必須多寫一個Activity基類供下面的Activity去繼承。這顯然是不優雅的實現方式。
如果說你的Activity或Fragment不需要繼承其他的Activity或Fragment,那麼使用RxLifecycle也沒有什麼不妥。

3.使用AutoDispose優雅的實現RxJava自動解綁

首先是 AutoDispose Github地址。
AutoDispose是uber的一個開源庫。

我們先來看看使用方法:

在Java中使用

  Observable.interval(1, TimeUnit.SECONDS)
        .doOnDispose(new Action() {
          @Override public void run() throws Exception {
            Log.i(TAG, "Disposing subscription from onResume() with untilEvent ON_DESTROY");
          }
        })
        .as(AutoDispose.<Long>autoDisposable(
            AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)))//OnDestory時自動解綁
        .subscribe(new Consumer<Long>() {
          @Override public void accept(Long num) throws Exception {
            Log.i(TAG, "Started in onResume(), running until in onDestroy(): " + num);
          }
        });

在Kotlin中使用

 Observable.interval(1, TimeUnit.SECONDS)
        .doOnDispose {
          Log.i(TAG, "Disposing subscription from onResume() with untilEvent ON_DESTROY")
        }
        .autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))//OnDestory時自動解綁
        .subscribeBy { num -> Log.i(TAG, "Started in onResume(), running until in onDestroy(): $num") }

可以看到,使用方法跟RxLifecycle很像,我們只需要呼叫一下
autoDisposable(AndroidLifecycleScopeProvider.from(this,Lifecycle.Event.ON_DESTROY)) 即可(這裡是kotlin的寫法,java的可以看官方的demo)
首先我們來看看
AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)中的this是什麼。

這裡寫圖片描述

通過原始碼我們可以看到這個this實際上是LifecycleOwner這個介面,也就是說只要是在實現這個介面的類中我們就可以直接使用
autoDisposable(AndroidLifecycleScopeProvider.from(this,Lifecycle.Event.ON_DESTROY))
進行自動解綁。

AutoDispose比RxLifecycle好的地方在於它不需要你的Activity或Fragment繼承指定的類。只要你的Activity或Fragment的父類實現了LifecycleOwner這個介面即可。
通過原始碼發現,support.v7包中的AppCompatActivity最終繼承自SupportActivity,SupportActivity實現了LifecycleOwner介面。
support.v4包中的Fragment也實現了LifecycleOwner介面。
而我們目前的專案中為了保證相容性,都是要依賴Android Support v7這個包的。這樣一來我們就可以優雅的通過AutoDispose解決RxJava產生的記憶體洩漏問題了