彻底搞懂Android Volley成功响应的解析处理:从源码到实战的全面解析
一、引言
在Android开发中,网络请求是必不可少的一部分。而Volley作为Android官方推荐的网络请求库,凭借其简洁的API和高效的性能,受到了广大开发者的喜爱。当我们发送一个网络请求后,服务器会返回响应数据,而如何正确地解析这些响应数据就成为了关键。
本文将深入剖析Android Volley库中成功响应的解析处理机制,从源码级别详细分析响应解析的各个关键环节。通过本文的学习,你将全面掌握Volley响应解析的实现方法,理解其内部工作机制,从而在开发中更加灵活地处理各种复杂的网络响应场景。
二、Volley响应解析概述
2.1 响应解析的基本概念
响应解析是指将从服务器接收到的原始数据(如字节流、JSON字符串、XML等)转换为应用程序可以直接使用的数据结构(如Java对象、字符串等)的过程。在Android开发中,响应解析是网络请求处理的重要环节,它直接影响到应用程序对服务器数据的处理效率和准确性。
2.2 Volley响应解析的流程
在Volley中,响应解析的基本流程如下:
- 网络请求发送后,服务器返回响应数据
- Volley将响应数据封装到NetworkResponse对象中
- NetworkResponse对象被传递给Request的parseNetworkResponse方法进行解析
- parseNetworkResponse方法将原始数据解析为应用程序需要的类型,并封装到Response对象中
- Response对象通过ResponseDelivery机制传递到主线程
- 在主线程中,Response对象被传递给我们设置的响应监听器进行处理
下面我们将详细分析这个流程中的每一个环节。
三、NetworkResponse类解析
3.1 NetworkResponse类的作用
NetworkResponse类是Volley中用于封装网络响应数据的核心类。它包含了从服务器接收到的原始数据、HTTP状态码、响应头等信息。
3.2 NetworkResponse类的源码分析
让我们来看一下NetworkResponse类的源码:
/**
* 封装从网络接收到的响应数据。
*/
public class NetworkResponse {
/** 响应状态码 */
public final int statusCode;
/** 响应数据的字节数组 */
public final byte[] data;
/** 响应头 */
public final Map<String, String> headers;
/** 是否从缓存中获取的响应 */
public final boolean notModified;
/** 网络请求的耗时,单位为毫秒 */
public final long networkTimeMs;
/**
* 创建一个新的NetworkResponse实例。
*
* @param statusCode HTTP状态码
* @param data 响应数据的字节数组
* @param headers 响应头
* @param notModified 是否未修改(HTTP 304状态)
* @param networkTimeMs 网络请求的耗时
*/
public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
boolean notModified, long networkTimeMs) {
this.statusCode = statusCode;
this.data = data;
this.headers = headers;
this.notModified = notModified;
this.networkTimeMs = networkTimeMs;
}
/**
* 创建一个新的NetworkResponse实例,不包含耗时信息。
*
* @param statusCode HTTP状态码
* @param data 响应数据的字节数组
* @param headers 响应头
* @param notModified 是否未修改(HTTP 304状态)
*/
public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
boolean notModified) {
this(statusCode, data, headers, notModified, 0);
}
/**
* 创建一个新的NetworkResponse实例,用于成功响应。
*
* @param data 响应数据的字节数组
* @param headers 响应头
*/
public NetworkResponse(byte[] data, Map<String, String> headers) {
this(HttpStatus.SC_OK, data, headers, false, 0);
}
/**
* 创建一个新的NetworkResponse实例,用于成功响应,不包含响应头。
*
* @param data 响应数据的字节数组
*/
public NetworkResponse(byte[] data) {
this(HttpStatus.SC_OK, data, Collections.<String, String>emptyMap(), false, 0);
}
}
从上面的源码可以看出,NetworkResponse类包含了以下几个重要的成员变量:
statusCode:HTTP状态码,如200表示成功,404表示未找到资源data:响应数据的字节数组,包含了服务器返回的原始数据headers:响应头,包含了服务器返回的各种元数据notModified:一个布尔值,表示资源是否未被修改(对应HTTP 304状态)networkTimeMs:网络请求的耗时,单位为毫秒
3.3 NetworkResponse的创建过程
在Volley中,NetworkResponse对象是在网络请求完成后创建的。具体来说,是在BasicNetwork类的performRequest方法中创建的:
/**
* 执行网络请求并返回响应。
*/
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
// 循环处理请求,支持重试机制
while (true) {
HttpURLConnection connection = null;
InputStream inputStream = null;
NetworkResponse networkResponse = null;
try {
// 准备请求头
Map<String, String> headers = new HashMap<>();
addCacheHeaders(headers, request.getCacheEntry());
// 添加请求自定义的请求头
headers.putAll(request.getHeaders());
// 打开HTTP连接
connection = openConnection(request, headers);
// 设置连接和读取超时时间
setConnectionParametersForRequest(connection, request);
// 执行请求并获取响应码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
// 如果返回304状态码,表示资源未修改
Cache.Entry entry = request.getCacheEntry();
if (entry == null) {
// 如果没有缓存条目,返回一个空的响应
return new NetworkResponse(HttpURLConnection.HTTP_NOT_FOUND, null,
headers, true, SystemClock.elapsedRealtime() - requestStart);
}
// 返回缓存的响应
return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED,
entry.data, headers, true,
SystemClock.elapsedRealtime() - requestStart);
}
// 读取响应数据
inputStream = connection.getInputStream();
byte[] responseContents = null;
Map<String, String> responseHeaders = convertHeaders(connection.getHeaderFields());
if (responseCode == HttpURLConnection.HTTP_OK) {
// 如果是200状态码,读取响应内容
responseContents = entityToBytes(connection.getInputStream(), connection.getContentLength());
} else {
// 如果是其他状态码,读取错误内容
responseContents = entityToBytes(connection.getErrorStream(), connection.getContentLength());
}
// 创建并返回NetworkResponse对象
networkResponse = new NetworkResponse(responseCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
// 处理重定向
if (request.shouldRetryServerErrors() && isRetriableStatusCode(responseCode)) {
attemptRetryOnException("server", request,
new ServerError(networkResponse));
}
return networkResponse;
} 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);
}
} finally {
// 关闭资源
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
VolleyLog.v("Error closing stream");
}
}
if (connection != null) {
connection.disconnect();
}
}
}
}
从上面的代码可以看出,NetworkResponse对象是在网络请求成功后,根据HTTP状态码、响应数据和响应头创建的。如果请求成功(状态码为200),则读取响应内容并创建包含响应数据的NetworkResponse对象;如果请求失败,则读取错误内容并创建包含错误信息的NetworkResponse对象。
四、Response类解析
4.1 Response类的作用
Response类是Volley中用于封装解析后响应数据的核心类。它包含了解析后的数据、缓存条目等信息。
4.2 Response类的源码分析
让我们来看一下Response类的源码:
/**
* 封装解析后的响应数据和可能的缓存条目。
*/
public class Response<T> {
/** 解析后的响应数据 */
public final T result;
/** 错误信息,如果请求失败 */
public final VolleyError error;
/** 缓存条目,如果响应应该被缓存 */
public final Cache.Entry cacheEntry;
/** 是否从缓存中获取的响应 */
public final boolean intermediate;
/**
* 响应成功的回调接口
*/
public interface Listener<T> {
/**
* 当请求成功时调用
*
* @param response 解析后的响应数据
*/
void onResponse(T response);
}
/**
* 响应失败的回调接口
*/
public interface ErrorListener {
/**
* 当请求失败时调用
*
* @param error 错误信息
*/
void onErrorResponse(VolleyError error);
}
/**
* 私有构造函数,使用静态工厂方法创建实例
*/
private Response(T result, Cache.Entry cacheEntry, boolean intermediate) {
this.result = result;
this.error = null;
this.cacheEntry = cacheEntry;
this.intermediate = intermediate;
}
/**
* 私有构造函数,使用静态工厂方法创建实例
*/
private Response(VolleyError error) {
this.result = null;
this.error = error;
this.cacheEntry = null;
this.intermediate = false;
}
/**
* 判断响应是否成功
*/
public boolean isSuccess() {
return error == null;
}
/**
* 创建一个成功的响应
*
* @param result 解析后的响应数据
* @param cacheEntry 缓存条目
*/
public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
return new Response<>(result, cacheEntry, false);
}
/**
* 创建一个中间响应(例如,用于流式响应)
*
* @param result 解析后的响应数据
* @param cacheEntry 缓存条目
*/
public static <T> Response<T> intermediate(T result, Cache.Entry cacheEntry) {
return new Response<>(result, cacheEntry, true);
}
/**
* 创建一个失败的响应
*
* @param error 错误信息
*/
public static <T> Response<T> error(VolleyError error) {
return new Response<>(error);
}
}
从上面的源码可以看出,Response类是一个泛型类,其泛型参数T表示解析后的数据类型。它包含了以下几个重要的成员变量:
result:解析后的响应数据,类型为Terror:错误信息,如果请求失败cacheEntry:缓存条目,如果响应应该被缓存intermediate:一个布尔值,表示是否为中间响应(例如,用于流式响应)
Response类还定义了两个重要的内部接口:Listener和ErrorListener,分别用于处理成功和失败的响应。
4.3 Response对象的创建
在Volley中,Response对象是在Request的parseNetworkResponse方法中创建的。具体来说,不同类型的Request会根据自己的需求创建不同类型的Response对象。
例如,StringRequest的parseNetworkResponse方法会创建一个包含字符串响应的Response对象:
/**
* 解析网络响应为字符串
*/
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
// 根据响应头中的字符集解析响应数据
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
// 如果字符集不支持,使用默认字符集
parsed = new String(response.data);
}
// 返回成功响应,包含解析后的字符串和缓存条目
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
而JsonObjectRequest的parseNetworkResponse方法会创建一个包含JSONObject的Response对象:
/**
* 解析网络响应为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));
}
}
五、parseNetworkResponse方法解析
5.1 parseNetworkResponse方法的作用
parseNetworkResponse方法是Request类中的一个抽象方法,用于将从服务器接收到的原始响应数据解析为应用程序需要的类型。所有具体的Request类都必须实现这个方法。
5.2 parseNetworkResponse方法的源码分析
Request类中的parseNetworkResponse方法定义如下:
/**
* 解析网络响应数据。
* 子类必须重写此方法来解析网络响应数据并返回解析后的结果。
*
* @param response 从网络接收到的响应
* @return 包含解析结果的Response对象
*/
protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
从上面的代码可以看出,parseNetworkResponse方法接收一个NetworkResponse对象作为参数,并返回一个Response对象。具体的解析逻辑由子类实现。
5.3 不同Request子类的parseNetworkResponse实现
Volley提供了多个内置的Request子类,每个子类都有自己的parseNetworkResponse实现。下面我们来看几个常见的Request子类的实现:
5.3.1 StringRequest的实现
/**
* StringRequest将响应数据解析为字符串
*/
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
// 根据响应头中的字符集解析响应数据
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
// 如果字符集不支持,使用默认字符集
parsed = new String(response.data);
}
// 返回成功响应,包含解析后的字符串和缓存条目
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
5.3.2 JsonObjectRequest的实现
/**
* 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));
}
}
5.3.3 JsonArrayRequest的实现
/**
* JsonArrayRequest将响应数据解析为JSONArray
*/
@Override
protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
try {
// 将响应数据转换为字符串
String jsonString = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
// 将字符串解析为JSONArray
return Response.success(
new JSONArray(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// 处理编码异常
return Response.error(new ParseError(e));
} catch (JSONException je) {
// 处理JSON解析异常
return Response.error(new ParseError(je));
}
}
5.3.4 ImageRequest的实现
/**
* ImageRequest将响应数据解析为Bitmap
*/
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
// 同步锁,确保BitmapFactory.Options只被配置一次
synchronized (sDecodeLock) {
try {
// 解码响应数据为Bitmap
return Response.success(
decodeBitmap(response.data),
HttpHeaderParser.parseCacheHeaders(response));
} catch (OutOfMemoryError e) {
// 处理内存溢出错误
VolleyLog.e("Caught OOM for %d byte image, url=%s",
response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
/**
* 解码字节数组为Bitmap
*/
private Bitmap decodeBitmap(byte[] data) {
// 如果没有指定最大宽度和高度,直接解码
if (mMaxWidth <= 0 && mMaxHeight <= 0) {
return BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions);
}
// 先获取图片尺寸,不加载整个图片
mDecodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions);
int actualWidth = mDecodeOptions.outWidth;
int actualHeight = mDecodeOptions.outHeight;
// 计算目标尺寸
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth);
// 计算inSampleSize
mDecodeOptions.inSampleSize = calculateInSampleSize(actualWidth, actualHeight,
desiredWidth, desiredHeight);
// 加载实际图片
mDecodeOptions.inJustDecodeBounds = false;
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions);
// 如果需要缩放图片到精确尺寸
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
return bitmap;
} else {
return tempBitmap;
}
}
六、ResponseDelivery机制解析
6.1 ResponseDelivery接口的作用
ResponseDelivery接口定义了将响应数据传递到主线程的方法。它是Volley中响应分发机制的核心接口。
6.2 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);
}
从上面的代码可以看出,ResponseDelivery接口定义了三个方法:
postResponse(Request<?>, Response<?>):将响应传递到主线程postResponse(Request<?>, Response<?>, Runnable):将响应传递到主线程,并在传递完成后执行回调postError(Request<?>, VolleyError):将错误传递到主线程
6.3 ResponseDelivery接口的实现类
Volley提供了一个默认的ResponseDelivery接口实现类: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类使用一个Executor来将响应和错误传递到主线程。在创建ExecutorDelivery实例时,我们可以传入一个Handler,这样所有的响应和错误都会通过这个Handler在主线程执行。
6.4 ResponseDelivery的调用流程
在Volley中,ResponseDelivery的调用发生在NetworkDispatcher类的run方法中。当网络请求完成并解析后,会调用ResponseDelivery的postResponse方法将响应传递到主线程:
/**
* 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 (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);
}
}
}
七、deliverResponse方法解析
7.1 deliverResponse方法的作用
deliverResponse方法是Request类中的一个抽象方法,用于将解析后的响应数据传递给响应监听器。这个方法在主线程中被调用。
7.2 deliverResponse方法的源码分析
Request类中的deliverResponse方法定义如下:
/**
* 将解析后的响应数据传递给响应监听器。
* 子类可以重写此方法来实现自定义的响应分发逻辑。
*
* @param response 解析后的响应数据
*/
protected abstract void deliverResponse(T response);
从上面的代码可以看出,deliverResponse方法接收一个泛型参数T,表示解析后的响应数据。具体的响应分发逻辑由子类实现。
7.3 不同Request子类的deliverResponse实现
Volley提供的内置Request子类都实现了deliverResponse方法,将解析后的响应数据传递给我们设置的响应监听器。
下面是几个常见的Request子类的实现:
7.3.1 StringRequest的实现
/**
* StringRequest的响应分发方法
*/
@Override
protected void deliverResponse(String response) {
// 调用响应监听器的onResponse方法
mListener.onResponse(response);
}
7.3.2 JsonObjectRequest的实现
/**
* JsonObjectRequest的响应分发方法
*/
@Override
protected void deliverResponse(JSONObject response) {
// 调用响应监听器的onResponse方法
mListener.onResponse(response);
}
7.3.3 JsonArrayRequest的实现
/**
* JsonArrayRequest的响应分发方法
*/
@Override
protected void deliverResponse(JSONArray response) {
// 调用响应监听器的onResponse方法
mListener.onResponse(response);
}
7.3.4 ImageRequest的实现
/**
* ImageRequest的响应分发方法
*/
@Override
protected void deliverResponse(Bitmap response) {
// 调用响应监听器的onResponse方法
mListener.onResponse(response);
}
八、自定义响应解析器
8.1 为什么需要自定义响应解析器
虽然Volley提供了多种内置的Request类,可以处理常见的响应格式(如字符串、JSON、图片等),但在实际开发中,我们可能会遇到一些特殊的响应格式,或者需要对响应数据进行特殊处理。这时,就需要我们自定义响应解析器。
8.2 如何实现自定义响应解析器
实现自定义响应解析器的关键是创建一个继承自Request类的子类,并重写parseNetworkResponse方法。
下面是一个自定义响应解析器的示例,用于解析XML格式的响应数据:
/**
* 自定义XML请求类,用于解析XML格式的响应数据
*/
public class XmlRequest extends Request<Document> {
private final Response.Listener<Document> mListener;
/**
* 创建一个新的XmlRequest实例
*
* @param method 请求方法
* @param url 请求的URL
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public XmlRequest(int method, String url, Response.Listener<Document> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* 获取请求体的内容类型
*/
@Override
public String getBodyContentType() {
return "application/xml; charset=utf-8";
}
/**
* 解析网络响应数据为XML Document
*/
@Override
protected Response<Document> parseNetworkResponse(NetworkResponse response) {
try {
// 将响应数据转换为InputStream
ByteArrayInputStream inputStream = new ByteArrayInputStream(response.data);
// 创建DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 创建DocumentBuilder
DocumentBuilder builder = factory.newDocumentBuilder();
// 解析XML数据为Document
Document document = builder.parse(inputStream);
// 返回成功响应
return Response.success(document, HttpHeaderParser.parseCacheHeaders(response));
} catch (ParserConfigurationException e) {
// 处理解析器配置异常
return Response.error(new ParseError(e));
} catch (SAXException e) {
// 处理SAX解析异常
return Response.error(new ParseError(e));
} catch (IOException e) {
// 处理IO异常
return Response.error(new ParseError(e));
}
}
/**
* 将解析后的响应分发到主线程
*/
@Override
protected void deliverResponse(Document response) {
mListener.onResponse(response);
}
}
8.3 使用Gson等第三方库进行响应解析
除了手动解析响应数据外,我们还可以使用Gson、Jackson等第三方库来简化响应解析过程。
下面是一个使用Gson解析JSON响应的示例:
/**
* 基于Gson的自定义请求类,用于将JSON响应解析为Java对象
*/
public class GsonRequest<T> extends Request<T> {
private final Gson gson;
private final Class<T> clazz;
private final Response.Listener<T> listener;
/**
* 创建一个新的GsonRequest实例
*
* @param method 请求方法
* @param url 请求的URL
* @param clazz 响应数据类型
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public GsonRequest(int method, String url, Class<T> clazz,
Response.Listener<T> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
this.gson = new Gson();
this.clazz = clazz;
this.listener = listener;
}
/**
* 解析网络响应数据为Java对象
*/
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
// 将响应数据转换为字符串
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
// 使用Gson将JSON字符串解析为Java对象
T result = gson.fromJson(json, clazz);
// 返回成功响应
return Response.success(result,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// 处理编码异常
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
// 处理JSON语法异常
return Response.error(new ParseError(e));
}
}
/**
* 将解析后的响应分发到主线程
我们已经深入探讨了Volley成功响应解析处理的多个关键部分,接下来将从缓存处理、错误处理、性能优化以及与其他模块的关联等方面继续深入分析,进一步完善对Volley响应解析机制的理解。
九、响应解析与缓存处理的关联
9.1 缓存条目的生成与解析
在Volley中,当请求成功且响应可以被缓存时,会生成缓存条目(Cache.Entry)。这一过程与响应解析紧密相连,在parseNetworkResponse方法中解析响应数据的同时,也会处理缓存相关信息。
以StringRequest为例,在其parseNetworkResponse方法中,通过HttpHeaderParser.parseCacheHeaders(response)来解析缓存头信息,并生成缓存条目:
/**
* StringRequest将响应数据解析为字符串
*/
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
// 根据响应头中的字符集解析响应数据
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
// 如果字符集不支持,使用默认字符集
parsed = new String(response.data);
}
// 返回成功响应,包含解析后的字符串和缓存条目
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
HttpHeaderParser.parseCacheHeaders方法会从响应头中提取如Cache-Control、Expires等关键信息,构建Cache.Entry对象。其部分源码如下:
/**
* 解析缓存头信息,生成缓存条目
* @param headers 响应头
* @return 缓存条目
*/
public static Cache.Entry parseCacheHeaders(Map<String, String> headers) {
long now = System.currentTimeMillis();
Map<String, String> serverHeaders = new HashMap<>();
for (Map.Entry<String, String> entry : headers.entrySet()) {
String headerName = entry.getKey();
String headerValue = entry.getValue();
if (headerName != null && headerValue != null) {
serverHeaders.put(headerName.toLowerCase(), headerValue);
}
}
// 提取过期时间
long serverExpires = 0;
String serverExpiresStr = serverHeaders.get("expires");
if (serverExpiresStr != null) {
serverExpires = DateUtils.parseDateAsEpoch(serverExpiresStr);
}
// 提取缓存控制信息
String cacheControl = serverHeaders.get("cache-control");
if (cacheControl == null) {
cacheControl = "no-cache";
}
// 计算缓存有效时间
long softExpire = now;
long maxAge = 0;
if (cacheControl.contains("max-age=")) {
maxAge = Long.parseLong(cacheControl.substring(cacheControl.indexOf("max-age=") + 8));
softExpire = now + maxAge * 1000;
} else if (serverExpires > 0) {
softExpire = serverExpires;
}
// 创建缓存条目
Cache.Entry entry = new Cache.Entry();
entry.data = null;
entry.etag = serverHeaders.get("etag");
entry.softTtl = softExpire;
entry.ttl = softExpire;
entry.serverDate = serverExpires;
entry.responseHeaders = serverHeaders;
return entry;
}
通过这样的处理,将响应头中的缓存相关信息转换为可用于后续缓存管理的Cache.Entry对象。
9.2 缓存响应的使用与更新
当再次发起相同请求时,Volley会先检查缓存。如果缓存有效,直接使用缓存响应,而无需再次进行网络请求和响应解析。在NetworkDispatcher类中,处理请求时会判断缓存是否可用:
// 执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 如果服务器返回304状态码(未修改),并且我们有缓存条目,则使用缓存响应
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
当使用缓存响应时,会直接跳过网络请求和parseNetworkResponse的解析过程,将缓存中的数据传递给deliverResponse方法进行处理。
而当网络请求成功且响应可缓存时,会将新的响应数据和缓存条目写入缓存,实现缓存的更新。在NetworkDispatcher类中,若请求应缓存且有有效的缓存条目:
// 如果请求需要缓存,将响应写入缓存
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
通过这种方式,响应解析与缓存处理相互配合,提升了应用的性能和用户体验。
十、响应解析过程中的错误处理
10.1 解析异常的捕获与处理
在parseNetworkResponse方法中,由于涉及到数据格式转换、编码处理等操作,可能会出现各种异常。以JsonObjectRequest为例,在解析JSON数据时,可能会遇到UnsupportedEncodingException和JSONException:
/**
* 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));
}
}
当捕获到异常时,会创建一个ParseError类型的VolleyError对象,并通过Response.error方法创建包含错误信息的Response对象。这样,在后续的处理中,错误信息会被传递到主线程,由deliverError方法进行处理。
10.2 错误响应的传递与展示
在NetworkDispatcher类中,当解析过程中出现异常或网络请求失败时,会调用parseAndDeliverNetworkError方法处理错误:
catch (VolleyError volleyError) {
// 处理网络错误
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
}
parseAndDeliverNetworkError方法会进一步处理错误信息,并通过mDelivery.postError将错误传递到主线程:
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
error = mErrorProcessor.process(error);
mDelivery.postError(request, error);
}
在主线程中,ExecutorDelivery类的postError方法会被调用:
@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));
}
最终,ResponseDeliveryRunnable的run方法会调用request.deliverError(mResponse.error),将错误信息传递给我们设置的ErrorListener,在应用中展示错误提示,如:
// 创建请求时设置错误监听器
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) {
// 展示错误提示
Toast.makeText(context, "请求失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
}
});
十一、响应解析的性能优化
11.1 减少不必要的解析操作
在实际应用中,有些响应数据可能并非每次都需要完整解析。例如,当只需要获取响应中的部分字段时,可以在parseNetworkResponse方法中进行选择性解析,避免对整个响应数据进行复杂的解析操作。
以解析JSON数据为例,如果只需要获取其中的一个字段:
/**
* 自定义JSON请求类,只解析特定字段
*/
public class CustomJsonRequest extends Request<String> {
private final Response.Listener<String> mListener;
private static final String TARGET_FIELD = "specific_field";
public CustomJsonRequest(String url, Response.Listener<String> listener,
Response.ErrorListener errorListener) {
super(Request.Method.GET, url, errorListener);
mListener = listener;
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
JSONObject jsonObject = new JSONObject(jsonString);
// 只提取特定字段
String targetValue = jsonObject.optString(TARGET_FIELD);
return Response.success(targetValue, HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException je) {
return Response.error(new ParseError(je));
}
}
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
}
通过这种方式,减少了不必要的解析操作,提高了响应解析的效率。
11.2 合理使用内存
在响应解析过程中,尤其是处理较大的响应数据(如图片、大JSON数据等)时,内存管理至关重要。对于图片响应的解析,ImageRequest类在decodeBitmap方法中通过设置BitmapFactory.Options的inSampleSize来控制图片的加载尺寸,避免一次性加载过大的图片导致内存溢出:
/**
* 解码字节数组为Bitmap
*/
private Bitmap decodeBitmap(byte[] data) {
// 如果没有指定最大宽度和高度,直接解码
if (mMaxWidth <= 0 && mMaxHeight <= 0) {
return BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions);
}
// 先获取图片尺寸,不加载整个图片
mDecodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions);
int actualWidth = mDecodeOptions.outWidth;
int actualHeight = mDecodeOptions.outHeight;
// 计算目标尺寸
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth);
// 计算inSampleSize
mDecodeOptions.inSampleSize = calculateInSampleSize(actualWidth, actualHeight,
desiredWidth, desiredHeight);
// 加载实际图片
mDecodeOptions.inJustDecodeBounds = false;
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, mDecodeOptions);
// 如果需要缩放图片到精确尺寸
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
return bitmap;
} else {
return tempBitmap;
}
}
对于JSON等数据的解析,也可以及时释放不再使用的对象引用,避免内存占用过高。
十二、响应解析与Volley其他模块的交互
12.1 与请求队列的交互
请求队列(RequestQueue)负责管理和调度请求。在响应解析完成后,通过ResponseDelivery机制将响应数据传递到主线程,此时请求队列会标记请求已完成相关操作。在ExecutorDelivery类的ResponseDeliveryRunnable中:
@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();
}
}
通过mRequest.finish方法通知请求队列该请求已处理完毕,请求队列可以进行后续的调度和管理操作,如处理队列中的下一个请求。
12.2 与网络模块的协作
网络模块(Network)负责实际的网络请求发送和响应接收,获取到的NetworkResponse是响应解析的基础。在NetworkDispatcher类中,网络模块执行请求后返回NetworkResponse,然后将其传递给parseNetworkResponse方法进行解析:
// 执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 解析网络响应
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
网络模块获取到的响应数据质量、完整性等直接影响到响应解析的结果,两者紧密协作,共同完成网络请求到响应处理的整个流程。