彻底搞懂Android Volley错误响应的捕获与分析(12)

115 阅读18分钟

彻底搞懂Android Volley错误响应的捕获与分析:从源码到实战的全面解析

一、引言

在Android开发中,网络请求是不可或缺的一部分。而Volley作为Android官方推荐的网络请求库,凭借其简洁的API和高效的性能,受到了广大开发者的喜爱。然而,在实际开发中,网络请求难免会遇到各种错误,如网络连接失败、服务器错误、数据解析异常等。如何正确地捕获和分析这些错误响应,对于提高应用的稳定性和用户体验至关重要。

本文将深入剖析Android Volley库中错误响应的捕获与分析机制,从源码级别详细分析错误处理的各个关键环节。通过本文的学习,你将全面掌握Volley错误响应的捕获方法、错误类型的分类与识别、错误信息的解析与利用,从而在开发中更加高效地处理各种网络错误场景。

二、Volley错误处理概述

2.1 错误处理的基本概念

错误处理是指在程序执行过程中,对可能出现的异常情况进行捕获、分析和处理的过程。在Android开发中,网络请求的错误处理尤为重要,因为网络环境复杂多变,可能会出现各种不可预测的问题。

2.2 Volley错误处理的流程

在Volley中,错误处理的基本流程如下:

  1. 网络请求发送后,可能会出现各种错误
  2. Volley将错误信息封装到VolleyError对象中
  3. VolleyError对象通过ResponseDelivery机制传递到主线程
  4. 在主线程中,VolleyError对象被传递给我们设置的错误监听器进行处理
  5. 开发者可以根据错误类型和错误信息,采取相应的处理措施

下面我们将详细分析这个流程中的每一个环节。

三、VolleyError类解析

3.1 VolleyError类的作用

VolleyError类是Volley中用于封装网络请求错误信息的核心类。它继承自Exception类,是所有Volley错误的基类。

3.2 VolleyError类的源码分析

让我们来看一下VolleyError类的源码:

/**
 * Volley错误的基类。
 */
public class VolleyError extends Exception {
    /** 网络响应数据,如果有的话 */
    public final NetworkResponse networkResponse;
    
    /** 网络请求的耗时,单位为毫秒 */
    private long networkTimeMs;

    /**
     * 使用消息创建一个新的VolleyError实例。
     */
    public VolleyError() {
        networkResponse = null;
    }

    /**
     * 使用消息创建一个新的VolleyError实例。
     */
    public VolleyError(String exceptionMessage) {
        super(exceptionMessage);
        networkResponse = null;
    }

    /**
     * 使用消息和原因创建一个新的VolleyError实例。
     */
    public VolleyError(String exceptionMessage, Throwable reason) {
        super(exceptionMessage, reason);
        networkResponse = null;
    }

    /**
     * 使用原因创建一个新的VolleyError实例。
     */
    public VolleyError(Throwable cause) {
        super(cause);
        networkResponse = null;
    }

    /**
     * 使用网络响应创建一个新的VolleyError实例。
     */
    public VolleyError(NetworkResponse response) {
        networkResponse = response;
    }

    /**
     * 使用网络响应和原因创建一个新的VolleyError实例。
     */
    public VolleyError(NetworkResponse response, Throwable cause) {
        super(cause);
        networkResponse = response;
    }

    /**
     * 设置网络请求的耗时。
     */
    public void setNetworkTimeMs(long networkTimeMs) {
        this.networkTimeMs = networkTimeMs;
    }

    /**
     * 获取网络请求的耗时。
     */
    public long getNetworkTimeMs() {
        return networkTimeMs;
    }
}

从上面的源码可以看出,VolleyError类包含了以下几个重要的成员变量:

  • networkResponse:网络响应数据,如果有的话。它包含了服务器返回的原始数据、HTTP状态码、响应头等信息。
  • networkTimeMs:网络请求的耗时,单位为毫秒。

VolleyError类提供了多个构造函数,允许我们使用不同的参数创建错误实例。例如,我们可以使用消息、原因、网络响应等参数来创建不同类型的错误。

