深入剖析Android Volley自定义错误处理逻辑:从源码到实战的全面解析
一、引言
在Android开发中,网络请求是不可或缺的一部分。而Volley作为Android官方推荐的网络请求库,凭借其简洁的API和高效的性能,受到了广大开发者的喜爱。然而,在实际开发中,网络请求难免会遇到各种错误,如网络连接失败、服务器错误、数据解析异常等。Volley默认的错误处理机制虽然提供了基本的错误信息,但在复杂的应用场景中,往往无法满足我们的需求。因此,自定义错误处理逻辑变得尤为重要。
本文将深入剖析Android Volley库中自定义错误处理的实现原理,从源码级别详细分析错误处理的各个关键环节。通过本文的学习,你将全面掌握Volley错误处理的核心机制,学会如何自定义错误类型、如何拦截和处理错误、如何实现统一的错误处理逻辑,从而在开发中更加灵活、高效地处理各种网络错误场景。
二、Volley错误处理基础
2.1 Volley错误类层次结构
Volley的错误处理基于一个完整的类层次结构,所有的错误类都继承自VolleyError
类。下面是Volley错误类的基本层次结构:
/**
* Volley错误的基类,继承自Exception
*/
public class VolleyError extends Exception {
// 网络响应数据,如果有的话
public final NetworkResponse networkResponse;
// 网络请求的耗时,单位为毫秒
private long networkTimeMs;
// 各种构造函数...
}
/**
* 认证失败错误
*/
public class AuthFailureError extends VolleyError {
// 用于获取认证请求头和请求体的方法
}
/**
* 网络连接错误
*/
public class NetworkError extends VolleyError {
// 构造函数...
}
/**
* 无连接错误,是NetworkError的子类
*/
public class NoConnectionError extends NetworkError {
// 构造函数...
}
/**
* 解析错误
*/
public class ParseError extends VolleyError {
// 构造函数...
}
/**
* 服务器错误
*/
public class ServerError extends VolleyError {
// 构造函数...
}
/**
* 超时错误
*/
public class TimeoutError extends VolleyError {
// 构造函数...
}
从上面的类层次结构可以看出,Volley已经为我们提供了多种常见的错误类型,每种错误类型都对应着不同的网络错误场景。
2.2 错误处理的基本流程
在Volley中,错误处理的基本流程如下:
- 网络请求执行过程中,如果发生错误,会创建相应的VolleyError对象
- 错误对象通过ResponseDelivery机制传递到主线程
- 在主线程中,错误对象被传递给我们设置的ErrorListener进行处理
- 我们可以在ErrorListener中根据错误类型进行相应的处理
下面是一个简单的示例,展示了Volley错误处理的基本流程:
// 创建一个StringRequest对象
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) {
// 处理超时错误
} else if (error instanceof NetworkError) {
// 处理网络连接错误
} else if (error instanceof ServerError) {
// 处理服务器错误
} else if (error instanceof AuthFailureError) {
// 处理认证失败错误
} else if (error instanceof ParseError) {
// 处理解析错误
} else {
// 处理其他错误
}
}
});
// 将请求添加到请求队列
requestQueue.add(request);
2.3 错误处理的核心类
Volley错误处理的核心类主要包括以下几个:
- VolleyError:所有错误的基类,封装了错误信息和网络响应
- Request:请求的基类,包含了错误处理的相关方法
- ResponseDelivery:负责将错误响应传递到主线程
- ExecutorDelivery:ResponseDelivery的默认实现类
- NetworkDispatcher:网络调度器,处理网络请求和错误
下面我们将详细分析这些核心类在错误处理中的作用。
三、自定义错误类型
3.1 为什么需要自定义错误类型
虽然Volley已经为我们提供了多种常见的错误类型,但在实际开发中,我们可能会遇到一些特定的业务场景,需要自定义错误类型来更准确地表达错误信息。例如:
- 服务器返回特定的错误码,需要自定义错误类型来处理
- 业务逻辑中特定的错误情况,需要自定义错误类型来表示
- 需要在错误信息中包含更多的业务相关数据
通过自定义错误类型,我们可以更清晰地表达错误信息,使错误处理更加灵活和高效。
3.2 自定义错误类型的实现
自定义错误类型非常简单,只需要继承VolleyError类,并添加必要的属性和方法即可。下面是一个示例,展示了如何自定义一个业务相关的错误类型:
/**
* 自定义业务错误类型,继承自VolleyError
*/
public class BusinessError extends VolleyError {
// 业务错误码
private int errorCode;
// 额外的错误信息
private String extraInfo;
/**
* 构造函数,接收网络响应和错误码
*/
public BusinessError(NetworkResponse response, int errorCode) {
super(response);
this.errorCode = errorCode;
}
/**
* 构造函数,接收网络响应、错误码和额外信息
*/
public BusinessError(NetworkResponse response, int errorCode, String extraInfo) {
super(response);
this.errorCode = errorCode;
this.extraInfo = extraInfo;
}
/**
* 获取错误码
*/
public int getErrorCode() {
return errorCode;
}
/**
* 获取额外信息
*/
public String getExtraInfo() {
return extraInfo;
}
/**
* 重写getMessage方法,返回更详细的错误信息
*/
@Override
public String getMessage() {
if (super.getMessage() != null) {
return "BusinessError: " + errorCode + ", " + super.getMessage();
} else {
return "BusinessError: " + errorCode;
}
}
}
3.3 在请求中使用自定义错误类型
自定义错误类型后,我们需要在请求中使用它。通常,我们会在解析网络响应的方法中根据服务器返回的结果创建自定义错误对象。
下面是一个示例,展示了如何在自定义请求类中使用自定义错误类型:
/**
* 自定义请求类,处理业务相关的响应和错误
*/
public class BusinessRequest extends Request<BusinessResponse> {
private final Response.Listener<BusinessResponse> mListener;
/**
* 构造函数
*/
public BusinessRequest(int method, String url, Response.Listener<BusinessResponse> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* 解析网络响应
*/
@Override
protected Response<BusinessResponse> parseNetworkResponse(NetworkResponse response) {
try {
// 将响应数据转换为JSON对象
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
JSONObject jsonObject = new JSONObject(jsonString);
// 检查服务器返回的状态码
int statusCode = jsonObject.getInt("status");
String message = jsonObject.getString("message");
// 如果状态码不是成功状态,创建自定义错误
if (statusCode != 200) {
// 获取额外的错误信息(如果有)
String extraInfo = jsonObject.optString("extra_info");
return Response.error(new BusinessError(response, statusCode, extraInfo));
}
// 解析业务数据
JSONObject data = jsonObject.getJSONObject("data");
BusinessResponse businessResponse = parseBusinessData(data);
// 返回成功响应
return Response.success(businessResponse,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException e) {
return Response.error(new ParseError(e));
}
}
/**
* 解析业务数据
*/
private BusinessResponse parseBusinessData(JSONObject data) throws JSONException {
// 解析业务数据的逻辑
BusinessResponse response = new BusinessResponse();
response.setId(data.getInt("id"));
response.setName(data.getString("name"));
// 其他属性...
return response;
}
/**
* 传递响应到监听器
*/
@Override
protected void deliverResponse(BusinessResponse response) {
mListener.onResponse(response);
}
}
在上面的代码中,我们在parseNetworkResponse
方法中检查服务器返回的状态码。如果状态码不是成功状态(200),我们就创建一个BusinessError
对象,并通过Response.error
方法返回。这样,当请求执行时,如果服务器返回错误状态码,我们的自定义错误就会被传递到错误监听器中。
四、自定义错误处理器
4.1 错误处理器的作用
在Volley中,错误处理器(ErrorProcessor)负责处理网络错误,并可以对错误进行转换或修改。通过自定义错误处理器,我们可以实现统一的错误处理逻辑,例如:
- 对特定的错误进行转换或包装
- 添加额外的错误信息
- 实现错误重试策略
- 记录错误日志
4.2 ErrorProcessor接口源码分析
ErrorProcessor是一个接口,定义了处理错误的方法:
/**
* 错误处理器接口,用于处理网络错误
*/
public interface ErrorProcessor {
/**
* 处理网络错误
* @param error 原始错误
* @return 处理后的错误
*/
VolleyError process(VolleyError error);
}
从上面的源码可以看出,ErrorProcessor接口只有一个方法process
,该方法接收一个VolleyError对象,并返回一个处理后的VolleyError对象。
4.3 默认错误处理器实现
Volley提供了一个默认的错误处理器实现DefaultErrorProcessor
,其源码如下:
/**
* 默认的错误处理器实现
*/
public class DefaultErrorProcessor implements ErrorProcessor {
@Override
public VolleyError process(VolleyError error) {
// 如果错误是服务器错误,并且响应状态码是304(未修改)
if (error instanceof ServerError && error.networkResponse != null) {
NetworkResponse response = error.networkResponse;
// 检查状态码是否为304
if (response.statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
// 如果是304状态码,返回一个特殊的NotModifiedError
return new NotModifiedError(response);
}
}
// 对于其他错误,直接返回原始错误
return error;
}
}
从上面的源码可以看出,默认的错误处理器只处理了一种特殊情况:当服务器返回304状态码时,将其转换为NotModifiedError。对于其他错误,直接返回原始错误。
4.4 自定义错误处理器实现
下面我们来实现一个自定义的错误处理器,用于处理更复杂的错误场景:
/**
* 自定义错误处理器,用于统一处理网络错误
*/
public class CustomErrorProcessor implements ErrorProcessor {
private static final String TAG = "CustomErrorProcessor";
// 上下文对象,用于获取资源等
private Context mContext;
// 错误日志记录器
private ErrorLogger mErrorLogger;
/**
* 构造函数
*/
public CustomErrorProcessor(Context context, ErrorLogger errorLogger) {
mContext = context;
mErrorLogger = errorLogger;
}
/**
* 处理网络错误
*/
@Override
public VolleyError process(VolleyError error) {
// 记录错误日志
logError(error);
// 根据错误类型进行不同的处理
if (error instanceof TimeoutError) {
// 处理超时错误
return handleTimeoutError(error);
} else if (error instanceof NoConnectionError) {
// 处理无连接错误
return handleNoConnectionError(error);
} else if (error instanceof AuthFailureError) {
// 处理认证失败错误
return handleAuthFailureError(error);
} else if (error instanceof ServerError) {
// 处理服务器错误
return handleServerError((ServerError) error);
} else if (error instanceof NetworkError) {
// 处理网络错误
return handleNetworkError(error);
} else if (error instanceof ParseError) {
// 处理解析错误
return handleParseError(error);
}
// 对于其他类型的错误,直接返回
return error;
}
/**
* 记录错误日志
*/
private void logError(VolleyError error) {
if (mErrorLogger != null) {
mErrorLogger.logError(error);
} else {
Log.e(TAG, "Error: " + error.getMessage(), error);
}
}
/**
* 处理超时错误
*/
private VolleyError handleTimeoutError(VolleyError error) {
// 可以在这里添加额外的处理逻辑
// 例如,创建一个包含更多信息的自定义超时错误
return new CustomTimeoutError(error);
}
/**
* 处理无连接错误
*/
private VolleyError handleNoConnectionError(VolleyError error) {
// 检查网络连接状态
if (!isNetworkAvailable()) {
// 如果没有网络连接,创建一个包含提示信息的自定义错误
return new NoNetworkConnectionError(mContext.getString(R.string.no_network_connection));
}
// 否则返回原始错误
return error;
}
/**
* 处理认证失败错误
*/
private VolleyError handleAuthFailureError(VolleyError error) {
// 可以在这里添加重新认证的逻辑
// 例如,刷新token并重新发起请求
// 返回原始错误
return error;
}
/**
* 处理服务器错误
*/
private VolleyError handleServerError(ServerError error) {
if (error.networkResponse != null) {
int statusCode = error.networkResponse.statusCode;
// 根据不同的状态码进行不同的处理
switch (statusCode) {
case 500:
// 处理服务器内部错误
return new ServerInternalError(error.networkResponse);
case 503:
// 处理服务不可用错误
return new ServiceUnavailableError(error.networkResponse);
case 404:
// 处理资源未找到错误
return new ResourceNotFoundError(error.networkResponse);
default:
// 处理其他服务器错误
return error;
}
}
// 如果没有网络响应,返回原始错误
return error;
}
/**
* 处理网络错误
*/
private VolleyError handleNetworkError(VolleyError error) {
// 可以在这里添加额外的处理逻辑
return error;
}
/**
* 处理解析错误
*/
private VolleyError handleParseError(VolleyError error) {
// 可以在这里添加额外的处理逻辑
return error;
}
/**
* 检查网络连接状态
*/
private boolean isNetworkAvailable() {
ConnectivityManager connectivityManager =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
}
4.5 在RequestQueue中设置自定义错误处理器
要使用自定义错误处理器,需要在创建RequestQueue时进行设置:
// 创建自定义错误处理器
CustomErrorProcessor errorProcessor = new CustomErrorProcessor(context, new ErrorLogger());
// 创建缓存
Cache cache = new DiskBasedCache(context.getCacheDir(), 10 * 1024 * 1024); // 10MB
// 创建网络
Network network = new BasicNetwork(new HurlStack());
// 创建请求队列,并设置自定义错误处理器
RequestQueue requestQueue = new RequestQueue(cache, network, 4, errorProcessor);
requestQueue.start();
通过以上步骤,我们就实现了一个自定义的错误处理器,并在请求队列中使用它。这样,所有通过该请求队列执行的请求,都会使用我们的自定义错误处理器来处理错误。
五、全局错误监听器
5.1 为什么需要全局错误监听器
在实际开发中,我们可能有很多个请求,每个请求都需要设置一个错误监听器。这样会导致代码冗余,而且难以实现统一的错误处理逻辑。为了解决这个问题,我们可以实现一个全局错误监听器,统一处理所有请求的错误。
5.2 实现全局错误监听器
实现全局错误监听器的关键是创建一个基类错误监听器,然后让所有的错误监听器继承自这个基类。下面是一个示例:
/**
* 全局错误监听器基类
*/
public abstract class GlobalErrorListener implements Response.ErrorListener {
// 上下文对象
private Context mContext;
// 错误处理器
private ErrorProcessor mErrorProcessor;
/**
* 构造函数
*/
public GlobalErrorListener(Context context, ErrorProcessor errorProcessor) {
mContext = context;
mErrorProcessor = errorProcessor;
}
/**
* 处理错误响应
*/
@Override
public void onErrorResponse(VolleyError error) {
// 使用错误处理器处理错误
VolleyError processedError = mErrorProcessor.process(error);
// 根据错误类型进行不同的处理
if (processedError instanceof TimeoutError) {
handleTimeoutError((TimeoutError) processedError);
} else if (processedError instanceof NoConnectionError) {
handleNoConnectionError((NoConnectionError) processedError);
} else if (processedError instanceof AuthFailureError) {
handleAuthFailureError((AuthFailureError) processedError);
} else if (processedError instanceof ServerError) {
handleServerError((ServerError) processedError);
} else if (processedError instanceof NetworkError) {
handleNetworkError((NetworkError) processedError);
} else if (processedError instanceof ParseError) {
handleParseError((ParseError) processedError);
} else if (processedError instanceof BusinessError) {
handleBusinessError((BusinessError) processedError);
} else {
handleOtherError(processedError);
}
// 调用抽象方法,让子类处理特定的错误
onSpecificError(processedError);
}
/**
* 处理超时错误
*/
protected void handleTimeoutError(TimeoutError error) {
showToast("请求超时,请检查网络连接");
}
/**
* 处理无连接错误
*/
protected void handleNoConnectionError(NoConnectionError error) {
showToast("没有网络连接,请检查网络设置");
}
/**
* 处理认证失败错误
*/
protected void handleAuthFailureError(AuthFailureError error) {
showToast("认证失败,请重新登录");
// 跳转到登录页面的逻辑
navigateToLoginPage();
}
/**
* 处理服务器错误
*/
protected void handleServerError(ServerError error) {
if (error.networkResponse != null) {
int statusCode = error.networkResponse.statusCode;
showToast("服务器错误: " + statusCode);
} else {
showToast("服务器错误,请稍后再试");
}
}
/**
* 处理网络错误
*/
protected void handleNetworkError(NetworkError error) {
showToast("网络连接错误,请检查网络设置");
}
/**
* 处理解析错误
*/
protected void handleParseError(ParseError error) {
showToast("数据解析错误,请稍后再试");
}
/**
* 处理业务错误
*/
protected void handleBusinessError(BusinessError error) {
int errorCode = error.getErrorCode();
String errorMessage = error.getMessage();
showToast("业务错误: " + errorCode + ", " + errorMessage);
}
/**
* 处理其他错误
*/
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);
}
5.3 使用全局错误监听器
使用全局错误监听器非常简单,只需要让我们的错误监听器继承自GlobalErrorListener
,并实现onSpecificError
方法即可:
// 创建请求时使用全局错误监听器
StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理成功响应
}
},
new GlobalErrorListener(context, errorProcessor) {
@Override
protected void onSpecificError(VolleyError error) {
// 处理特定于这个请求的错误
Log.e("MyApp", "Specific error: " + error.getMessage());
}
});
// 将请求添加到请求队列
requestQueue.add(request);
通过这种方式,我们可以实现统一的错误处理逻辑,同时让每个请求的错误监听器可以处理特定于该请求的错误。
六、错误日志记录
6.1 错误日志记录的重要性
在开发和维护过程中,错误日志记录是非常重要的。通过记录详细的错误信息,我们可以快速定位和解决问题,提高开发效率和应用质量。
6.2 实现错误日志记录器
下面是一个简单的错误日志记录器实现:
/**
* 错误日志记录器
*/
public class ErrorLogger {
private static final String TAG = "VolleyErrorLogger";
// 日志文件路径
private static final String LOG_FILE_PATH = "volley_errors.log";
// 上下文对象
private Context mContext;
// 是否记录到文件
private boolean mLogToFile;
// 是否记录到控制台
private boolean mLogToConsole;
/**
* 构造函数
*/
public ErrorLogger(Context context, boolean logToFile, boolean logToConsole) {
mContext = context;
mLogToFile = logToFile;
mLogToConsole = logToConsole;
}
/**
* 记录错误
*/
public void logError(VolleyError error) {
// 创建错误信息
StringBuilder errorMessage = new StringBuilder();
errorMessage.append("Error Time: ").append(new Date()).append("\n");
errorMessage.append("Error Type: ").append(error.getClass().getSimpleName()).append("\n");
errorMessage.append("Error Message: ").append(error.getMessage()).append("\n");
// 如果有网络响应,添加网络响应信息
if (error.networkResponse != null) {
errorMessage.append("Status Code: ").append(error.networkResponse.statusCode).append("\n");
if (error.networkResponse.data != null) {
try {
String responseData = new String(error.networkResponse.data, "UTF-8");
errorMessage.append("Response Data: ").append(responseData).append("\n");
} catch (UnsupportedEncodingException e) {
errorMessage.append("Response Data: (Unsupported Encoding)").append("\n");
}
}
}
// 添加堆栈跟踪信息
StackTraceElement[] stackTrace = error.getStackTrace();
if (stackTrace != null && stackTrace.length > 0) {
errorMessage.append("Stack Trace:\n");
for (StackTraceElement element : stackTrace) {
errorMessage.append(" ").append(element.toString()).append("\n");
}
}
// 如果有原因,添加原因信息
Throwable cause = error.getCause();
if (cause != null) {
errorMessage.append("Cause: ").append(cause.getMessage()).append("\n");
StackTraceElement[] causeStackTrace = cause.getStackTrace();
if (causeStackTrace != null && causeStackTrace.length > 0) {
errorMessage.append("Cause Stack Trace:\n");
for (StackTraceElement element : causeStackTrace) {
errorMessage.append(" ").append(element.toString()).append("\n");
}
}
}
errorMessage.append("----------------------------------------\n");
// 记录到控制台
if (mLogToConsole) {
Log.e(TAG, errorMessage.toString());
}
// 记录到文件
if (mLogToFile) {
writeToFile(errorMessage.toString());
}
}
/**
* 将错误信息写入文件
*/
private void writeToFile(String errorMessage) {
FileOutputStream fos = null;
try {
// 获取日志文件
File logFile = new File(mContext.getExternalFilesDir(null), LOG_FILE_PATH);
// 如果文件不存在,创建文件
if (!logFile.exists()) {
logFile.createNewFile();
}
// 写入错误信息
fos = new FileOutputStream(logFile, true);
fos.write(errorMessage.getBytes());
} catch (IOException e) {
Log.e(TAG, "Failed to write to log file", e);
} finally {
// 关闭流
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
// 忽略
}
}
}
}
}
6.3 在错误处理器中使用错误日志记录器
在前面实现的自定义错误处理器中,我们已经使用了错误日志记录器:
/**
* 自定义错误处理器
*/
public class CustomErrorProcessor implements ErrorProcessor {
private static final String TAG = "CustomErrorProcessor";
// 上下文对象
private Context mContext;
// 错误日志记录器
private ErrorLogger mErrorLogger;
/**
* 构造函数
*/
public CustomErrorProcessor(Context context, ErrorLogger errorLogger) {
mContext = context;
mErrorLogger = errorLogger;
}
/**
* 处理网络错误
*/
@Override
public VolleyError process(VolleyError error) {
// 记录错误日志
logError(error);
// 其他处理逻辑...
}
/**
* 记录错误日志
*/
private void logError(VolleyError error) {
if (mErrorLogger != null) {
mErrorLogger.logError(error);
} else {
Log.e(TAG, "Error: " + error.getMessage(), error);
}
}
// 其他方法...
}
通过这种方式,我们可以记录所有的网络错误信息,方便后续的问题排查和应用优化。
七、错误重试机制
7.1 错误重试的适用场景
在网络请求中,有些错误是暂时的,例如网络波动导致的连接超时、服务器暂时不可用等。对于这些暂时的错误,我们可以通过重试机制来提高请求的成功率。
7.2 RetryPolicy接口源码分析
Volley提供了RetryPolicy
接口来实现请求重试机制:
/**
* 请求重试策略接口
*/
public interface RetryPolicy {
/**
* 获取当前超时时间(毫秒)
*/
int getCurrentTimeout();
/**
* 获取当前重试次数
*/
int getCurrentRetryCount();
/**
* 处理重试
* @param error 导致重试的错误
* @throws VolleyError 如果不应该重试,抛出此异常
*/
void retry(VolleyError error) throws VolleyError;
}
从上面的源码可以看出,RetryPolicy
接口定义了三个方法:
getCurrentTimeout()
:获取当前的超时时间getCurrentRetryCount()
:获取当前的重试次数retry()
:处理重试逻辑,如果不应该重试,抛出异常
7.3 默认重试策略实现
Volley提供了一个默认的重试策略实现DefaultRetryPolicy
:
/**
* 默认的重试策略实现
*/
public class DefaultRetryPolicy implements RetryPolicy {
/** 默认的套接字超时时间(毫秒) */
public static final int DEFAULT_TIMEOUT_MS = 2500;
/** 默认的最大重试次数 */
public static final int DEFAULT_MAX_RETRIES = 1;
/** 默认的退避乘数 */
public static final float DEFAULT_BACKOFF_MULT = 1f;
/** 当前超时时间 */
private int mCurrentTimeoutMs;
/** 当前重试次数 */
private int mCurrentRetryCount;
/** 最大重试次数 */
private final int mMaxNumRetries;
/** 退避乘数 */
private final float mBackoffMultiplier;
/**
* 使用默认值创建一个新的重试策略
*/
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
/**
* 创建一个新的重试策略
* @param initialTimeoutMs 初始超时时间(毫秒)
* @param maxNumRetries 最大重试次数
* @param backoffMultiplier 退避乘数
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
/**
* 获取当前超时时间
*/
@Override
public int getCurrentTimeout() {
return mCurrentTimeoutMs;
}
/**
* 获取当前重试次数
*/
@Override
public int getCurrentRetryCount() {
return mCurrentRetryCount;
}
/**
* 获取退避乘数
*/
public float getBackoffMultiplier() {
return mBackoffMultiplier;
}
/**
* 处理重试
*/
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
// 计算新的超时时间
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
// 如果超过最大重试次数,抛出异常
if (mCurrentRetryCount > mMaxNumRetries) {
throw error;
}
}
}
从上面的源码可以看出,DefaultRetryPolicy
实现了指数退避算法,每次重试时超时时间会按退避乘数递增。默认情况下,初始超时时间为2500毫秒,最大重试次数为1次,退避乘数为1.0。
7.4 自定义重试策略
我们可以根据实际需求实现自定义的重试策略。例如,只对特定类型的错误进行重试:
/**
* 自定义重试策略,只对特定类型的错误进行重试
*/
public class CustomRetryPolicy implements RetryPolicy {
// 初始超时时间(毫秒)
private int mInitialTimeoutMs;
// 当前超时时间
private int mCurrentTimeoutMs;
// 当前重试次数
private int mCurrentRetryCount;
// 最大重试次数
private final int mMaxNumRetries;
// 退避乘数
private final float mBackoffMultiplier;
/**
* 创建一个新的自定义重试策略
*/
public CustomRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mInitialTimeoutMs = initialTimeoutMs;
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
/**
* 获取当前超时时间
*/
@Override
public int getCurrentTimeout() {
return mCurrentTimeoutMs;
}
/**
* 获取当前重试次数
*/
@Override
public int getCurrentRetryCount() {
return mCurrentRetryCount;
}
/**
* 处理重试
*/
@Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
// 计算新的超时时间
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
// 判断是否应该重试
if (!shouldRetry(error)) {
throw error;
}
// 如果超过最大重试次数,抛出异常
if (mCurrentRetryCount > mMaxNumRetries) {
throw error;
}
}
/**
* 判断是否应该重试
*/
private boolean shouldRetry(VolleyError error) {
// 只对特定类型的错误进行重试
if (error instanceof TimeoutError || error instanceof NoConnectionError) {
return true;
}
// 对于服务器错误,只对特定状态码进行重试
if (error instanceof ServerError) {
if (error.networkResponse != null) {
int statusCode = error.networkResponse.statusCode;
// 对503(服务不可用)和504(网关超时)进行重试
return statusCode == 503 || statusCode == 504;
}
}
// 其他错误不进行重试
return false;
}
}
7.5 为请求设置重试策略
为请求设置重试策略非常简单,只需要调用setRetryPolicy
方法即可:
// 创建请求
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) {
// 处理
为请求设置重试策略非常简单,只需要调用setRetryPolicy
方法即可:
// 创建请求
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) {
// 处理错误响应
}
});
// 设置自定义重试策略
request.setRetryPolicy(new CustomRetryPolicy(
3000, // 初始超时时间3秒
2, // 最大重试次数2次
1.5f // 退避乘数1.5
));
// 将请求添加到请求队列
requestQueue.add(request);
7.6 在错误处理器中集成重试机制
我们也可以在错误处理器中集成重试机制,对特定错误自动进行重试:
/**
* 集成重试机制的错误处理器
*/
public class RetryErrorProcessor implements ErrorProcessor {
private static final String TAG = "RetryErrorProcessor";
// 上下文对象
private Context mContext;
// 错误日志记录器
private ErrorLogger mErrorLogger;
// 最大重试次数
private int mMaxRetries;
// 重试间隔(毫秒)
private long mRetryDelay;
// 请求队列
private RequestQueue mRequestQueue;
// 记录每个请求的重试次数
private Map<String, Integer> mRetryCountMap = new HashMap<>();
/**
* 构造函数
*/
public RetryErrorProcessor(Context context, ErrorLogger errorLogger,
int maxRetries, long retryDelay, RequestQueue requestQueue) {
mContext = context;
mErrorLogger = errorLogger;
mMaxRetries = maxRetries;
mRetryDelay = retryDelay;
mRequestQueue = requestQueue;
}
/**
* 处理网络错误
*/
@Override
public VolleyError process(final VolleyError error) {
// 记录错误日志
logError(error);
// 如果错误是可重试的,并且未超过最大重试次数,则安排重试
if (shouldRetry(error)) {
final Request<?> request = error.getRequest();
if (request != null) {
String requestTag = String.valueOf(request.getTag());
int retryCount = mRetryCountMap.getOrDefault(requestTag, 0);
if (retryCount < mMaxRetries) {
// 增加重试计数
mRetryCountMap.put(requestTag, retryCount + 1);
// 安排重试
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Retrying request: " + request.getUrl());
mRequestQueue.add(request);
}
}, mRetryDelay * (retryCount + 1)); // 指数退避
// 返回一个特殊的重试错误
return new RetryError(error, mMaxRetries - retryCount);
}
}
}
// 对于不可重试的错误或已达到最大重试次数,直接返回原始错误
return error;
}
/**
* 判断是否应该重试
*/
private boolean shouldRetry(VolleyError error) {
// 只对特定类型的错误进行重试
if (error instanceof TimeoutError || error instanceof NoConnectionError) {
return true;
}
// 对于服务器错误,只对特定状态码进行重试
if (error instanceof ServerError) {
if (error.networkResponse != null) {
int statusCode = error.networkResponse.statusCode;
// 对503(服务不可用)和504(网关超时)进行重试
return statusCode == 503 || statusCode == 504;
}
}
// 其他错误不进行重试
return false;
}
/**
* 记录错误日志
*/
private void logError(VolleyError error) {
if (mErrorLogger != null) {
mErrorLogger.logError(error);
} else {
Log.e(TAG, "Error: " + error.getMessage(), error);
}
}
}
/**
* 表示一个需要重试的错误
*/
public class RetryError extends VolleyError {
// 剩余重试次数
private int mRemainingRetries;
public RetryError(VolleyError originalError, int remainingRetries) {
super(originalError.getMessage(), originalError);
mRemainingRetries = remainingRetries;
}
/**
* 获取剩余重试次数
*/
public int getRemainingRetries() {
return mRemainingRetries;
}
}
八、错误处理的最佳实践
8.1 统一错误处理框架
为了更好地管理和处理网络错误,建议创建一个统一的错误处理框架。这个框架可以包含以下几个组件:
- 自定义错误类型:根据业务需求创建特定的错误类型
- 错误处理器:统一处理各种类型的错误
- 错误日志记录器:记录详细的错误信息
- 全局错误监听器:为所有请求提供统一的错误处理逻辑
- 错误码映射表:将服务器返回的错误码映射为友好的错误信息
下面是一个简单的统一错误处理框架的实现示例:
/**
* 统一错误处理框架
*/
public class VolleyErrorHandler {
private static VolleyErrorHandler sInstance;
// 上下文对象
private Context mContext;
// 错误处理器
private ErrorProcessor mErrorProcessor;
// 错误日志记录器
private ErrorLogger mErrorLogger;
// 错误码映射表
private Map<Integer, String> mErrorCodeMap;
private VolleyErrorHandler(Context context) {
mContext = context.getApplicationContext();
// 初始化错误日志记录器
mErrorLogger = new ErrorLogger(mContext, true, true);
// 初始化错误处理器
mErrorProcessor = new CustomErrorProcessor(mContext, mErrorLogger);
// 初始化错误码映射表
initErrorCodeMap();
}
/**
* 获取单例实例
*/
public static synchronized VolleyErrorHandler getInstance(Context context) {
if (sInstance == null) {
sInstance = new VolleyErrorHandler(context);
}
return sInstance;
}
/**
* 初始化错误码映射表
*/
private void initErrorCodeMap() {
mErrorCodeMap = new HashMap<>();
mErrorCodeMap.put(400, "错误的请求");
mErrorCodeMap.put(401, "未授权,请登录");
mErrorCodeMap.put(403, "禁止访问");
mErrorCodeMap.put(404, "资源未找到");
mErrorCodeMap.put(500, "服务器内部错误");
mErrorCodeMap.put(503, "服务不可用");
mErrorCodeMap.put(504, "网关超时");
// 添加业务相关的错误码
mErrorCodeMap.put(1001, "用户名或密码错误");
mErrorCodeMap.put(1002, "账号已被封禁");
mErrorCodeMap.put(1003, "验证码错误");
// 其他错误码...
}
/**
* 获取错误处理器
*/
public ErrorProcessor getErrorProcessor() {
return mErrorProcessor;
}
/**
* 获取错误日志记录器
*/
public ErrorLogger getErrorLogger() {
return mErrorLogger;
}
/**
* 根据错误码获取错误信息
*/
public String getErrorMessage(int errorCode) {
return mErrorCodeMap.getOrDefault(errorCode, "未知错误");
}
/**
* 创建全局错误监听器
*/
public GlobalErrorListener createGlobalErrorListener(Response.ErrorListener specificErrorListener) {
return new GlobalErrorListener(mContext, mErrorProcessor) {
@Override
protected void onSpecificError(VolleyError error) {
if (specificErrorListener != null) {
specificErrorListener.onErrorResponse(error);
}
}
};
}
}
8.2 使用示例
下面是一个使用统一错误处理框架的示例:
// 初始化请求队列
RequestQueue requestQueue = Volley.newRequestQueue(context);
// 创建请求
StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// 处理成功响应
}
},
// 使用全局错误监听器
VolleyErrorHandler.getInstance(context).createGlobalErrorListener(
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理特定于这个请求的错误
Log.e("MyApp", "Specific error: " + error.getMessage());
}
}
)
);
// 设置重试策略
request.setRetryPolicy(new CustomRetryPolicy(
3000, // 初始超时时间3秒
2, // 最大重试次数2次
1.5f // 退避乘数1.5
));
// 将请求添加到请求队列
requestQueue.add(request);
8.3 错误处理的其他建议
- 用户友好的错误提示:向用户展示友好的错误提示,避免直接显示技术细节
- 错误分类处理:根据错误类型进行不同的处理,例如网络错误、服务器错误、解析错误等
- 错误上报机制:将严重错误上报到服务器,便于开发者分析和修复
- 本地错误缓存:缓存最近的错误信息,方便用户查看和反馈
- 错误恢复机制:对于一些可恢复的错误,提供恢复选项,例如重新加载、重新登录等
九、Volley错误处理源码深度解析
9.1 NetworkDispatcher源码分析
NetworkDispatcher
是Volley中负责执行网络请求的核心类之一,它从请求队列中取出请求并执行,处理网络响应和错误。下面是其关键部分的源码分析:
/**
* 网络调度器,负责从请求队列中取出请求并执行
*/
public class NetworkDispatcher extends Thread {
// 请求队列
private final BlockingQueue<Request<?>> mQueue;
// 网络接口
private final Network mNetwork;
// 缓存接口
private final Cache mCache;
// 响应分发器
private final ResponseDelivery mDelivery;
// 是否停止
private volatile boolean mQuit = false;
/**
* 创建一个新的网络调度器
*/
public NetworkDispatcher(BlockingQueue<Request<?>> queue,
Network network, Cache cache,
ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery;
}
/**
* 停止调度器
*/
public void quit() {
mQuit = true;
interrupt();
}
@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;
}
// 设置请求的重试策略
request.addMarker("network-http-attempt");
// 执行网络请求
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 (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);
}
}
}
/**
* 解析并分发网络错误
*/
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
// 让请求尝试处理错误(例如重试)
error = request.parseNetworkError(error);
// 分发错误到主线程
mDelivery.postError(request, error);
}
}
从上面的源码可以看出,NetworkDispatcher
在线程的运行循环中不断从请求队列中取出请求并执行。在执行过程中,如果发生错误,会捕获VolleyError
或其他异常,并通过parseAndDeliverNetworkError
方法处理错误,最终通过响应分发器将错误分发到主线程。
9.2 Request源码分析
Request
类是所有请求的基类,包含了错误处理的核心逻辑。下面是其关键部分的源码分析:
/**
* 请求的基类
*/
public abstract class Request<T> implements Comparable<Request<T>> {
// 请求的优先级
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
// 请求的唯一标识
private final int mSerialNumber;
// 请求的URL
private final String mUrl;
// 请求的重试策略
private RetryPolicy mRetryPolicy;
// 请求的标签
private Object mTag;
// 请求的优先级
private Priority mPriority = Priority.NORMAL;
// 请求是否被取消
private boolean mCanceled = false;
// 请求是否已交付响应
private boolean mResponseDelivered = false;
// 响应监听器
private final Response.ErrorListener mErrorListener;
// 请求的开始时间
private long mRequestBirthTime = 0;
// 请求的重试次数
private int mCurrentRetryCount = 0;
// 其他成员变量...
/**
* 创建一个新的请求
*/
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
mSerialNumber = sNextRequestNumber.getAndIncrement();
mRetryPolicy = new DefaultRetryPolicy();
mRequestBirthTime = SystemClock.elapsedRealtime();
}
// 其他构造函数...
/**
* 设置重试策略
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
mRetryPolicy = retryPolicy;
}
/**
* 获取重试策略
*/
public RetryPolicy getRetryPolicy() {
return mRetryPolicy;
}
/**
* 解析网络错误
*/
public VolleyError parseNetworkError(VolleyError volleyError) {
return volleyError;
}
/**
* 处理网络错误
*/
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
/**
* 尝试重试请求
*/
public void attemptRetryOnError(ResponseDelivery delivery, VolleyError error) {
mCurrentRetryCount++;
// 获取重试策略
RetryPolicy retryPolicy = getRetryPolicy();
try {
// 调用重试策略的retry方法
retryPolicy.retry(error);
} catch (VolleyError e) {
// 如果重试策略决定不再重试,分发错误
setRetryPolicy(new DefaultRetryPolicy()); // 重置重试策略
delivery.postError(this, e);
return;
}
// 如果需要重试,将请求重新添加到队列
delivery.postRetry(this);
}
// 其他方法...
}
从上面的源码可以看出,Request
类提供了错误处理的基本框架:
parseNetworkError
方法:用于解析和处理网络错误,可以被子类重写deliverError
方法:将错误传递给错误监听器attemptRetryOnError
方法:尝试根据重试策略进行重试
9.3 ResponseDelivery源码分析
ResponseDelivery
接口定义了响应分发的方法,包括成功响应和错误响应的分发:
/**
* 响应分发接口
*/
public interface ResponseDelivery {
/**
* 分发成功响应
*/
void postResponse(Request<?> request, Response<?> response);
/**
* 分发成功响应,并在完成后执行回调
*/
void postResponse(Request<?> request, Response<?> response, Runnable runnable);
/**
* 分发错误响应
*/
void postError(Request<?> request, VolleyError error);
/**
* 分发重试请求
*/
void postRetry(Request<?> request);
}
ExecutorDelivery
是ResponseDelivery
的默认实现类,负责将响应和错误分发到主线程:
/**
* 使用Executor将响应分发到主线程的实现类
*/
public class ExecutorDelivery implements ResponseDelivery {
// 主线程的Executor
private final Executor mResponsePoster;
/**
* 创建一个新的ExecutorDelivery
*/
public ExecutorDelivery(final Handler handler) {
// 创建一个在主线程执行的Executor
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
/**
* 创建一个新的ExecutorDelivery,使用提供的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");
// 设置错误的请求
error.setRequest(request);
// 创建并执行分发错误的Runnable
mResponsePoster.execute(new ResponseDeliveryRunnable(request, error));
}
@Override
public void postRetry(Request<?> request) {
request.addMarker("post-retry");
// 创建并执行重试的Runnable
mResponsePoster.execute(new ResponseDeliveryRunnable(request));
}
/**
* 响应分发的Runnable
*/
@SuppressWarnings("rawtypes")
private class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final VolleyError mError;
private final Runnable mRunnable;
private final boolean mIsRetry;
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
mRequest = request;
mResponse = response;
mError = null;
mRunnable = runnable;
mIsRetry = false;
}
public ResponseDeliveryRunnable(Request request, VolleyError error) {
mRequest = request;
mResponse = null;
mError = error;
mRunnable = null;
mIsRetry = false;
}
public ResponseDeliveryRunnable(Request request) {
mRequest = request;
mResponse = null;
mError = null;
mRunnable = null;
mIsRetry = true;
}
@Override
public void run() {
// 如果请求已被取消,不执行任何操作
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
if (mIsRetry) {
// 处理重试请求
mRequestQueue.add(mRequest);
return;
}
if (mResponse != null) {
// 处理成功响应
mRequest.deliverResponse(mResponse.result);
// 如果有回调,执行回调
if (mRunnable != null) {
mRunnable.run();
}
} else {
// 处理错误响应
mRequest.deliverError(mError);
}
// 如果请求应该被缓存,执行缓存相关操作
if (mResponse != null && mResponse.isSuccess()) {
mRequest.finish("done");
}
}
}
}
从上面的源码可以看出,ExecutorDelivery
通过Handler
将响应和错误分发到主线程执行。当收到错误响应时,会调用Request
的deliverError
方法,最终将错误传递给我们设置的错误监听器。
十、总结与展望
10.1 总结
通过本文的分析,我们深入了解了Android Volley库的自定义错误处理逻辑。从错误类的层次结构到错误处理的核心流程,从自定义错误类型到全局错误监听器,从错误日志记录到错误重试机制,我们全面掌握了Volley错误处理的各个方面。
主要内容包括:
- Volley错误处理的基础,包括错误类层次结构和基本流程
- 自定义错误类型的实现方法,以及如何在请求中使用
- 自定义错误处理器的设计和实现,用于统一处理各种错误
- 全局错误监听器的实现,避免代码冗余,实现统一的错误处理逻辑
- 错误日志记录的重要性和实现方法,帮助我们快速定位和解决问题
- 错误重试机制的实现,提高请求的成功率
- 错误处理的最佳实践,包括统一错误处理框架的设计
- Volley错误处理源码的深度分析,了解其内部工作原理
通过合理运用这些技术,我们可以在Android应用中实现更加健壮、灵活和高效的网络错误处理机制,提升应用的稳定性和用户体验。
10.2 展望
随着移动互联网技术的不断发展,网络请求在Android应用中的地位越来越重要。未来,Volley的错误处理机制可能会朝着以下方向发展:
- 更加智能化的错误处理:利用人工智能和机器学习技术,分析错误模式,自动提供更精准的错误解决方案
- 更完善的错误监控和分析:集成更多的错误监控工具,提供更详细的错误分析报告,帮助开发者快速定位和解决问题
- 更简洁的API设计:简化错误处理的API,降低开发者的使用门槛
- 更好的与其他库集成:与其他网络库、依赖注入框架等更好地集成,提供更统一的错误处理体验
- 更强大的错误恢复机制:提供更强大的错误恢复机制,帮助应用在遇到错误时能够自动恢复,减少对用户的影响
总之,随着技术的不断进步,Android Volley的错误处理机制也会不断完善和发展,为开发者提供更加便捷、高效的错误处理解决方案。