Androidのレトロフィットで「インターネット接続なし」を処理するにはどうすればよいですか


119

インターネットに接続されていない状況に対応したいのですが。通常、私は実行します:

ConnectivityManager cm =
    (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
                  activeNetwork.isConnectedOrConnecting();

ここから)ネットワークにリクエストを送信する前に、インターネット接続がなかった場合にユーザーに通知します。

私が見たところによると、レトロフィットはこの状況を特に処理しません。インターネット接続がない場合はRetrofitError、理由としてタイムアウトが発生するだけです。

この種類のチェックをRetrofitを使用してすべてのHTTPリクエストに組み込みたい場合、どうすればよいですか?それとも私はそれをするべきですか?

ありがとう

アレックス


try-catchブロックを使用して、http接続のタイムアウト例外をキャッチできます。次に、インターネット接続の状態をユーザーに通知します。かなりではなく、代替ソリューション。
Tugrul 2013

8
はい、ただし、ソケットからの接続タイムアウトの取得を待つ代わりに、インターネット接続があるかどうかをAndroidで確認する方がはるかに高速です
AlexV

Androidクエリは「インターネットなし」を処理し、対応するエラーコードをすぐに返します。多分それをaQueryに置き換える価値はありますか?別の解決策は、ネットワーク変更のリスナーを作成することです。これにより、アプリはリクエストを送信する前にインターネットの可用性を認識します。
スタン

後付け2については、github.com
square /

回答:


62

私がやったことは、リクエストを実行する前に接続をチェックして例外をスローするカスタムRetrofitクライアントを作成することです。

public class ConnectivityAwareUrlClient implements Client {

    Logger log = LoggerFactory.getLogger(ConnectivityAwareUrlClient.class);

    public ConnectivityAwareUrlClient(Client wrappedClient, NetworkConnectivityManager ncm) {
        this.wrappedClient = wrappedClient;
        this.ncm = ncm;
    }

    Client wrappedClient;
    private NetworkConnectivityManager ncm;

    @Override
    public Response execute(Request request) throws IOException {
        if (!ncm.isConnected()) {
            log.debug("No connectivity %s ", request);
            throw new NoConnectivityException("No connectivity");
        }
        return wrappedClient.execute(request);
    }
}

構成時に使用します RestAdapter

RestAdapter.Builder().setEndpoint(serverHost)
                     .setClient(new ConnectivityAwareUrlClient(new OkHttpClient(), ...))

1
NetworkConnectivityManagerクラスはどこから来ましたか?カスタム?
NPike 14

はい、カスタム。それは基本的に質問からのコードだ
AlexV

2
OKClient()。BDWを使用する代わりに、OkHttpClientは機能しません。ありがとうございました。
Harshvardhan Trivedi 2014

ありがとう:-)。OkHttpを適切に使用して、回答を編集しました。
user1007522 2015

1
@MominAlAzizユーザーが定義した方法に応じてNoConnectivityException、あなたはどちらかそれを拡張することができますIOExceptionか、それは拡張作るRuntimeException
AlexV

45

改造以来、1.8.0これは廃止されました

retrofitError.isNetworkError()

あなたは使用する必要があります

if (retrofitError.getKind() == RetrofitError.Kind.NETWORK)
{

}

処理できるエラーには複数のタイプがあります。

NETWORK サーバーとの通信中にIOExceptionが発生しました(タイムアウト、接続なしなど)。

CONVERSION ボディの(非)シリアル化中に例外がスローされました。

HTTP 200以外のHTTPステータスコードがサーバーから受信されました(例:502、503など)。

UNEXPECTEDリクエストの実行中に内部エラーが発生しました。アプリケーションがクラッシュするように、この例外を再スローすることをお勧めします。


8
NoverPointerExceptionが発生する可能性を回避するためにequals、オーバー変数の使用は避け、常にCONSTANT.equals(variable)代わりに使用してください。または、この場合はさらに優れています。列挙型は==比較を受け入れるためerror.getKind() == RetrofitError.Kind.NETWORK、より良いアプローチかもしれません
MariusBudin

1
NPEやその他の構文制限/問題に飽き飽きしている場合は、JavaをKotlinに置き換えてください
Louis CAD

42

Retrofit 2では、OkHttp Interceptor実装を使用して、リクエストを送信する前にネットワーク接続を確認します。ネットワークがない場合は、必要に応じて例外をスローします。

これにより、Retrofitを実行する前に、ネットワーク接続の問題を明確に処理できます。

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Response;
import io.reactivex.Observable

public class ConnectivityInterceptor implements Interceptor {

    private boolean isNetworkActive;

    public ConnectivityInterceptor(Observable<Boolean> isNetworkActive) {
       isNetworkActive.subscribe(
               _isNetworkActive -> this.isNetworkActive = _isNetworkActive,
               _error -> Log.e("NetworkActive error " + _error.getMessage()));
    }

    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        if (!isNetworkActive) {
            throw new NoConnectivityException();
        }
        else {
            Response response = chain.proceed(chain.request());
            return response;
        }
    }
}

public class NoConnectivityException extends IOException {

    @Override
    public String getMessage() {
        return "No network available, please check your WiFi or Data connection";
    }
}

3
飛行機モードをオンにしてからオフにすると欠陥があり、一度観測変数を設定するのは観測値が役に立たなくなるためです。
Oliver Dixon

3
だからあなたはここにあるOkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))べきものを作っていますか?
ルミット2017