3.3 VolleyError的子类

VolleyError类有多个子类,用于表示不同类型的网络错误。这些子类包括:

  1. AuthFailureError:表示认证失败错误,通常是由于缺少或无效的认证信息导致的。
  2. NetworkError:表示网络连接错误,通常是由于网络不可用或连接超时导致的。
  3. NoConnectionError:表示没有网络连接错误,是NetworkError的子类。
  4. ParseError:表示解析错误,通常是由于响应数据格式不正确导致的。
  5. ServerError:表示服务器错误,通常是由于服务器返回了错误的HTTP状态码(如500)导致的。
  6. TimeoutError:表示请求超时错误,通常是由于请求在规定时间内没有得到响应导致的。

下面我们将详细分析这些子类。

四、AuthFailureError类解析

4.1 AuthFailureError类的作用

AuthFailureError类用于表示认证失败错误,通常是由于缺少或无效的认证信息导致的。

4.2 AuthFailureError类的源码分析

AuthFailureError类的源码如下:

/**
 * 表示认证失败的错误。通常是由于缺少或无效的认证信息导致的。
 */
public class AuthFailureError extends VolleyError {
    /**
     * 创建一个新的AuthFailureError实例。
     */
    public AuthFailureError() {
        super();
    }

    /**
     * 使用消息创建一个新的AuthFailureError实例。
     */
    public AuthFailureError(String message) {
        super(message);
    }

    /**
     * 使用网络响应创建一个新的AuthFailureError实例。
     */
    public AuthFailureError(NetworkResponse response) {
        super(response);
    }

    /**
     * 使用原因创建一个新的AuthFailureError实例。
     */
    public AuthFailureError(Throwable cause) {
        super(cause);
    }

    /**
     * 获取用于认证的请求头。
     * 如果实现了这个方法,Volley会自动在请求中添加这些头。
     */
    public Map<String, String> getResponseHeaders() {
        return null;
    }

    /**
     * 获取用于认证的请求体。
     * 如果实现了这个方法,Volley会自动在请求中添加这个体。
     */
    public byte[] getBody() {
        return null;
    }
}

从上面的源码可以看出,AuthFailureError类继承自VolleyError类,并提供了几个额外的方法:

  • getResponseHeaders():获取用于认证的请求头。如果实现了这个方法,Volley会自动在请求中添加这些头。
  • getBody():获取用于认证的请求体。如果实现了这个方法,Volley会自动在请求中添加这个体。

4.3 AuthFailureError的使用场景

AuthFailureError通常在以下场景中出现:

  1. 当请求需要认证信息(如API密钥、OAuth令牌等),但请求中没有包含这些信息时。
  2. 当提供的认证信息无效或过期时。
  3. 当服务器要求重新认证时。

在这些情况下,服务器通常会返回401(未授权)或403(禁止访问)HTTP状态码,Volley会将这些响应封装为AuthFailureError。

五、NetworkError类解析

5.1 NetworkError类的作用

NetworkError类用于表示网络连接错误,通常是由于网络不可用或连接超时导致的。

5.2 NetworkError类的源码分析

NetworkError类的源码如下:

/**
 * 表示网络连接错误的错误。通常是由于网络不可用或连接超时导致的。
 */
public class NetworkError extends VolleyError {
    /**
     * 创建一个新的NetworkError实例。
     */
    public NetworkError() {
        super();
    }

    /**
     * 使用原因创建一个新的NetworkError实例。
     */
    public NetworkError(Throwable cause) {
        super(cause);
    }

    /**
     * 使用网络响应创建一个新的NetworkError实例。
     * 注意:网络响应通常为null,因为在发生网络错误时通常没有响应。
     */
    public NetworkError(NetworkResponse networkResponse) {
        super(networkResponse);
    }
}

从上面的源码可以看出,NetworkError类继承自VolleyError类,并提供了几个构造函数,允许我们使用不同的参数创建错误实例。

