美文网首页Android深入
RxJava(十三)RxJava导致Fragment Activ

RxJava(十三)RxJava导致Fragment Activ

作者: Chiclaim | 来源:发表于2017-07-03 19:30 被阅读396次

    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

    相关文章

      网友评论

        本文标题:RxJava(十三)RxJava导致Fragment Activ

        本文链接:https://www.haomeiwen.com/subject/poyzcxtx.html