RxJava(十三)RxJava导致Fragment Activity内存泄漏问题

RxJava系列文章目录导读:

一、RxJava create操作符的用法和源码分析
二、RxJava map操作符用法详解
三、RxJava flatMap操作符用法详解
四、RxJava concatMap操作符用法详解
五、RxJava onErrorResumeNext操作符实现app与服务器间token机制
六、RxJava retryWhen操作符实现错误重试机制
七、RxJava 使用debounce操作符优化app搜索功能
八、RxJava concat操作处理多数据源
九、RxJava zip操作符在Android中的实际使用场景
十、RxJava switchIfEmpty操作符实现Android检查本地缓存逻辑判断
十一、RxJava defer操作符实现代码支持链式调用
十二、combineLatest操作符的高级使用
十三、RxJava导致Fragment Activity内存泄漏问题
十四、interval、takeWhile操作符实现获取验证码功能


一般我们在实际的开发中,RxJava和Retrofit2结合使用的比较多,因为他们可以无缝集成,例如我们下面的一个网络请求:

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();
                }
            });
}

上面的代码非常简单,用过Retrofit2和RxJava一眼就看明白了,我们知道还需要在界面destroy的时候,把subscription反注销掉,避免内存泄漏,如:

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

但是这真的能避免内存泄漏吗?下面我们来做一个实验。

操作步骤:我们进入某个界面(Activity、Fragment),点击按钮请求网络,故意让该网络请求执行10秒,在网络返回前,我们关闭界面。

后端代码如下:


//如果用户传进来的timeout>0则当前线程休眠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的代码如下:

//点击按钮请求网络,在成功回调方法里输出服务器返回的结果和当前Fragment的对象

@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;
    }
}

//用户按返回按钮关闭当前界面,subscription执行unsubscribe()方法

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

点击网络请求按钮后,立马关闭当前界面,等待我们设定的超时时间10秒,测试输出结果如下:

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"

最后一行日志道出了真相,虽然我们关闭了界面,但是回调依然对Fragment有引用,所以当服务器返回界面的时候,依然可以打印Fragment的对象。

Rxjava 为我们提供onTerminateDetach操作符来解决这样的问题,在RxJava 1.1.2版本还没有这个操作符的,在RxJava1.2.4是有这个操作符。

/**
* 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));
}

上面的注释意思就是说 当执行了反注册unsubscribes或者发送数据序列中断了,解除上游生产者与下游订阅者之间的引用。

所以onTerminateDetach操作符要和subscription.unsubscribe() 结合使用,因为不执行subscription.unsubscribe()的话,onTerminateDetach就不会被触发。

所以只要调用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();
        }
    });

测试结果如下 :

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)

从日志可以看出,虽然服务器返回了数据,但是RxJava Action1的回调并没有执行,内存泄漏的问题已经解决了。


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,478评论 25 707
  • 作者寄语 很久之前就想写一个专题,专写Android开发框架,专题的名字叫 XXX 从入门到放弃 ,沉淀了这么久,...
    戴定康阅读 7,613评论 13 85
  • http://blog.csdn.net/yyh352091626/article/details/5330472...
    奈何心善阅读 3,544评论 0 0
  • 1 眼睛一睁,便到了大三。回想过往的两年,好像并没有什么让人印象深刻的事,这边想着一定要提高自己,...
    柠檬的阅读 247评论 0 0
  • 2016年过年时和家人朋友一起欢聚的场景还历历在目,2017年竟悄然而至,令人猝不及防。有时会心生怨恨,时间,你...
    咣当球阅读 222评论 0 0