RxJava(十三)RxJava导致Fragment Activ
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