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


一般我们在实际的开发中,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/AndroidAll

猜你喜欢

转载自blog.csdn.net/johnny901114/article/details/67640594