17

First of all, I have read so many questions regarding my question but it never gives me the solution. Here are some of the questions which I read regarding my question.

I also read this article regarding my question but it also never provide me the solution.

Problem:

I am using Okhhtp3 library in my application for Web Services. It's working fine but when the internet connection slow or unreliable connection it's stuck and never times out or never calls the timeout exception or failure method.

Here is the client code:

OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .retryOnConnectionFailure(false)
            .build();

How can get the timeout exception or called failure method after 20 secs?

Please help me. Thanks

Tanveer Munir
  • 1,956
  • 1
  • 12
  • 27
  • 1
    Have you tried setting `callTimeoutMillis` to 20 secs? It isn't well documented but looking at the source it uses Okio timeout to abort an operation: see https://github.com/square/okhttp/blob/4981f92e50abd929d386d995c43c89d996af63bd/okhttp/src/main/java/okhttp3/internal/connection/Transmitter.java#L84 and https://github.com/square/okio/blob/master/okio/jvm/src/main/java/okio/Timeout.kt#L56 – Trevor Halvorson Mar 12 '19 at 18:09
  • Found docs here: https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#callTimeout-long-java.util.concurrent.TimeUnit- > The call timeout spans the entire call: resolving DNS, connecting, writing the request body, server processing, and reading the response body. If the call requires redirects or retries all must complete within one timeout period. – Trevor Halvorson Mar 12 '19 at 18:18
  • @TrevorHalvorson overall call span if I calculate according to my share code is 1 Minute but call continuously running until unless I kill the app. – Tanveer Munir Mar 13 '19 at 06:17
  • @TrevorHalvorson how can I set `callTimeoutMillis` for the client there is no method provided. – Tanveer Munir Mar 13 '19 at 06:23
  • The method to call on the OkHttpClient.Builder is `callTimeout`. You can use it just like the other timeout methods (`connectTimeout`, etc.) you are currently using. see: https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#callTimeout-long-java.util.concurrent.TimeUnit- – Trevor Halvorson Mar 13 '19 at 16:46
  • @TrevorHalvorson this not available for during client builder. Available methods I already set which you can show in the above code. – Tanveer Munir Mar 14 '19 at 07:09
  • @TanveerMunir if you are using `okhttp3` `callTimeout` is definitely available during build time. – shadowsheep Mar 14 '19 at 17:19
  • @shadowsheep I'm using `com.squareup.okhttp3:okhttp:3.11.0` that's why it's not available in that so now I changed now hope it's worked for me. – Tanveer Munir Mar 15 '19 at 06:10

2 Answers2

14

As pointed out by Trevor Halvorson, you could set callTimeout during client builder, in this way:

OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(20, TimeUnit.SECONDS)
            .callTimeout(20, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .retryOnConnectionFailure(false)
            .build();

I've personally tested in a dummy project using version 3.14.0 of okhttp3:

implementation 'com.squareup.okhttp3:okhttp:3.14.0'

And setting a timout of 5 seconds and my emulator connection to GPRS and Poor connectivity I get

java.net.SocketExcpetion: Socket closed: timeout

enter image description here

This is my full dummy activity:

package com.example.shadowsheep.myapplication;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.TimeUnit;

import androidx.appcompat.app.AppCompatActivity;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView helloTextView = findViewById(R.id.helloTextView);

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(5, TimeUnit.SECONDS)
                .callTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .retryOnConnectionFailure(false)
                .build();

        Request request = new Request.Builder()
                .url("https://www.versionestabile.it/blog")
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                Log.d("OKHTTP3", e.getMessage());
                // You get this failure
                runOnUiThread(() -> helloTextView.setText("TIMEOUT - FAILURE -> " + e.getMessage()));
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                try {
                    final String _body = response.body().string();
                    Log.d("OKHTTP3", _body);
                    runOnUiThread(() -> {
                        helloTextView.setText(_body);
                    });
                } catch (InterruptedIOException e) {
                    runOnUiThread(() -> {
                        // Or this exception depending when timeout is reached
                        helloTextView.setText("TIMEOUT EXCEPTION->"+ e.getCause() + ": " + e.getMessage());
                    });
                }
            }
        });
    }
}