5.3 NetworkError的使用场景

NetworkError通常在以下场景中出现:

  1. 当设备没有网络连接时。
  2. 当网络连接不稳定或中断时。
  3. 当服务器不可达时。
  4. 当DNS解析失败时。

在这些情况下,通常没有服务器响应,因此NetworkError的networkResponse成员变量可能为null。

六、NoConnectionError类解析

6.1 NoConnectionError类的作用

NoConnectionError类用于表示没有网络连接错误,是NetworkError的子类。

6.2 NoConnectionError类的源码分析

NoConnectionError类的源码如下:

/**
 * 表示没有网络连接的错误。是NetworkError的子类。
 */
public class NoConnectionError extends NetworkError {
    /**
     * 创建一个新的NoConnectionError实例。
     */
    public NoConnectionError() {
        super();
    }

    /**
     * 使用原因创建一个新的NoConnectionError实例。
     */
    public NoConnectionError(Throwable reason) {
        super(reason);
    }
}

从上面的源码可以看出,NoConnectionError类继承自NetworkError类,并提供了几个构造函数,允许我们使用不同的参数创建错误实例。

6.3 NoConnectionError的使用场景

NoConnectionError通常在以下场景中出现:

  1. 当设备的Wi-Fi和移动数据都关闭时。
  2. 当设备处于飞行模式时。
  3. 当设备在没有网络覆盖的区域时。

在这些情况下,应用无法建立任何网络连接,Volley会抛出NoConnectionError。

七、ParseError类解析

7.1 ParseError类的作用

ParseError类用于表示解析错误,通常是由于响应数据格式不正确导致的。

7.2 ParseError类的源码分析

ParseError类的源码如下:

/**
 * 表示解析错误的错误。通常是由于响应数据格式不正确导致的。
 */
public class ParseError extends VolleyError {
    /**
     * 创建一个新的ParseError实例。
     */
    public ParseError() {
        super();
    }

    /**
     * 使用网络响应创建一个新的ParseError实例。
     */
    public ParseError(NetworkResponse response) {
        super(response);
    }

    /**
     * 使用原因创建一个新的ParseError实例。
     */
    public ParseError(Throwable cause) {
        super(cause);
    }
}

从上面的源码可以看出,ParseError类继承自VolleyError类,并提供了几个构造函数,允许我们使用不同的参数创建错误实例。

7.3 ParseError的使用场景

ParseError通常在以下场景中出现:

  1. 当响应数据格式不符合预期时,例如JSON格式不正确。
  2. 当响应数据使用不支持的编码时。
  3. 当响应数据不完整或损坏时。

例如,当我们使用JsonObjectRequest请求一个JSON数据,但服务器返回的不是有效的JSON格式时,Volley会抛出ParseError。

八、ServerError类解析

8.1 ServerError类的作用

ServerError类用于表示服务器错误,通常是由于服务器返回了错误的HTTP状态码(如500)导致的。

8.2 ServerError类的源码分析

ServerError类的源码如下:

/**
 * 表示服务器错误的错误。通常是由于服务器返回了错误的HTTP状态码(如500)导致的。
 */
public class ServerError extends VolleyError {
    /**
     * 创建一个新的ServerError实例。
     */
    public ServerError() {
        super();
    }

    /**
     * 使用网络响应创建一个新的ServerError实例。
     */
    public ServerError(NetworkResponse response) {
        super(response);
    }

    /**
     * 使用原因创建一个新的ServerError实例。
     */
    public ServerError(Throwable cause) {
        super(cause);
    }
}

从上面的源码可以看出,ServerError类继承自VolleyError类,并提供了几个构造函数,允许我们使用不同的参数创建错误实例。

8.3 ServerError的使用场景

ServerError通常在以下场景中出现:

  1. 当服务器返回500系列状态码(如500、502、503、504等)时。
  2. 当服务器遇到内部错误无法处理请求时。
  3. 当服务器暂时不可用或过载时。

