Android RxLife 一款輕量級(jí)別的RxJava生命周期管理庫(kù)(一)

簡(jiǎn)介

RxLife是一款輕量級(jí)別的RxJava生命周期管理庫(kù),代碼侵入性極低,隨用隨取,不需要做任何準(zhǔn)備工作,支持在Activity/Fragment 的任意生命周期方法斷開管道垒迂。

原理

RxLife通過Jetpack 下的 Lifecycle 獲取 Activity/Fragment 的生命周期變化,并通過Observable.lift(ObservableOperator) 操作符妒蛇,注入自己實(shí)現(xiàn)的Observer對(duì)象(該對(duì)象能感知 Activity/Fragment的生命周期變化)机断,從而在onSubscribe(Disposable d)方法中拿到Disposable對(duì)象,隨后在相應(yīng)的生命周期回調(diào)里執(zhí)行Disposable.dispose()方法斷開管道绣夺,這樣就能將lift操作符上面的所有Disposable對(duì)象全部斷開吏奸。

為什么要重復(fù)造輪子

熟悉RxJava的同學(xué)應(yīng)該都知道trello/RxLifecycle 項(xiàng)目,它在目前的3.0.0版本中通過Lifecycle感知Activity/Fragment 的生命周期變化陶耍,并通過BehaviorSubject類及compose奋蔚、takeUntil操作符來實(shí)現(xiàn)管道的中斷,這種實(shí)現(xiàn)原理有一點(diǎn)不足的是,它在管道斷開后泊碑,始終會(huì)往下游發(fā)送一個(gè)onComplete事件坤按,這對(duì)于在onComplete事件中有業(yè)務(wù)邏輯的同學(xué)來說,無(wú)疑是致命的馒过。那為什么會(huì)這樣呢臭脓?因?yàn)?code>takeUntil操作符內(nèi)部實(shí)現(xiàn)機(jī)制就是這樣的,有興趣的同學(xué)可以去閱讀takeUntil操作符的源碼沉桌,這里不展開。而RxLife就不會(huì)有這樣問題算吩,因?yàn)樵谠砩?code>RxLife就與trello/RxLifecycle不同留凭,并且RxLife還在lift操作都的基礎(chǔ)上提供了一些額外的api,能有效的避免因RxJava內(nèi)部類持有Activity/Fragment的引用偎巢,而造成的內(nèi)存泄漏問題蔼夜,下面開始講解。

gradle依賴


implementation 'com.rxjava.rxlife:rxlife:1.0.4'

源碼下載

用法


Observable.timer(10, TimeUnit.SECONDS)

        //默認(rèn)在onDestroy時(shí)中斷管道

        .lift(RxLife.lift(this))

        .subscribe(aLong -> {

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

        });

//或者

Observable.timer(10, TimeUnit.SECONDS)

        //指定在onStop時(shí)中斷管道

        .lift(RxLife.lift(this,Event.ON_STOP))

        .subscribe(aLong -> {

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

        });

在Activity/Fragment 中压昼,使用Observable的lift()操作符求冷,方法中傳入RxLife.lift(this),如果需要指定生命周期方法窍霞,額外再傳一個(gè)Event對(duì)象即可匠题。怎么樣?但金?是不是極其簡(jiǎn)單韭山,根本不需要做任何準(zhǔn)備工作,代碼侵入性極低冷溃。

處理內(nèi)存泄漏

我們來看一個(gè)案例


public void leakcanary(View view) {

    Observable.timer(100, TimeUnit.MILLISECONDS)

            .map(new MyFunction<>()) //阻塞操作

            .lift(RxLife.lift(this))

            .subscribe(new Consumer<Long>() { //這里使用匿名內(nèi)部類钱磅,持有Activity的引用

                //注意這里不能使用Lambda表達(dá)式,否則leakcanary檢測(cè)不到內(nèi)存泄漏

                @Override

                public void accept(Long aLong) throws Exception {

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

                }

            });

}

//這里使用靜態(tài)內(nèi)部類似枕,不會(huì)持有外部類的引用