3
そのコードを含めて、人々が全体像を把握できるようにしてください。
Oliver Dixon

1
@Kevin接続が利用可能になったときに更新されるようにするにはどうすればよいですか?
ルミット

3
これは受け入れられる答えになるはずです。その他の例: migapro.com/detect-offline-error-in-retrofit-2
j2emanue

35

@AlexVインターネット接続がない場合、RetrofitErrorに理由としてタイムアウト(getCause()が呼び出されたときのSocketTimeOutException)が含まれていることを確認しますか?

私が知る限り、インターネット接続がない場合、RetrofitErrorには原因としてConnectionExceptionが含まれています。

ErrorHandlerを実装する場合、次のようなことができます。

public class RetrofitErrorHandler implements ErrorHandler {

    @Override
    public Throwable handleError(RetrofitError cause) {
        if (cause.isNetworkError()) {
            if (cause.getCause() instanceof SocketTimeoutException) {
                return new MyConnectionTimeoutException();
            } else {
                return new MyNoConnectionException();
            }
        } else {
            [... do whatever you want if it's not a network error ...]  
        }
    }

}

1
使用できるRetrofitソースには、提供されているErrorHandlerがあります。自分でエラーを処理しない場合、Retrofitは[java.net.UnknownHostException:Unable to resolve host "example.com":No address associated with hostname]
Codeversed

1
@Codeversedだから、isNetworkErrorホストエラーを解決できないことを取り除くのですか?
2014年

2
これをクライアントのインターフェースにどのように実装しますか?つまり、このクラスを何に接続しますか?
FRR 2014年

23
cause.isNetworkError()は非推奨です:使用error.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse '19

6

後付け用1

あなたが取得するとThrowable、あなたのHTTPリクエストからエラーを、あなたはそれがこのような方法でネットワークエラーであるかどうかを検出することができます:

String getErrorMessage(Throwable e) {
    RetrofitError retrofitError;
    if (e instanceof RetrofitError) {
        retrofitError = ((RetrofitError) e);
        if (retrofitError.getKind() == RetrofitError.Kind.NETWORK) {
            return "Network is down!";
        }
    }
}

5

これを行うだけで、次のような問題についても通知されます

UnknownHostException

SocketTimeoutException

その他。

 @Override public void onFailure(Call<List<BrokenGitHubRepo>> call, Throwable t) {  
if (t instanceof IOException) {
    Toast.makeText(ErrorHandlingActivity.this, "this is an actual network failure :( inform the user and possibly retry", Toast.LENGTH_SHORT).show();
    // logging probably not necessary
}
else {
    Toast.makeText(ErrorHandlingActivity.this, "conversion issue! big problems :(", Toast.LENGTH_SHORT).show();
    // todo log to some central bug tracking service
} }

1

このコードを使用できます

Response.java

import com.google.gson.annotations.SerializedName;

/**
 * Created by hackro on 19/01/17.
 */

public class Response {
    @SerializedName("status")
    public String status;

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    @SuppressWarnings({"unused", "used by Retrofit"})
    public Response() {
    }

    public Response(String status) {
        this.status = status;
    }
}

NetworkError.java

import android.text.TextUtils;

import com.google.gson.Gson;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import retrofit2.adapter.rxjava.HttpException;

import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;

/**
 * Created by hackro on 19/01/17.
 */

public class NetworkError extends Throwable {
    public static final String DEFAULT_ERROR_MESSAGE = "Please try again.";
    public static final String NETWORK_ERROR_MESSAGE = "No Internet Connection!";
    private static final String ERROR_MESSAGE_HEADER = "Error Message";
    private final Throwable error;

    public NetworkError(Throwable e) {
        super(e);
        this.error = e;
    }

    public String getMessage() {
        return error.getMessage();
    }

    public boolean isAuthFailure() {
        return error instanceof HttpException &&
                ((HttpException) error).code() == HTTP_UNAUTHORIZED;
    }

    public boolean isResponseNull() {
        return error instanceof HttpException && ((HttpException) error).response() == null;
    }

    public String getAppErrorMessage() {
        if (this.error instanceof IOException) return NETWORK_ERROR_MESSAGE;
        if (!(this.error instanceof HttpException)) return DEFAULT_ERROR_MESSAGE;
        retrofit2.Response<?> response = ((HttpException) this.error).response();
        if (response != null) {
            String status = getJsonStringFromResponse(response);
            if (!TextUtils.isEmpty(status)) return status;

            Map<String, List<String>> headers = response.headers().toMultimap();
            if (headers.containsKey(ERROR_MESSAGE_HEADER))
                return headers.get(ERROR_MESSAGE_HEADER).get(0);
        }

        return DEFAULT_ERROR_MESSAGE;
    }

    protected String getJsonStringFromResponse(final retrofit2.Response<?> response) {
        try {
            String jsonString = response.errorBody().string();
            Response errorResponse = new Gson().fromJson(jsonString, Response.class);
            return errorResponse.status;
        } catch (Exception e) {
            return null;
        }
    }

    public Throwable getError() {
        return error;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        NetworkError that = (NetworkError) o;

        return error != null ? error.equals(that.error) : that.error == null;

    }

    @Override
    public int hashCode() {
        return error != null ? error.hashCode() : 0;
    }
}

メソッドでの実装

        @Override
        public void onCompleted() {
            super.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            super.onError(e);
            networkError.setError(e);
            Log.e("Error:",networkError.getAppErrorMessage());
        }

        @Override
        public void onNext(Object obj) {   super.onNext(obj);        
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.