在这些情况下,服务器返回的HTTP状态码表明服务器端存在问题,Volley会将这些响应封装为ServerError。

九、TimeoutError类解析

9.1 TimeoutError类的作用

TimeoutError类用于表示请求超时错误,通常是由于请求在规定时间内没有得到响应导致的。

9.2 TimeoutError类的源码分析

TimeoutError类的源码如下:

/**
 * 表示请求超时的错误。通常是由于请求在规定时间内没有得到响应导致的。
 */
public class TimeoutError extends VolleyError {
    /**
     * 创建一个新的TimeoutError实例。
     */
    public TimeoutError() {
        super();
    }

    /**
     * 使用原因创建一个新的TimeoutError实例。
     */
    public TimeoutError(Throwable cause) {
        super(cause);
    }
}

从上面的源码可以看出,TimeoutError类继承自VolleyError类,并提供了几个构造函数,允许我们使用不同的参数创建错误实例。

9.3 TimeoutError的使用场景

TimeoutError通常在以下场景中出现:

  1. 当网络连接缓慢,请求在超时时间内没有得到响应时。
  2. 当服务器负载过高,无法及时处理请求时。
  3. 当请求的数据量过大,传输时间超过超时时间时。

在Volley中,我们可以通过setRetryPolicy方法设置请求的超时时间和重试策略。如果请求在指定的超时时间内没有得到响应,Volley会抛出TimeoutError。

十、错误响应的捕获过程

10.1 网络请求执行过程中的错误捕获

在Volley中,网络请求的执行主要由NetworkDispatcher类负责。当执行网络请求时,会捕获各种可能的异常,并将其转换为相应的VolleyError。

下面是NetworkDispatcher类的run方法的部分源码,展示了错误捕获的过程:

/**
 * NetworkDispatcher的run方法
 */
@Override
public void run() {
    // 设置线程名称
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    
    // 循环处理请求
    while (true) {
        long startTimeMs = SystemClock.elapsedRealtime();
        Request<?> request;
        
        try {
            // 从请求队列中取出一个请求
            request = mQueue.take();
        } catch (InterruptedException e) {
            // 如果线程被中断,退出循环
            if (mQuit) {
                return;
            }
            continue;
        }
        
        try {
            request.addMarker("network-queue-take");
            
            // 如果请求已取消,跳过处理
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                continue;
            }
            
            // 标记请求开始网络处理
            addTrafficStatsTag(request);
            
            // 执行网络请求
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");
            
            // 如果服务器返回304状态码(未修改),并且我们有缓存条目,则使用缓存响应
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }
            
            // 解析网络响应
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");
            
            // 如果请求需要缓存,将响应写入缓存
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }
            
            // 标记请求已交付
            request.markDelivered();
            
            // 将响应传递到主线程
            mDelivery.postResponse(request, response);
        } catch (SocketTimeoutException e) {
            // 处理超时异常
            attemptRetryOnException("socket", request, new TimeoutError());
        } catch (MalformedURLException e) {
            // 处理URL格式错误
            throw new RuntimeException("Bad URL " + request.getUrl(), e);
        } catch (IOException e) {
            // 处理IO异常
            int statusCode = 0;
            NetworkResponse networkResponse = null;
            
            if (connection != null) {
                statusCode = connection.getResponseCode();
            } else {
                throw new NoConnectionError(e);
            }
            
            // 读取错误响应
            inputStream = connection.getErrorStream();
            try {
                byte[] errorResponse = entityToBytes(inputStream, connection.getContentLength());
                networkResponse = new NetworkResponse(statusCode, errorResponse,
                        convertHeaders(connection.getHeaderFields()), false,
                        SystemClock.elapsedRealtime() - requestStart);
            } catch (IOException ioe) {
                VolleyLog.e("Error reading error response body: %s", ioe.getMessage());
            }
            
            // 处理可重试的错误
            if (statusCode == HttpURLConnection.HTTP_UNAVAILABLE ||
                    (request.shouldRetryServerErrors() && isRetriableStatusCode(statusCode))) {
                attemptRetryOnException("server", request, new ServerError(networkResponse));
            }
            
            // 抛出其他错误
            if (networkResponse != null) {
                throw new ServerError(networkResponse);
            } else {
                throw new NetworkError(e);
            }
        } catch (VolleyError volleyError) {
            // 处理网络错误
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
        } catch (Exception e) {
            // 处理其他异常
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
            VolleyError volleyError = new VolleyError(e);
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            mDelivery.postError(request, volleyError);
        }
    }
}