I'll give you also my app build.gradle file.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.shadowsheep.myapplication"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha03'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.2-alpha02'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha02'

    implementation 'com.squareup.okhttp3:okhttp:3.14.0'
}
shadowsheep
  • 14,048
  • 3
  • 67
  • 77
  • I'm using `com.squareup.okhttp3:okhttp:3.11.0` that's why it's not available in that so now I changed now hope it's worked for me. – Tanveer Munir Mar 15 '19 at 06:11
  • @TanveerMunir It should work ;-]. It works for me. Let me know. – shadowsheep Mar 15 '19 at 06:47
  • @TanveerMunir Have you tried my code? You'll see that you get an `InterruptedIOException`. If my code works, yours should work too. Try it, then if you wanna give me a working code of yours to try, I'm happy to test it. – shadowsheep Mar 15 '19 at 08:36
  • 1
    I am using a single client for my whole app I'm getting stuck when the connection is slow and data expired. I don't why. I'm using `calltimeout` but same problem – Tanveer Munir Mar 15 '19 at 08:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/190072/discussion-between-shadowsheep-and-tanveer-munir). – shadowsheep Mar 15 '19 at 08:50
  • actually, I'm not using try/catch in the response that's why it's getting stuck my App and exception coming in response and I supposed that It will come in failure. So now it's working fine. I test the whole App after that I will mark the answer and assign the bounty to you. – Tanveer Munir Mar 15 '19 at 11:01
1

My test case won't work too, it takes 30+ seconds every time:

  • Android Emulator Nexus 4 (Android Version 4.1.2)
  • Windows 7 With no internet connection (Ensure emulator with no internet connection)

gradle:

implementation 'com.squareup.okhttp3:okhttp:3.12.2'

defaultConfig {
    minSdkVersion 16
    targetSdkVersion 26
}
compileSdkVersion 27
buildToolsVersion '28.0.3'

java:

public static class CheckIpAsyncTask extends util.android.os.AsyncTask<Void, Void, Void> {

@Override
    protected Void doInBackground(Void... voids) {
        try {
        Logger.i(TAG, "API::FBD::checkIpInfo, API_IPINFO = " + API_IPINFO);
        if (BuildConfig.DEBUG) {
            //ThreadUtil.sleep(10 * TimeUtil.SECOND);
        }

        Request req = new Request.Builder()
                .get()
                .url(API_IPINFO)
                .build();
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(2, TimeUnit.SECONDS)
                .callTimeout(2, TimeUnit.SECONDS)
                .readTimeout(2, TimeUnit.SECONDS)
                .writeTimeout(2, TimeUnit.SECONDS)
                .build();
        Response response = client.newCall(req).execute();

        mHttpCode = response.code();
        if (mHttpCode == HttpURLConnection.HTTP_OK) {
            boolean appConnected = ServerHelper.checkAppConnected();
            if (!appConnected) {
                JSONObject res = new JSONObject(response.body().string().trim());
                mCountry = res.getString("country").toLowerCase();
                mIp = res.getString("ip").toLowerCase();
                Logger.i(TAG, "API::FBD::checkIpInfo, res = " + res);

                PreferenceHelper.get(mContext).setIpInfoApiCountry(mCountry);
                PreferenceHelper.get(mContext).setIpInfoApiHost(mIp);
            }
        }
    } catch (java.io.InterruptedIOException interruptedIOException) {
        Logger.e(TAG, "API::FBD::checkIpInfo, InterruptedIOException=" + interruptedIOException);
    } catch (Throwable ignored) {
        Logger.e(TAG, "API::FBD::checkIpInfo, Throwable=" + ignored);
    }
    return null;
}

logs:

04-04 06:25:06.940 4492-4645/? I/Toolbox: [PLACE] at com.xy.ui.FbdUtil$CheckIpAsyncTask.doInBackground(FbdUtil.java:98)
                                          [FbdUtil]API::FBD::checkIpInfo, API_IPINFO = http://api.z.com:30070/servers/ipinfo
04-04 06:25:34.960 4492-4645/? E/Toolbox: [PLACE] at com.xy.ui.FbdUtil$CheckIpAsyncTask.doInBackground(FbdUtil.java:129)
                                          [FbdUtil]API::FBD::checkIpInfo, InterruptedIOException=java.io.InterruptedIOException: timeout

logs on Emulator Nexus 6 (Android version 8.0):

04-04 06:52:44.696 2624-3950/? I/Toolbox: [PLACE] at com.xy.ui.FbdUtil$CheckIpAsyncTask.doInBackground(FbdUtil.java:98)
                                          [FbdUtil]API::FBD::checkIpInfo, API_IPINFO = http://api.z.com:30070/servers/ipinfo
04-04 06:53:59.886 2624-3175/? E/Toolbox: [PLACE] at com.xy.ui.ForbiddenUtil$CheckIpAsyncTask.doInBackground(FbdUtil.java:129)
                                          [FbdUtil]API::FBD::checkIpInfo, InterruptedIOException=java.io.InterruptedIOException: timeout

Finally, Dmitry Zenovich's solution in 'Question 6' works for me:

.dns(hostname -> Single.fromCallable(() -> Arrays.asList(InetAddress.getAllByName(hostname)))
                            .timeout(2, TimeUnit.SECONDS)
                            .subscribeOn(Schedulers.io())
                            .observeOn(Schedulers.computation())
                            .onErrorReturnItem(new ArrayList<>())
                            .blockingGet())
thecr0w
  • 2,148
  • 4
  • 33
  • 59