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

15 篇文章 42 订阅

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 线程的自由切换


一般我们在实际的开发中,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 的回调并没有执行,内存泄漏的问题已经解决了。


如果你觉得本文帮助到你,给我个关注和赞呗!

另外,我为 Android 程序员编写了一份:超详细的 Android 程序员所需要的技术栈思维导图

如果有需要可以移步我的 GitHub -> AndroidAll,里面包含了最全的目录和对应知识点链接,帮你扫除 Android 知识点盲区。 由于篇幅原因只展示了 Android 思维导图:
超详细的Android技术栈

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiclaim

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值