RxJava(十三)RxJava導(dǎo)致Fragment Activity內(nèi)存泄漏問(wèn)題

RxJava系列文章目錄導(dǎo)讀:

一黍少、RxJava create操作符的用法和源碼分析
二魂角、RxJava map操作符用法詳解
三、RxJava flatMap操作符用法詳解
四、RxJava concatMap操作符用法詳解
五、RxJava onErrorResumeNext操作符實(shí)現(xiàn)app與服務(wù)器間token機(jī)制
六唇辨、RxJava retryWhen操作符實(shí)現(xiàn)錯(cuò)誤重試機(jī)制
七、RxJava 使用debounce操作符優(yōu)化app搜索功能
八、RxJava concat操作處理多數(shù)據(jù)源
九搜变、RxJava zip操作符在Android中的實(shí)際使用場(chǎng)景
十绩社、RxJava switchIfEmpty操作符實(shí)現(xiàn)Android檢查本地緩存邏輯判斷
十一摔蓝、RxJava defer操作符實(shí)現(xiàn)代碼支持鏈?zhǔn)秸{(diào)用
十二、combineLatest操作符的高級(jí)使用
十三愉耙、RxJava導(dǎo)致Fragment Activity內(nèi)存泄漏問(wèn)題
十四贮尉、interval、takeWhile操作符實(shí)現(xiàn)獲取驗(yàn)證碼功能


一般我們?cè)趯?shí)際的開(kāi)發(fā)中朴沿,RxJava和Retrofit2結(jié)合使用的比較多猜谚,因?yàn)樗麄兛梢詿o(wú)縫集成,例如我們下面的一個(gè)網(wǎng)絡(luò)請(qǐng)求:

public interface OtherApi {

    @GET("/timeout")
    Observable<Response> testTimeout(@Query("timeout") String timeout);
}

private void getSomething(){
    subscription = otherApi.testTimeout("10000")
            .subscribe(new Action1<Response>() {
                @Override
                public void call(Response response) {
                    String content = new String(((TypedByteArray) response.getBody()).getBytes());
                    Log.d("RxJavaLeakFragment", RxJavaLeakFragment.this + ":" + content);
                }
            }, new Action1<Throwable>() {
                @Override
                public void call(Throwable throwable) {
                    throwable.printStackTrace();
                }
            });
}

上面的代碼非常簡(jiǎn)單赌渣,用過(guò)Retrofit2和RxJava一眼就看明白了魏铅,我們知道還需要在界面destroy的時(shí)候,把subscription反注銷掉坚芜,避免內(nèi)存泄漏览芳,如:

@Override
public void onDestroy() {
    super.onDestroy();
    if (subscription != null && !subscription.isUnsubscribed()) {
        subscription.unsubscribe();
        Log.d("RxJavaLeakFragment", "subscription.unsubscribe()");
    }
}

但是這真的能避免內(nèi)存泄漏嗎?下面我們來(lái)做一個(gè)實(shí)驗(yàn)鸿竖。

操作步驟:我們進(jìn)入某個(gè)界面(Activity沧竟、Fragment)铸敏,點(diǎn)擊按鈕請(qǐng)求網(wǎng)絡(luò),故意讓該網(wǎng)絡(luò)請(qǐng)求執(zhí)行10秒悟泵,在網(wǎng)絡(luò)返回前杈笔,我們關(guān)閉界面。

后端代碼如下:


//如果用戶傳進(jìn)來(lái)的timeout>0則當(dāng)前線程休眠timeout糕非,否則休眠20秒
@WebServlet("/timeout")
public class TimeoutServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        String timeout = request.getParameter("timeout");
        long to = getLong(timeout);
        if (to <= 0) {
            to = 20000;
        }
        try {
            Thread.sleep(to);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ResponseJsonUtils.json(response, "timeout success");
    }

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);

    }

    static long getLong(String value) {
        try {
            return Long.parseLong(value);
        } catch (Exception e) {
        }
        return -1;
    }

}

Fragment的代碼如下:

//點(diǎn)擊按鈕請(qǐng)求網(wǎng)絡(luò)蒙具,在成功回調(diào)方法里輸出服務(wù)器返回的結(jié)果和當(dāng)前Fragment的對(duì)象