从上面的代码可以看出,在执行网络请求的过程中,会捕获以下几种异常:

  1. SocketTimeoutException:当请求超时时,捕获该异常,并调用attemptRetryOnException方法尝试重试请求,或者将其转换为TimeoutError。
  2. MalformedURLException:当URL格式错误时,捕获该异常,并抛出RuntimeException。
  3. IOException:当发生IO异常时,捕获该异常,并根据具体情况将其转换为NoConnectionError或ServerError。
  4. VolleyError:当发生Volley错误时,捕获该异常,并调用parseAndDeliverNetworkError方法处理错误。
  5. Exception:当发生其他异常时,捕获该异常,并将其转换为VolleyError。

10.2 响应解析过程中的错误捕获

在响应解析过程中,也会捕获各种可能的异常,并将其转换为相应的VolleyError。例如,在JsonObjectRequestparseNetworkResponse方法中:

/**
 * JsonObjectRequest将响应数据解析为JSONObject
 */
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
    try {
        // 将响应数据转换为字符串
        String jsonString = new String(
                response.data,
                HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
        
        // 将字符串解析为JSONObject
        return Response.success(
                new JSONObject(jsonString),
                HttpHeaderParser.parseCacheHeaders(response));
    } catch (UnsupportedEncodingException e) {
        // 处理编码异常
        return Response.error(new ParseError(e));
    } catch (JSONException je) {
        // 处理JSON解析异常
        return Response.error(new ParseError(je));
    }
}

从上面的代码可以看出,在解析JSON数据时,会捕获以下两种异常:

  1. UnsupportedEncodingException:当响应数据使用不支持的编码时,捕获该异常,并将其转换为ParseError。
  2. JSONException:当响应数据不是有效的JSON格式时,捕获该异常,并将其转换为ParseError。

十一、错误响应的传递过程

11.1 通过ResponseDelivery传递错误

在Volley中,错误响应通过ResponseDelivery接口传递到主线程。ResponseDelivery接口定义了将响应和错误传递到主线程的方法。

下面是ResponseDelivery接口的源码:

/**
 * 响应分发接口,负责将响应和错误传递到主线程。
 */
public interface ResponseDelivery {
    /**
     * 将响应传递到主线程
     *
     * @param request 原始请求
     * @param response 响应数据
     */
    void postResponse(Request<?> request, Response<?> response);
    
    /**
     * 将响应传递到主线程,并在传递完成后执行回调
     *
     * @param request 原始请求
     * @param response 响应数据
     * @param runnable 传递完成后执行的回调
     */
    void postResponse(Request<?> request, Response<?> response, Runnable runnable);
    
    /**
     * 将错误传递到主线程
     *
     * @param request 原始请求
     * @param error 错误信息
     */
    void postError(Request<?> request, VolleyError error);
}

Volley提供了一个默认的ResponseDelivery实现类ExecutorDelivery,它使用一个Executor将响应和错误传递到主线程。

11.2 ExecutorDelivery类的实现

ExecutorDelivery类的源码如下:

/**
 * 默认的响应分发器实现,使用Executor将响应和错误传递到主线程。
 */
public class ExecutorDelivery implements ResponseDelivery {
    /** 用于在主线程执行的Executor */
    private final Executor mResponsePoster;

