Robolectric + rxJava + retrofit 第二次调用会抛出 java.io.InterruptedIOException。

7

我正在开发一款安卓应用。我正在使用retrofit (with OkClient)进行api请求,并使用Robolectric进行测试。我的api看起来像这样:

@GET("/v1/book/{bookId}") Observable<Book> getBook(@Path("bookId") int bookId);

为了使用Robolectric,我强制API调用是同步的。restAdapter构建器如下:

RestAdapter.Builder builder = new RestAdapter.Builder().setEndpoint(environment.getServerEndpoint())
                        .setClient(new OkClient(client))
                        .setExecutors(new ImmediateExecutor(), null)
                        .setErrorHandler(new ErrorHandler())
                        .setRequestInterceptor(new RequestInterceptor() {
                            @Override
                            public void intercept(RequestFacade request) {
                                // Always ask for JSON data
                                request.addHeader("Accept", "application/json");
                                request.addHeader("Content-Type", "application/json");               
                            }
                        });

public class ImmediateExecutor implements Executor {

        @Override public void execute(Runnable command) {
            command.run();
        }
    }

我有一个简单的测试,看起来像这样:

API.getBook(1).subscribe();
API.getBook(2).subscribe();

使用构建器创建Restadapter,并与其一起创建API对象(restadapter.create(...))。我将其省略,因为这是微不足道的。

第一个没有问题,但第二个应该是相同的,却会抛出异常:

java.io.InterruptedIOException
    at okio.Timeout.throwIfReached(Timeout.java:146)
    at okio.Okio$1.write(Okio.java:75)
    at okio.AsyncTimeout$1.write(AsyncTimeout.java:155)
    at okio.RealBufferedSink.flush(RealBufferedSink.java:201)
    at com.squareup.okhttp.internal.http.HttpConnection.flush(HttpConnection.java:140)
    at com.squareup.okhttp.internal.http.HttpTransport.finishRequest(HttpTransport.java:52)
    at com.squareup.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:828)
    at com.squareup.okhttp.internal.http.HttpEngine.access$200(HttpEngine.java:95)
    at com.squareup.okhttp.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:823)
    at com.carmacarpool.carmaridepool.rest.CarmaHttpClientFactory$NetworkLoggingInterceptor.intercept(CarmaHttpClientFactory.java:77)
    at com.squareup.okhttp.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:803)
    at com.squareup.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:684)
    at com.squareup.okhttp.Call.getResponse(Call.java:272)
    at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:228)
    at com.carmacarpool.carmaridepool.rest.CarmaHttpClientFactory$LoggingInterceptor.intercept(CarmaHttpClientFactory.java:53)
    at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:225)
    at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:199)
    at com.squareup.okhttp.Call.execute(Call.java:79)
    at retrofit.client.OkClient.execute(OkClient.java:53)
    at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:326)
    at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
    at retrofit.RestAdapter$RestHandler$1.invoke(RestAdapter.java:265)
    at retrofit.RxSupport$2.run(RxSupport.java:55)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at com.carmacarpool.carmaridepool.testutils.ShadowUICarmaRESTClassFactory$ImmediateExecutor.execute(ShadowUICarmaRESTClassFactory.java:91)
    at retrofit.RxSupport$1.call(RxSupport.java:42)
    at retrofit.RxSupport$1.call(RxSupport.java:32)
    at rx.Observable.subscribe(Observable.java:7393)
    at rx.Observable.subscribe(Observable.java:7083)
    at com.carmacarpool.carmaridepool.CorridorTest.test(CorridorTest.java:99)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:265)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:205)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:173)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:64)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:106)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)
    at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)

我有一个只输出日志的网络拦截器,它工作正常。我能够访问服务器日志,但请求甚至没有到达服务器。
有人知道问题可能是什么吗?似乎由于某种未知原因,线程被终止了?
谢谢。
编辑:如果在onNext函数(subscribe的第一个参数)中执行第二个请求,它会起作用。一切都是同步的,和预期一样。
解决方案:经过多次尝试,我找到了一个解决方案。问题似乎来自Okio。显然,有一个缓冲区写入响应(或类似的东西,我几周前解决了这个问题,我记得不到100%)。这个缓冲区在第二个请求中间被关闭,这就是导致错误的原因。
为了解决这个问题,我将请求包装在try / catch块中。如果发生IOException,则进行重试。我最多重试5次(以避免无限循环)。
代码如下:
    try {
        // Perform the request
        return chain.proceed(request);
    } catch (IOException e) {

        // Retry again if we haven't tried at least REQUEST_RETRIES times
        if (iteration < REQUEST_RETRIES) {
            return performRequest(chain, ++iteration);
        }

        // Otherwise, save the exception and throw it later
        exception = e;
    }

这个链对象来源于一个OkHttpClient拦截器:

OkHttpClient client = new OkHttpClient(); client.interceptors().add(new CustomInterceptor());

private class CustomInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { ...

希望这对你有所帮助。


2
关于你的测试,我只想提出两点建议 - 为什么不在没有真实网络的情况下进行测试呢? - Eugen Martynov
这也可能有所帮助 - https://groups.google.com/forum/#!topic/robolectric/-HVhS3rnU54 - Eugen Martynov
1个回答

2

我认为你在测试时为了使observables同步运行而做了太多的工作。相反,你只需要在测试期间

 Book book = retrofitAPI.getBook(someId).toBlocking.first()

这将同步执行observable。Rx库中我最喜欢的部分之一。

这对我不起作用,因为在我的测试中,我创建了一个活动,是活动调用执行请求的服务。所以我不能使用toBlocking().first()。我在测试服务本身时这样做。 - Antonio C G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接