static class MyFunction<T> implements Function<T, T> {

    @Override

    public T apply(T t) throws Exception {

        //當(dāng)dispose時(shí)盖淡,第一次睡眠會(huì)被吵醒,接著便會(huì)進(jìn)入第二次睡眠

        try {

            Thread.sleep(3000);

        } catch (Exception e) {

        }

        try {

            Thread.sleep(30000);

        } catch (Exception e) {

        }

        return t;

    }

}

上面的代碼會(huì)造成Activity無(wú)法回收,導(dǎo)致內(nèi)存泄漏凿歼,我們用Leakcannry工具來檢測(cè)一下褪迟,發(fā)現(xiàn)確實(shí)造成來內(nèi)存泄漏,如下

在這里插入圖片描述

我們已經(jīng)使用RxLife庫(kù)答憔,會(huì)自動(dòng)中斷管道牵咙,那為什么還會(huì)造成內(nèi)存泄漏呢?其實(shí)原因很簡(jiǎn)單攀唯,我們只是中斷了管道洁桌,而沒有中斷上游對(duì)下游引用『钹郑看上面的截圖就能知道另凌,上游始終持有下游的引用谱轨,而最下游的匿名內(nèi)部類Consumer又持有了Activity的引用,所以就導(dǎo)致了Activity無(wú)法回收吠谢。

那為什么中斷管道時(shí)土童,不會(huì)中斷上下游的引用呢?

首先有一點(diǎn)我們需要明確工坊,調(diào)用Disposable.dispose()方法來斷開管道献汗,并不是真正意義上的將上游與下游斷開,它只是改變了管道上各個(gè)Observer對(duì)象的一個(gè)標(biāo)志位的值王污,我們來看一下LambdaObserver類的源碼就會(huì)知道


@Override

    public void dispose() {

        DisposableHelper.dispose(this);

    }

呃呃罢吃,只有一行代碼,我們繼續(xù)


public static boolean dispose(AtomicReference<Disposable> field) {

        Disposable current = field.get(); //此處得到上游的Disposable對(duì)象

        Disposable d = DISPOSED;

        if (current != d) {

            current = field.getAndSet(d); //更改自己的標(biāo)志位為DISPOSED

            if (current != d) {

                if (current != null) {

                    current.dispose();//關(guān)閉上游的Disposable對(duì)象

                }

                return true;

            }

        }

        return false;

    }

可以看到昭齐,這里只做了兩件事尿招,一是更改自己的標(biāo)志位,二是調(diào)用上游的dispose()方法阱驾,其實(shí)你只要多看看就谜,你就發(fā)現(xiàn),RxJava內(nèi)部大多數(shù)Observer在dispose()方法都會(huì)干這兩件事里覆。

到這丧荐,我們?cè)撊绾谓鉀Q這個(gè)內(nèi)存泄漏問題呢?其實(shí)喧枷,RxJava早就想到了這一點(diǎn)篮奄,它給我們提供了一個(gè)onTerminateDetach()操作符,這個(gè)操作符會(huì)在onError(Throwable t)割去、onComplete()窟却、dispose()這個(gè)3個(gè)時(shí)刻,斷開上游對(duì)下游的引用呻逆,我們來看看源碼夸赫,源碼在ObservableDetach類中


@Override

public void dispose() {

    Disposable d = this.upstream;

    this.upstream = EmptyComponent.INSTANCE;//上游重新賦值

    this.downstream = EmptyComponent.asObserver();//下游重新賦值

    d.dispose();//調(diào)用上游的dispose()方法

}

@Override

public void onError(Throwable t) {

    Observer<? super T> a = downstream;

    this.upstream = EmptyComponent.INSTANCE;//上游重新賦值

    this.downstream = EmptyComponent.asObserver();//下游重新賦值

    a.onError(t); //調(diào)用下游的onError方法

}

@Override