    /**
     * 创建一个新的ExecutorDelivery实例
     *
     * @param handler 用于在主线程执行的Handler
     */
    public ExecutorDelivery(final Handler handler) {
        // 创建一个在主线程执行的Executor
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    /**
     * 创建一个新的ExecutorDelivery实例,使用提供的Executor
     *
     * @param executor 用于执行的Executor
     */
    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        // 标记请求为已完成
        request.markDelivered();
        request.addMarker("post-response");
        
        // 创建一个响应分发Runnable
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        // 添加错误标记
        request.addMarker("post-error");
        
        // 解析错误响应
        Response<?> response = Response.error(error);
        
        // 创建一个响应分发Runnable
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

    /**
     * 用于在主线程分发响应的Runnable
     */
    @SuppressWarnings("rawtypes")
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mRunnable = runnable;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            // 如果请求已取消,不进行分发
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // 处理成功响应
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                // 处理错误响应
                mRequest.deliverError(mResponse.error);
            }

            // 如果响应是中间响应(例如,用于流式响应)
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                // 完成请求
                mRequest.finish("done");
            }

            // 如果有回调Runnable,执行它
            if (mRunnable != null) {
                mRunnable.run();
            }
        }
    }
}

从上面的代码可以看出,ExecutorDelivery类的postError方法会创建一个包含错误信息的Response对象,并通过mResponsePoster将其传递到主线程。在主线程中,ResponseDeliveryRunnablerun方法会调用request.deliverError(mResponse.error),将错误信息传递给我们设置的错误监听器。

十二、错误响应的分析与处理

12.1 错误类型的判断与识别

在处理Volley错误时,我们首先需要判断错误的类型,以便采取相应的处理措施。可以通过 instanceof 运算符来判断错误的具体类型。

下面是一个示例代码,展示了如何判断错误类型:

// 创建请求时设置错误监听器
StringRequest request = new StringRequest(Request.Method.GET, url,
    new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            // 处理成功响应
        }
    },
    new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            // 判断错误类型并进行相应处理
            if (error instanceof TimeoutError || error instanceof NoConnectionError) {
                // 处理超时或无连接错误
                showToast("网络连接超时,请检查网络设置");
            } else if (error instanceof AuthFailureError) {
                // 处理认证失败错误
                showToast("认证失败,请重新登录");
                // 跳转到登录页面
                navigateToLoginPage();
            } else if (error instanceof ServerError) {
                // 处理服务器错误
                showToast("服务器错误,请稍后再试");
            } else if (error instanceof NetworkError) {
                // 处理网络错误
                showToast("网络连接错误,请检查网络设置");
            } else if (error instanceof ParseError) {
                // 处理解析错误
                showToast("数据解析错误,请稍后再试");
            }
        }
    });

12.2 错误信息的获取与分析

VolleyError类提供了一些方法,用于获取错误的详细信息,帮助我们分析和定位问题。

下面是一些常用的方法:

  1. getMessage():获取错误消息。
  2. getNetworkTimeMs():获取网络请求的耗时。
  3. getCause():获取错误的根本原因。
  4. getNetworkResponse():获取网络响应数据,如果有的话。

下面是一个示例代码,展示了如何获取和分析错误信息:

// 创建请求时设置错误监听器
StringRequest request = new StringRequest(Request.Method.GET, url,
    new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            // 处理成功响应
        }
    },
    new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            // 获取错误信息
            String errorMessage = error.getMessage();
            long networkTimeMs = error.getNetworkTimeMs();
            Throwable cause = error.getCause();
            
            // 记录错误信息
            Log.e(TAG, "请求错误: " + errorMessage);
            Log.e(TAG, "请求耗时: " + networkTimeMs + "ms");
            
            // 如果有网络响应,获取响应状态码和数据
            if (error.networkResponse != null) {
                int statusCode = error.networkResponse.statusCode;
                byte[] data = error.networkResponse.data;
                
                Log.e(TAG, "HTTP状态码: " + statusCode);
                
                // 如果有响应数据,尝试将其转换为字符串
                if (data != null && data.length > 0) {
                    try {
                        String responseData = new String(data, "UTF-8");
                        Log.e(TAG, "响应数据: " + responseData);
                    } catch (UnsupportedEncodingException e) {
                        Log.e(TAG, "无法解析响应数据", e);
                    }
                }
            }
            
            // 如果有根本原因,记录根本原因
            if (cause != null) {
                Log.e(TAG, "错误原因: " + cause.getMessage(), cause);
            }
            
            // 根据错误类型进行相应处理
            if (error instanceof TimeoutError) {
                // 处理超时错误
                showToast("请求超时,请重试");
            } else if (error instanceof ServerError) {
                // 处理服务器错误
                showToast("服务器错误: " + error.networkResponse.statusCode);
            } else {
                // 处理其他错误
                showToast("请求失败: " + errorMessage);
            }
        }
    });