@Override
public void onClick(View v) {
    super.onClick(v);
    switch (v.getId()) {
        case R.id.btn_request_netword_and_pop:
            if (otherApi == null) {
                otherApi = ApiServiceFactory.createService(OtherApi.class);
            }
            subscription = otherApi.testTimeout("10000")
                    .subscribe(new Action1<Response>() {
                        @Override
                        public void call(Response response) {
                            String content = new String(((TypedByteArray) response.getBody()).getBytes());
                            Log.d("RxJavaLeakFragment", RxJavaLeakFragment.this + ":" + content);
                        }
                    }, new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            throwable.printStackTrace();
                        }
                    });
            break;
    }
}

//用戶按返回按鈕關(guān)閉當(dāng)前界面,subscription執(zhí)行unsubscribe()方法

@Override
public void onDestroy() {
    super.onDestroy();
    if (subscription != null && !subscription.isUnsubscribed()) {
        subscription.unsubscribe();
        Log.d("RxJavaLeakFragment", "subscription.unsubscribe()");
    }
}

點(diǎn)擊網(wǎng)絡(luò)請(qǐng)求按鈕后朽肥,立馬關(guān)閉當(dāng)前界面禁筏,等待我們?cè)O(shè)定的超時(shí)時(shí)間10秒,測(cè)試輸出結(jié)果如下:

D/Retrofit: ---> HTTP GET http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000
D/Retrofit: Authorization: test
D/Retrofit: ---> END HTTP (no body)
I/DpmTcmClient: RegisterTcmMonitor from: com.android.okhttp.TcmIdleTimerMonitor
D/RxJavaLeakFragment: subscription.unsubscribe()
D/Retrofit: <--- HTTP 200 http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000 (10086ms)
D/Retrofit: : HTTP/1.1 200 OK
D/Retrofit: Content-Type: text/plain;charset=UTF-8
D/Retrofit: Date: Tue, 28 Mar 2017 11:06:07 GMT
D/Retrofit: Server: Apache-Coyote/1.1
D/Retrofit: Transfer-Encoding: chunked
D/Retrofit: X-Android-Received-Millis: 1490699154102
D/Retrofit: X-Android-Response-Source: NETWORK 200
D/Retrofit: X-Android-Selected-Protocol: http/1.1
D/Retrofit: X-Android-Sent-Millis: 1490699144047
D/Retrofit: "timeout success"
D/Retrofit: <--- END HTTP (17-byte body)
D/RxJavaLeakFragment: RxJavaLeakFragment{60678c5}:"timeout success"

最后一行日志道出了真相鞠呈,雖然我們關(guān)閉了界面融师,但是回調(diào)依然對(duì)Fragment有引用,所以當(dāng)服務(wù)器返回界面的時(shí)候蚁吝,依然可以打印Fragment的對(duì)象旱爆。

Rxjava 為我們提供onTerminateDetach操作符來(lái)解決這樣的問(wèn)題,在RxJava 1.1.2版本還沒(méi)有這個(gè)操作符的窘茁,在RxJava1.2.4是有這個(gè)操作符怀伦。

/**
* Nulls out references to the upstream producer and downstream Subscriber if
     * the sequence is terminated or downstream unsubscribes.
*/
@Experimental
public final Observable<T> onTerminateDetach() {
    return create(new OnSubscribeDetach<T>(this));
}

上面的注釋意思就是說(shuō) 當(dāng)執(zhí)行了反注冊(cè)u(píng)nsubscribes或者發(fā)送數(shù)據(jù)序列中斷了,解除上游生產(chǎn)者與下游訂閱者之間的引用山林。

所以onTerminateDetach操作符要和subscription.unsubscribe() 結(jié)合使用房待,因?yàn)椴粓?zhí)行subscription.unsubscribe()的話,onTerminateDetach就不會(huì)被觸發(fā)驼抹。

所以只要調(diào)用onTerminateDetach()即可桑孩,如下所示:

subscription = otherApi.testTimeout("10000")
    .onTerminateDetach()
    .subscribe(new Action1<Response>() {
        @Override
        public void call(Response response) {
            String content = new String(((TypedByteArray) response.getBody()).getBytes());
            Log.d("RxJavaLeakFragment", RxJavaLeakFragment.this + ":" + content);
        }
    }, new Action1<Throwable>() {
        @Override
        public void call(Throwable throwable) {
            throwable.printStackTrace();
        }
    });