public void onComplete() {

    Observer<? super T> a = downstream;

    this.upstream = EmptyComponent.INSTANCE;//上游重新賦值

    this.downstream = EmptyComponent.asObserver();//下游重新賦值

    a.onComplete();//調(diào)用下游的onComplete方法

}

到這,我們就知道該怎么做了咖城,下面這樣寫就安全了


Observable.timer(100, TimeUnit.MILLISECONDS)

        .map(new MyFunction<>())//阻塞操作

        .onTerminateDetach() //管道斷開時(shí)茬腿,中斷上游對(duì)下游的引用

        .lift(RxLife.lift(this)) //默認(rèn)在onDestroy時(shí)斷開管道

        .subscribe(aLong -> {

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

        });

可是,每次都要這樣寫嗎宜雀?有沒有更簡(jiǎn)單的切平,有,RxLife提供了RxLife.compose(LifecycleOwner)方法辐董,內(nèi)部就是將onTerminateDetach悴品、lift這兩個(gè)操作符整合在了一起,接下來,看看如何使用


Observable.timer(100, TimeUnit.MILLISECONDS)

        .map(new MyFunction<>())//阻塞操作

        //注意這里使用compose操作符

        .compose(RxLife.compose(this))//默認(rèn)在onDestroy時(shí)中斷管道苔严,并中斷下下游之間的引用

        .subscribe(aLong -> {

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

        });

如果需要指定生命周期的方法定枷,也可以


Observable.timer(100, TimeUnit.MILLISECONDS)

        .map(new MyFunction<>())//阻塞操作

        //注意這里使用compose操作符

        .compose(RxLife.compose(this, Event.ON_STOP))//指定在onStop時(shí)斷開管道

        .subscribe(aLong -> {

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

        });

    }

大多數(shù)情況下,我們希望觀察者能主線程進(jìn)行回調(diào)届氢,也許你會(huì)這樣寫


Observable.timer(100, TimeUnit.MILLISECONDS)

        .map(new MyFunction<>())//阻塞操作

        .observeOn(AndroidSchedulers.mainThread()) //在主線程回調(diào)

        .compose(RxLife.compose(this, Event.ON_STOP))//指定在onStop回調(diào)時(shí)中斷管道欠窒,并中斷上下游引用

        .subscribe(aLong -> {

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

        });

如果你是用RxLife的話,就可以這樣寫退子,使用RxLife.composeOnMain方法


Observable.timer(100, TimeUnit.MILLISECONDS)

        .map(new MyFunction<>())//阻塞操作

        //在主線程進(jìn)程回調(diào)岖妄,在onStop回調(diào)時(shí)中斷管道,并中斷上下游引用

        .compose(RxLife.composeOnMain(this, Event.ON_STOP))

        .subscribe(aLong -> {

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

        });

RxLife類就只有6個(gè)靜態(tài)方法寂祥,如下

shshs

注意荐虐,前方高能預(yù)警!H腊小8壳巍>V椤!恬惯!

結(jié)合RxLife使用Observable的lift向拆、compose操作符時(shí),下游除了subscribe操作符外最好不要有其它的操作符酪耳,前面講過浓恳,當(dāng)調(diào)用Disposable.dispose()時(shí),它會(huì)往上一層一層的調(diào)用上游的dispose()方法碗暗,如果下游有Disposable對(duì)象颈将,是調(diào)用不到的,如果此時(shí)下游有自己的事件需要發(fā)送言疗,那么就無(wú)法攔截了晴圾。

如:


Observable.just(1)

        .compose(RxLife.compose(this))

        .flatMap((Function<Integer, ObservableSource<Long>>) integer -> {

            //每隔一秒發(fā)送一個(gè)數(shù)據(jù),共10個(gè)

            return Observable.intervalRange(0, 10, 0, 1, TimeUnit.SECONDS);

        })

        .subscribe(aLong -> {

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

        });

這樣噪奄,即使Activity關(guān)閉了死姚,觀察者每隔一秒后,依然能收到來自上游的事件勤篮,因?yàn)?code>compose無(wú)法切斷下游的管道都毒,我們改一下上面的代碼