12.3 自定义错误处理

除了使用Volley提供的错误类型,我们还可以自定义错误处理逻辑。例如,我们可以创建一个基类错误监听器,统一处理常见的错误情况:

/**
 * 自定义错误监听器基类
 */
public abstract class BaseErrorListener implements Response.ErrorListener {
    private Context mContext;

    public BaseErrorListener(Context context) {
        mContext = context;
    }

    @Override
    public void onErrorResponse(VolleyError error) {
        // 记录错误信息
        Log.e("VolleyError", "请求错误: " + error.getMessage());
        
        // 获取错误类型并进行相应处理
        if (error instanceof TimeoutError) {
            handleTimeoutError();
        } else if (error instanceof NoConnectionError) {
            handleNoConnectionError();
        } else if (error instanceof AuthFailureError) {
            handleAuthFailureError();
        } else if (error instanceof ServerError) {
            handleServerError(error);
        } else if (error instanceof NetworkError) {
            handleNetworkError();
        } else if (error instanceof ParseError) {
            handleParseError();
        } else {
            handleOtherError(error);
        }
        
        // 调用抽象方法,让子类处理特定错误
        onSpecificError(error);
    }

    /**
     * 处理超时错误
     */
    protected void handleTimeoutError() {
        showToast("请求超时,请检查网络连接");
    }

    /**
     * 处理无连接错误
     */
    protected void handleNoConnectionError() {
        showToast("没有网络连接,请检查网络设置");
    }

    /**
     * 处理认证失败错误
     */
    protected void handleAuthFailureError() {
        showToast("认证失败,请重新登录");
        // 跳转到登录页面
        navigateToLoginPage();
    }

    /**
     * 处理服务器错误
     */
    protected void handleServerError(VolleyError error) {
        if (error.networkResponse != null) {
            int statusCode = error.networkResponse.statusCode;
            showToast("服务器错误: " + statusCode);
        } else {
            showToast("服务器错误,请稍后再试");
        }
    }

    /**
     * 处理网络错误
     */
    protected void handleNetworkError() {
        showToast("网络连接错误,请检查网络设置");
    }

    /**
     * 处理解析错误
     */
    protected void handleParseError() {
        showToast("数据解析错误,请稍后再试");
    }

    /**
     * 处理其他错误
     */
    protected void handleOtherError(VolleyError error) {
        showToast("请求失败: " + error.getMessage());
    }

    /**
     * 显示Toast消息
     */
    private void showToast(String message) {
        Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
    }

    /**
     * 跳转到登录页面
     */
    private void navigateToLoginPage() {
        // 实现跳转到登录页面的逻辑
        Intent intent = new Intent(mContext, LoginActivity.class);
        mContext.startActivity(intent);
    }

    /**
     * 让子类处理特定错误
     */
    protected abstract void onSpecificError(VolleyError error);
}

然后在创建请求时,可以使用这个基类错误监听器:

// 创建请求时使用自定义错误监听器
StringRequest request = new StringRequest(Request.Method.GET, url,
    new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            // 处理成功响应
        }
    },
    new BaseErrorListener(context) {
        @Override
        protected void onSpecificError(VolleyError error) {
            // 处理特定于这个请求的错误
        }
    });

这样,我们就可以统一处理常见的错误情况,同时让子类处理特定于某个请求的错误。