測(cè)試結(jié)果如下 :

Retrofit: ---> HTTP GET http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000
Retrofit: Authorization: test
Retrofit: ---> END HTTP (no body)
DpmTcmClient: RegisterTcmMonitor from: com.android.okhttp.TcmIdleTimerMonitor
RxJavaLeakFragment: subscription.unsubscribe()
Retrofit: <--- HTTP 200 http://10.1.67.34:8080/android_mvvm_server/timeout?timeout=10000 (10165ms)
Retrofit: : HTTP/1.1 200 OK
Retrofit: Content-Type: text/plain;charset=UTF-8
Retrofit: Date: Tue, 28 Mar 2017 11:20:46 GMT
Retrofit: Server: Apache-Coyote/1.1
Retrofit: Transfer-Encoding: chunked
Retrofit: X-Android-Received-Millis: 1490700033441
Retrofit: X-Android-Response-Source: NETWORK 200
Retrofit: X-Android-Selected-Protocol: http/1.1
Retrofit: X-Android-Sent-Millis: 1490700023314
Retrofit: "timeout success"
Retrofit: <--- END HTTP (17-byte body)

從日志可以看出,雖然服務(wù)器返回了數(shù)據(jù)框冀,但是RxJava Action1的回調(diào)并沒(méi)有執(zhí)行流椒,內(nèi)存泄漏的問(wèn)題已經(jīng)解決了。


本文的例子放在github上 https://github.com/chiclaim/android-sample/tree/master/rxjava

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末明也,一起剝皮案震驚了整個(gè)濱河市宣虾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌温数,老刑警劉巖绣硝,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異撑刺,居然都是意外死亡鹉胖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甫菠,“玉大人败许,你說(shuō)我怎么就攤上這事∈缥担” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵愕撰,是天一觀的道長(zhǎng)刹衫。 經(jīng)常有香客問(wèn)我,道長(zhǎng)搞挣,這世上最難降的妖魔是什么带迟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮囱桨,結(jié)果婚禮上仓犬,老公的妹妹穿的比我還像新娘。我一直安慰自己舍肠,他們只是感情好搀继,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著翠语,像睡著了一般叽躯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肌括,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天点骑,我揣著相機(jī)與錄音,去河邊找鬼谍夭。 笑死黑滴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的紧索。 我是一名探鬼主播袁辈,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼齐板!你這毒婦竟也來(lái)了吵瞻?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤甘磨,失蹤者是張志新(化名)和其女友劉穎橡羞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體济舆,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卿泽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片签夭。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡齐邦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出第租,到底是詐尸還是另有隱情措拇,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布慎宾,位于F島的核電站丐吓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏趟据。R本人自食惡果不足惜券犁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汹碱。 院中可真熱鬧粘衬,春花似錦、人聲如沸咳促。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)跪腹。三九已至枷莉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尺迂,已是汗流浹背笤妙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留噪裕,地道東北人蹲盘。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像膳音,于是被迫代替她去往敵國(guó)和親召衔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評(píng)論 25 707
  • 作者寄語(yǔ) 很久之前就想寫(xiě)一個(gè)專題祭陷,專寫(xiě)Android開(kāi)發(fā)框架苍凛,專題的名字叫 XXX 從入門到放棄 ,沉淀了這么久兵志,...
    戴定康閱讀 7,625評(píng)論 13 85
  • http://blog.csdn.net/yyh352091626/article/details/5330472...
    奈何心善閱讀 3,558評(píng)論 0 0
  • 1 眼睛一睜醇蝴,便到了大三∠牒保回想過(guò)往的兩年悠栓,好像并沒(méi)有什么讓人印象深刻的事,這邊想著一定要提高自己,...
    檸檬的閱讀 248評(píng)論 0 0
  • 2016年過(guò)年時(shí)和家人朋友一起歡聚的場(chǎng)景還歷歷在目,2017年竟悄然而至癞志,令人猝不及防往枷。有時(shí)會(huì)心生怨恨,時(shí)間凄杯,你...
    咣當(dāng)球閱讀 229評(píng)論 0 0