Search code examples
androidssl-certificateokhttpnetworkonmainthread

NetworkOnMainThreadException when using SSLContext - Android Studio


I am developing an App that should connect to a webservice. For developing purposes I used simple http protocoll. This worked fine.

public Response sendValidRequest(Context context, String encodedAccountData) throws ExecutionException, InterruptedException {

    OkHttpClient client = new OkHttpClient().newBuilder()
            .build();
    RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
            .addFormDataPart(ID, stringParameter)
            .build();
    Request request = new Request.Builder()
            .url(URL)
            .method(POST, body)
            .addHeader(AUTH_HEADER, encodedAccountData)
            .build();
    CompletableFuture<Response> future = new CompletableFuture<>();
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
            future.complete(null);
        }
        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) {
            future.complete(response);
        }
    });
    return future.get();
}

public ValidResponse getValidResponse(Context context, String encodedAccountData) {
    Response response;
    try {
        response = sendValidRequest(context, encodedAccountData);
    } catch (ExecutionException | InterruptedException e) {
        //handle this case
    }

    if(response == null || response.body() == null) {
        //handle this case
    }
    try {
        final String myResponse = response.body().string();
        JSONObject jsonObject = new JSONObject(myResponse);
        //Do something here depending on the content of the response.
    } catch (JSONException | IOException e) {
        //handle this case
    }

I then created some certificates for the webservice in order to use https. Tests in Postman (with https) worked fine, no problem.

I added to the manifest: <application android:networkSecurityConfig="@xml/network_security_config">...</application> Netwok_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="@raw/server_certificate"/>
        </trust-anchors>
    </base-config>
</network-security-config>

and also the server_certificate.crt as well.

Then I added to following to the Code:

public Response sendValidRequest(Context context, String encodedAccountData) throws ExecutionException, InterruptedException {
    OkHttpClient client = new OkHttpClient().newBuilder()
        .sslSocketFactory(getSSLSocketFactory(), getTrustManager())
        .hostnameVerifier((hostname, session)->true)
        .build();
        .... //no further changes.

private SSLSocketFactory getSSLSocketFactory() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] {
                new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) {}

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new SecureRandom());

        return sslContext.getSocketFactory();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

private X509TrustManager getTrustManager() {
    return new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {}

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {}

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    };
}

This does call the webservice and gets a result back. However, I now get a NetworkOnMainThreadException in the line final String myResponse = response.body().string(); I do not know why this is happening now. It didn't happen when using http. When I reverse the changes the Exception is gone as well.

Can someone explain to me, why I get the exception now? Why does it work with a http-request?

I have tried finding the answer, but with no success.

For now I changed the ThreadPolicy, and it works, but I don't think this is the best practice for this. In the constructor of the class:

StrictMode.setThreadPolicy(policy);```


Solution

  • Based in the documentation of the Okhttp library : https://square.github.io/okhttp/3.x/okhttp/okhttp3/ResponseBody.html

    The response body should managed in the same callback as this specified example :

       Call call = client.newCall(request);
       call.enqueue(new Callback() {
         public void onResponse(Call call, Response response) throws IOException {
           try (ResponseBody responseBody = response.body()) {
             ... // Use the response.
           }
         }
         public void onFailure(Call call, IOException e) {
           ... // Handle the failure.
         }
       });
    

    And they mention that These examples will not work if you're consuming the response body on another thread. , so make sure the managing of the response was not done on the external Thread.

    And last remarks about using the StrictMode (StrictMode is a developer tool which detects things you might be doing by accident and brings them to your attention so you can fix them.) It's should be used in DEV mode means you can use only by putting it in some dev condtion e.g

    if(DEBUG_MODE) {
      StrictMode.setThreadPolicy(policy)
    }