RxJava內(nèi)存泄漏的一種解決方案

RxJava大家應(yīng)該都用過或者聽過气嫁,在用RxJava的時候當被觀察者(Observable)和觀察者(Observer)產(chǎn)生訂閱關(guān)系后沒有及時釋放這種subscription就很容易產(chǎn)生內(nèi)存泄漏速梗,一個典型的場景就是使用RxJava發(fā)起網(wǎng)絡(luò)請求傲宜,此時應(yīng)用程序被殺掉迁酸,這種訂閱關(guān)系就沒有得到及時釋放炸宵。當然這種情況在onDestroy中手動進行判斷也行。如果是這種場景君纫,發(fā)起的網(wǎng)絡(luò)請求還沒成功返回驯遇,此時應(yīng)用進入后臺,這時候就算請求成功返回也不應(yīng)該更新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產(chǎn)生的內(nèi)存泄漏陡叠。今天我們主要看下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

這種方式可以自動根據(jù)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中進行解綁反璃,其他的一一對應(yīng)就是昵慌。

/**
 * Lifecycle events that can be emitted by Activities.
 */
public enum ActivityEvent {

    CREATE,
    START,
    RESUME,
    PAUSE,
    STOP,
    DESTROY

}

Fragment也有對應(yīng)的生命周期,也是對稱對應(yīng)的淮蜈。

/**
 * 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會停止發(fā)送數(shù)據(jù)存皂,看下打印的日志是不是這樣的:


bindUntilEvent.PNG

基本上面這兩種方式就夠用了,下面的方式LifecycleProvider在MVP的模式中用處就比較大了逢艘。我們接著往下看旦袋。

3.LifecycleProvider

使用方式就是,首先繼承NaviActivity它改,然后在Activity中加上這句話

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

這樣就可以通過provider監(jiān)聽生命周期了疤孕。我這里在初始化presenter的時候傳遞過去

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

在Activity中添加一個按鈕,通過延時3秒模擬網(wǎng)絡(luò)請求央拖,請求成功后更新UI祭阀。這里通過 provider發(fā)送生命周期事件,然后在onNext中判斷事件類型鲜戒,如果已經(jīng)Activity已經(jīng)進入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

下面我們來搞一下破壞境输,在剛發(fā)起網(wǎng)絡(luò)請求后蔗牡,按home按鍵將APP切到后臺,這樣Activity就進入onStop的生命周期嗅剖,那么即使網(wǎng)絡(luò)請求成功也應(yīng)該再更新UI了辩越,看下日志是不是這樣的:

破壞.PNG

最終UI也是沒有更新的。我們在下面的邏輯中切斷了訂閱關(guān)系信粮,那么下次再次點擊按鈕發(fā)起網(wǎng)絡(luò)請求是還能正常使用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的日志:

切斷訂閱關(guān)系后再次監(jiān)聽生命周期.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.總結(jié)

RxLifecycle的侵入性還是比較低的,基本不需要改動原來的代碼就可以實現(xiàn)生命周期的監(jiān)聽赏胚,也提供了防止RxJava訂閱關(guān)系內(nèi)存泄漏的另外一種解決方案访娶,還是很不錯的。

今天的分享就到這了觉阅,后面有機會和大家分享下RxLifecycle的源碼崖疤。

平時工作也比較忙,寫博客真的是需要耐力典勇,如果對大家有幫助歡迎關(guān)注和點贊哈劫哼。

最后感謝@右傾傾的理解和支持哈。

以上割笙!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末权烧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子咳蔚,更是在濱河造成了極大的恐慌豪嚎,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谈火,死亡現(xiàn)場離奇詭異侈询,居然都是意外死亡,警方通過查閱死者的電腦和手機糯耍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門扔字,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人温技,你說我怎么就攤上這事革为。” “怎么了舵鳞?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵震檩,是天一觀的道長。 經(jīng)常有香客問我蜓堕,道長抛虏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任套才,我火速辦了婚禮迂猴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘背伴。我一直安慰自己沸毁,他們只是感情好峰髓,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著息尺,像睡著了一般携兵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掷倔,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天眉孩,我揣著相機與錄音,去河邊找鬼勒葱。 笑死,一個胖子當著我的面吹牛巴柿,可吹牛的內(nèi)容都是我干的凛虽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼广恢,長吁一口氣:“原來是場噩夢啊……” “哼凯旋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钉迷,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤至非,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后糠聪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荒椭,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年舰蟆,在試婚紗的時候發(fā)現(xiàn)自己被綠了趣惠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡身害,死狀恐怖味悄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塌鸯,我是刑警寧澤侍瑟,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站丙猬,受9級特大地震影響涨颜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淮悼,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一咐低、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袜腥,春花似錦见擦、人聲如沸钉汗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽损痰。三九已至,卻和暖如春酒来,著一層夾襖步出監(jiān)牢的瞬間卢未,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工堰汉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辽社,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓翘鸭,卻偏偏與公主長得像滴铅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子就乓,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容