ฉันจะจัดการ“ ไม่มีการเชื่อมต่ออินเทอร์เน็ต” ด้วย Retrofit บน Android อย่างไร


119

ฉันต้องการจัดการกับสถานการณ์เมื่อไม่มีการเชื่อมต่ออินเทอร์เน็ต โดยปกติฉันจะวิ่ง:

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

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

(จากที่นี่ ) ก่อนส่งคำขอไปยังเครือข่ายและแจ้งให้ผู้ใช้ทราบหากไม่มีการเชื่อมต่ออินเทอร์เน็ต

จากสิ่งที่ฉันเห็นว่า Retrofit ไม่ได้จัดการกับสถานการณ์นี้โดยเฉพาะ หากไม่มีการเชื่อมต่ออินเทอร์เน็ตฉันจะได้รับRetrofitErrorหมดเวลาเป็นเหตุผล

หากฉันต้องการรวมการตรวจสอบประเภทนี้ในคำขอ HTTP ทุกรายการด้วย Retrofit ฉันควรทำอย่างไร หรือควรทำเลย.

ขอบคุณ

อเล็กซ์


คุณอาจใช้ try-catch block เพื่อรับข้อยกเว้นการหมดเวลาสำหรับการเชื่อมต่อ http จากนั้นบอกผู้ใช้เกี่ยวกับสถานะการเชื่อมต่ออินเทอร์เน็ต ไม่สวย แต่เป็นทางเลือกอื่น
Tugrul

8
ใช่ แต่จะเร็วกว่ามากในการตรวจสอบกับ Android หากมีการเชื่อมต่ออินเทอร์เน็ตแทนที่จะรอให้หมดเวลาการเชื่อมต่อจากซ็อกเก็ต
AlexV

Android Query จัดการกับ "ไม่มีอินเทอร์เน็ต" และส่งคืนรหัสข้อผิดพลาดที่เกี่ยวข้องเร็วพอ อาจจะคุ้มค่าที่จะแทนที่ด้วย aQuery? อีกวิธีหนึ่งคือการสร้างผู้ฟังเกี่ยวกับการเปลี่ยนแปลงเครือข่ายดังนั้นแอปจะทราบเกี่ยวกับความพร้อมใช้งานอินเทอร์เน็ตก่อนส่งคำขอ
สแตน

สำหรับการติดตั้งเพิ่มเติม 2 โปรดดูที่github.com/square/retrofit/issues/1260
ghanbari

คำตอบ:


63

สิ่งที่ฉันทำคือการสร้างไคลเอนต์ 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

ใช่กำหนดเอง โดยพื้นฐานแล้วเป็นรหัสจากคำถาม
AlexV

2
OkHttpClient ใช้ไม่ได้แทนที่จะใช้ OKClient () คำตอบที่ดี BDW ขอบคุณ.
Harshvardhan Trivedi

ขอบคุณ :-) แก้ไขคำตอบของคุณด้วยการใช้ OkHttp อย่างถูกต้อง
user1007522

1
@MominAlAziz ขึ้นอยู่กับว่าคุณกำหนดNoConnectivityExceptionอย่างไรคุณสามารถทำให้มันขยายIOExceptionหรือทำให้มันขยายออกไปRuntimeException
AlexV

45

เนื่องจากชุดติดตั้งเพิ่มเติม1.8.0นี้ได้เลิกใช้แล้ว

retrofitError.isNetworkError()

คุณต้องใช้

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

}

มีข้อผิดพลาดหลายประเภทที่คุณสามารถจัดการได้:

NETWORK เกิด IOException ขณะสื่อสารกับเซิร์ฟเวอร์เช่นหมดเวลาไม่มีการเชื่อมต่อ ฯลฯ ...

CONVERSION เกิดข้อยกเว้นในขณะที่ (de) จัดลำดับร่างกาย

HTTP ได้รับรหัสสถานะ HTTP ที่ไม่ใช่ 200 จากเซิร์ฟเวอร์เช่น 502, 503 ฯลฯ ...

UNEXPECTEDเกิดข้อผิดพลาดภายในขณะพยายามดำเนินการตามคำขอ แนวทางปฏิบัติที่ดีที่สุดในการโยนข้อยกเว้นนี้ซ้ำเพื่อให้แอปพลิเคชันของคุณขัดข้อง


8
หลีกเลี่ยงการใช้equalsมากกว่าตัวแปรใช้CONSTANT.equals(variable)แทนเสมอเพื่อหลีกเลี่ยง NullPointerException ที่เป็นไปได้ หรือดีกว่าในกรณีนี้ enums ยอมรับ == การเปรียบเทียบดังนั้นerror.getKind() == RetrofitError.Kind.NETWORKอาจเป็นแนวทางที่ดีกว่า
MariusBudin

1
แทนที่ Java ด้วย Kotlin หากคุณเบื่อ NPE และข้อ จำกัด / ความเจ็บปวดทางไวยากรณ์อื่น ๆ
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
จะมีข้อบกพร่องถ้าคุณเปิดโหมดเครื่องบินแล้วปิดเพราะคุณตั้งค่า var เพียงครั้งเดียวทำให้การสังเกตนั้นไร้ประโยชน์
Oliver Dixon

3
คุณกำลังสร้างOkHttpClient.Builder.addInterceptor(new ConnectivityInterceptor(HERE))สิ่งที่ควรอยู่ที่นี่?
Rumid

3
กรุณาใส่รหัสเพื่อให้ผู้คนเห็นภาพรวมได้ไหม
Oliver Dixon

1
@ เควินฉันจะแน่ใจได้อย่างไรว่าจะอัปเดตเมื่อการเชื่อมต่อพร้อมใช้งาน
Rumid

3
นี่ควรเป็นคำตอบที่ยอมรับได้ ดูตัวอย่างเพิ่มเติมได้ที่นี่: migapro.com/detect-offline-error-in-retrofit-2
j2emanue

35

@AlexV คุณแน่ใจหรือไม่ว่า RetrofitError มีการหมดเวลาเป็นเหตุผล (SocketTimeOutException เมื่อเรียก getCause ()) เมื่อไม่มีการเชื่อมต่ออินเทอร์เน็ต

เท่าที่ฉันทราบเมื่อไม่มีการเชื่อมต่ออินเทอร์เน็ต 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
มี ErrorHandler ที่ให้มาในแหล่งการติดตั้งเพิ่มเติมที่คุณสามารถใช้ได้ หากคุณไม่ได้จัดการกับข้อผิดพลาดด้วยตนเอง Retrofit จะให้ RetrofitError ของ [java.net.UnknownHostException: ไม่สามารถแก้ไขโฮสต์ "example.com": ไม่มีที่อยู่ที่เชื่อมโยงกับชื่อโฮสต์]
Codeversed

1
@Codeversed ดังนั้นการisNetworkErrorกำจัดไม่สามารถแก้ไขข้อผิดพลาดของโฮสต์ได้หรือไม่?
อยู่ทุกหนทุกแห่ง

2
คุณจะนำสิ่งนี้ไปใช้กับอินเทอร์เฟซไคลเอ็นต์ของคุณได้อย่างไร ฉันหมายความว่าคุณเชื่อมต่อชั้นเรียนนี้กับอะไร
FRR

23
cause.isNetworkError () เลิกใช้แล้ว : useerror.getKind() == RetrofitError.Kind.NETWORK
Hugo Gresse

6

สำหรับ Retrofit 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
} }

2

คุณสามารถใช้รหัสนี้

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.