Observable.just(1)

        .flatMap((Function<Integer, ObservableSource<Long>>) integer -> {

            //每隔一秒發(fā)送一個(gè)數(shù)據(jù),共10個(gè)

            return Observable.intervalRange(0, 10, 0, 1, TimeUnit.SECONDS);

        })

        .compose(RxLife.compose(this))

        .subscribe(aLong -> {

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

        });

這樣ok了碰缔,其實(shí)這不是RxLife的問題账劲,使用鼎鼎大名的trello/RxLifecycle庫(kù)也是一樣的,因?yàn)镽xJava的設(shè)計(jì)就是如此,上游拿不到下游的Disposable對(duì)象涤垫,所以姑尺,我們?cè)谑褂?code>RxLife時(shí),一定要注意在lift或者compose操作符的下游蝠猬,除了subscribe操作符外最好不要有其它的操作符切蟋,這一點(diǎn)一定需要注意。

RxLife最新版本已經(jīng)使用as操作符規(guī)避這個(gè)問題榆芦,詳情查看Android RxLife 一款輕量級(jí)別的RxJava生命周期管理庫(kù)(二)

小彩蛋

RxLife類里面的life柄粹、compose系列方法,皆適用于Flowable匆绣、Observable驻右、Single、Maybe崎淳、Completable這5個(gè)被觀察者對(duì)象堪夭,道理都一樣,這里不在一一講解拣凹。

結(jié)尾

Ok森爽,RxLife的使用基本就介紹完了,到這我們會(huì)發(fā)現(xiàn)嚣镜,使用RxLife庫(kù)爬迟,我們只需要關(guān)注一個(gè)類即可,那即是RxLife類菊匿,api簡(jiǎn)單功能卻強(qiáng)大付呕。敢興趣的同學(xué),可以去閱讀RxLife源碼跌捆,有疑問徽职,請(qǐng)留言,我會(huì)在第一時(shí)間作答佩厚。

擴(kuò)展

RxLife結(jié)合HttpSender發(fā)送請(qǐng)求姆钉,簡(jiǎn)直不要太爽。

HttpSender詳情請(qǐng)點(diǎn)擊HttpSender OkHttp+RxJava超好用可款、功能超級(jí)強(qiáng)大的Http請(qǐng)求框架

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末育韩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闺鲸,更是在濱河造成了極大的恐慌筋讨,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摸恍,死亡現(xiàn)場(chǎng)離奇詭異悉罕,居然都是意外死亡赤屋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門壁袄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來类早,“玉大人,你說我怎么就攤上這事嗜逻∩В” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵栈顷,是天一觀的道長(zhǎng)逆日。 經(jīng)常有香客問我,道長(zhǎng)萄凤,這世上最難降的妖魔是什么室抽? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮靡努,結(jié)果婚禮上坪圾,老公的妹妹穿的比我還像新娘。我一直安慰自己惑朦,他們只是感情好兽泄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著行嗤,像睡著了一般已日。 火紅的嫁衣襯著肌膚如雪垛耳。 梳的紋絲不亂的頭發(fā)上栅屏,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音堂鲜,去河邊找鬼栈雳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缔莲,可吹牛的內(nèi)容都是我干的哥纫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼痴奏,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蛀骇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起读拆,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤擅憔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后檐晕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暑诸,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚌讼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了个榕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篡石。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖西采,靈堂內(nèi)的尸體忽然破棺而出凰萨,到底是詐尸還是另有隱情,我是刑警寧澤械馆,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布沟蔑,位于F島的核電站,受9級(jí)特大地震影響狱杰,放射性物質(zhì)發(fā)生泄漏瘦材。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一仿畸、第九天 我趴在偏房一處隱蔽的房頂上張望双仍。 院中可真熱鬧,春花似錦嚷硫、人聲如沸肺孤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)憔儿。三九已至,卻和暖如春放可,著一層夾襖步出監(jiān)牢的瞬間谒臼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工耀里, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜈缤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓冯挎,卻偏偏與公主長(zhǎng)得像底哥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子房官,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353