Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NetworkOnMainThread RxJava + Retrofit + Lollipop+

I'm getting reports of a NetworkOnMainThread exception on Lollipop+ when running an https api call using Retrofit and RxAndroid.

I have isolated the code and created the following example that still fails.

Here's the build.gradle:

apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'

android {
  compileSdkVersion 23
  buildToolsVersion "23.0.1"

  defaultConfig {
    applicationId "com.example.bug"
    minSdkVersion 9
    targetSdkVersion 23
    versionCode 1
    versionName "1.0"
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt')
    }
  }
  lintOptions {
    abortOnError false
  }
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  testCompile 'junit:junit:4.12'
  compile 'com.android.support:appcompat-v7:23.0.1'

  compile 'com.squareup.okhttp:okhttp:2.5.0'
  compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0'
  compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
  compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
  compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
  compile 'io.reactivex:rxandroid:1.0.1'
}

Here's the sole activity:

public class HomeActivity extends Activity {

  private static final String URL_BASE = "https://some.https.api.com/";
  private static final String ENDPOINT = "some/endpoint";

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.setConnectTimeout(15, TimeUnit.SECONDS);
    okHttpClient.setReadTimeout(15, TimeUnit.SECONDS);

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(URL_BASE)
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create(new Gson()))
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        .build();

    MyApi mService = retrofit.create(MyApi.class);

    setContentView(R.layout.home_activity);
    Button button = (Button) findViewById(R.id.button);
    button.setOnClickListener(v->
        mService.whatever(new ParamObject())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                response -> Log.d("BUG", response.status),
                error -> Log.d("BUG", error.toString()))
    );
  }

  interface MyApi {

    @POST(ENDPOINT)
    Observable<ResponseObject> whatever( @Body ParamObject requirements);
  }

  class ResponseObject {
    public String status;
  }

  class ParamObject {
  }
}

Here's the exception stacktrace:

E/AndroidRuntime(28345): FATAL EXCEPTION: main
E/AndroidRuntime(28345): Process: com.example.bug, PID: 28345
E/AndroidRuntime(28345): java.lang.IllegalStateException: Fatal Exception thrown on Scheduler.Worker thread.
E/AndroidRuntime(28345):    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:62)
E/AndroidRuntime(28345):    at android.os.Handler.handleCallback(Handler.java:739)
E/AndroidRuntime(28345):    at android.os.Handler.dispatchMessage(Handler.java:95)
E/AndroidRuntime(28345):    at android.os.Looper.loop(Looper.java:135)
E/AndroidRuntime(28345):    at android.app.ActivityThread.main(ActivityThread.java:5221)
E/AndroidRuntime(28345):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(28345):    at java.lang.reflect.Method.invoke(Method.java:372)
E/AndroidRuntime(28345):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
E/AndroidRuntime(28345):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
E/AndroidRuntime(28345): Caused by: android.os.NetworkOnMainThreadException
E/AndroidRuntime(28345):    at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1147)
E/AndroidRuntime(28345):    at com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream.write(OpenSSLSocketImpl.java:724)
E/AndroidRuntime(28345):    at okio.Okio$1.write(Okio.java:80)
E/AndroidRuntime(28345):    at okio.AsyncTimeout$1.write(AsyncTimeout.java:155)
E/AndroidRuntime(28345):    at okio.RealBufferedSink.flush(RealBufferedSink.java:221)
E/AndroidRuntime(28345):    at com.squareup.okhttp.internal.framed.Http2$Writer.rstStream(Http2.java:475)
E/AndroidRuntime(28345):    at com.squareup.okhttp.internal.framed.FramedConnection.writeSynReset(FramedConnection.java:356)
E/AndroidRuntime(28345):    at com.squareup.okhttp.internal.framed.FramedStream.close(FramedStream.java:222)
E/AndroidRuntime(28345):    at com.squareup.okhttp.internal.http.FramedTransport.disconnect(FramedTransport.java:215)
E/AndroidRuntime(28345):    at com.squareup.okhttp.internal.http.HttpEngine.disconnect(HttpEngine.java:573)
E/AndroidRuntime(28345):    at com.squareup.okhttp.Call.cancel(Call.java:122)
E/AndroidRuntime(28345):    at retrofit.OkHttpCall.cancel(OkHttpCall.java:162)
E/AndroidRuntime(28345):    at retrofit.RxJavaCallAdapterFactory$CallOnSubscribe$1.call(RxJavaCallAdapterFactory.java:102)
E/AndroidRuntime(28345):    at rx.subscriptions.BooleanSubscription.unsubscribe(BooleanSubscription.java:72)
E/AndroidRuntime(28345):    at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
E/AndroidRuntime(28345):    at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
E/AndroidRuntime(28345):    at rx.Subscriber.unsubscribe(Subscriber.java:98)
E/AndroidRuntime(28345):    at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
E/AndroidRuntime(28345):    at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
E/AndroidRuntime(28345):    at rx.Subscriber.unsubscribe(Subscriber.java:98)
E/AndroidRuntime(28345):    at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
E/AndroidRuntime(28345):    at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
E/AndroidRuntime(28345):    at rx.Subscriber.unsubscribe(Subscriber.java:98)
E/AndroidRuntime(28345):    at rx.observers.SafeSubscriber.onCompleted(SafeSubscriber.java:90)
E/AndroidRuntime(28345):    at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.java:201)
E/AndroidRuntime(28345):    at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$2.call(OperatorObserveOn.java:170)
E/AndroidRuntime(28345):    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
E/AndroidRuntime(28345):    ... 8 more

A few things:

  • This code example works just fine in Android 4.x
  • I have tried this example (with minor changes) against the GitHub API and it works just fine.
  • The API is running on Google's app engine.
  • Note that the observable is being subscribe on Schedulers.io() and observed on AndroidSchedulers.mainThread()

Any ideas?

Edit

  • After taking a closer look at the stacktrace I figure there was something about http2. Although not the same issue reported here, the following line to restrict the OkHttp available protocols made it work again.

    okHttpClient.setProtocols(Collections.singletonList(Protocol.HTTP_1_1));

like image 542
Robert Estivill Avatar asked Oct 21 '15 18:10

Robert Estivill


2 Answers

It's a known bug in OkHttp that canceling a call does I/O on the canceling thread. Will be fixed in a future release.

like image 186
Jesse Wilson Avatar answered Nov 19 '22 03:11

Jesse Wilson


Until this issue has been fixed add unsubscribeOn(Schedulers.io()) to the RX call chain. See this commit for an example if you need some more context.

like image 38
ubuntudroid Avatar answered Nov 19 '22 04:11

ubuntudroid