探秘 Android Volley JsonObjectRequest:从源码到实践的深度解析
一、引言
在移动应用开发领域,网络请求是连接客户端与服务器的重要桥梁。对于 Android 开发者而言,Volley 框架因其高效、灵活的特性,成为处理网络请求的首选方案之一。而在实际开发中,JSON 数据格式因其简洁性和通用性,被广泛应用于服务器与客户端的数据交互。Volley 提供的 JsonObjectRequest
类,则专门用于处理 JSON 对象类型的网络请求,极大地简化了开发者与 JSON 数据的交互过程。
本文将从源码层面深入剖析 Android Volley 中 JsonObjectRequest
的实现原理和使用方法,带领读者一步步揭开其背后的神秘面纱。通过对 JsonObjectRequest
的详细分析,开发者能够更加深入地理解其工作机制,从而在实际项目中更加灵活、高效地使用这一强大工具。
二、JsonObjectRequest 类基础介绍
2.1 JsonObjectRequest 类定义与继承关系
JsonObjectRequest
是 Volley 框架中专门用于处理 JSON 对象请求的类,它继承自 JsonRequest<JSONObject>
类,而 JsonRequest
又继承自 Request<JSONObject>
类。这种继承关系使得 JsonObjectRequest
能够充分利用 Volley 框架的基础功能,并针对 JSON 对象请求进行了特定优化。
以下是 JsonObjectRequest
类的定义:
public class JsonObjectRequest extends JsonRequest<JSONObject> {
/**
* 创建一个新的 JsonObjectRequest 实例
*
* @param method HTTP 请求方法(GET、POST 等)
* @param url 请求的 URL
* @param jsonRequest 请求中包含的 JSON 对象,如果为 null 则表示没有请求体
* @param listener 请求成功的回调监听器
* @param errorListener 请求失败的回调监听器
*/
public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener) {
// 调用父类 JsonRequest 的构造函数
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(),
listener, errorListener);
}
/**
* 创建一个使用 GET 方法的 JsonObjectRequest 实例
*
* @param url 请求的 URL
* @param listener 请求成功的回调监听器
* @param errorListener 请求失败的回调监听器
*/
public JsonObjectRequest(String url, Listener<JSONObject> listener, ErrorListener errorListener) {
// 调用上面的构造函数,默认使用 GET 方法
this(Method.GET, url, null, listener, errorListener);
}
/**
* 创建一个使用 POST/PUT 方法的 JsonObjectRequest 实例
*
* @param url 请求的 URL
* @param jsonRequest 请求中包含的 JSON 对象,如果为 null 则表示没有请求体
* @param listener 请求成功的回调监听器
* @param errorListener 请求失败的回调监听器
* @param isPut 指示是否使用 PUT 方法,如果为 false 则使用 POST 方法
*/
public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,
ErrorListener errorListener, boolean isPut) {
// 根据 isPut 参数决定使用 PUT 还是 POST 方法
this(isPut ? Method.PUT : Method.POST, url, jsonRequest,
listener, errorListener);
}
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
try {
// 将响应数据解析为 JSON 对象
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
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));
}
}
}
从上述代码可以看出,JsonObjectRequest
类主要提供了三个构造函数,用于创建不同类型的 JSON 对象请求。同时,它重写了 parseNetworkResponse
方法,用于将网络响应数据解析为 JSON 对象。
2.2 JsonRequest 父类分析
JsonObjectRequest
的父类 JsonRequest
是一个抽象类,专门用于处理 JSON 类型的请求。它定义了 JSON 请求的基本行为和属性。
以下是 JsonRequest
类的部分源码:
public abstract class JsonRequest<T> extends Request<T> {
/** 默认的字符集 */
protected static final String PROTOCOL_CHARSET = "utf-8";
/** 默认的 Content-Type */
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json; charset=%s", PROTOCOL_CHARSET);
/** 请求的 JSON 数据 */
private final String mRequestBody;
/**
* 创建一个新的 JsonRequest 实例
*
* @param method HTTP 请求方法
* @param url 请求的 URL
* @param requestBody 请求的 JSON 数据,以字符串形式表示
* @param listener 请求成功的回调监听器
* @param errorListener 请求失败的回调监听器
*/
public JsonRequest(int method, String url, String requestBody,
Listener<T> listener, ErrorListener errorListener) {
// 调用父类 Request 的构造函数
super(method, url, errorListener);
// 设置请求成功的回调监听器
setListener(listener);
// 保存请求的 JSON 数据
this.mRequestBody = requestBody;
}
@Override
public String getBodyContentType() {
// 返回请求体的 Content-Type
return PROTOCOL_CONTENT_TYPE;
}
@Override
public byte[] getBody() {
try {
// 将请求的 JSON 字符串转换为字节数组
return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
} catch (UnsupportedEncodingException uee) {
// 记录不支持的字符集错误
VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
mRequestBody, PROTOCOL_CHARSET);
return null;
}
}
}
JsonRequest
类主要完成了以下工作:
- 定义了默认的字符集和 Content-Type,用于处理 JSON 数据。
- 在构造函数中接收并保存请求的 JSON 数据。
- 重写了
getBodyContentType
方法,返回 JSON 数据的 Content-Type。 - 重写了
getBody
方法,将 JSON 字符串转换为字节数组,作为请求体发送。
2.3 Request 基类分析
Request
类是 Volley 框架中所有请求的基类,它定义了请求的基本属性和行为。理解 Request
类对于深入理解 JsonObjectRequest
至关重要。
以下是 Request
类的部分关键源码:
public abstract class Request<T> implements Comparable<Request<T>> {
/** 请求的默认优先级 */
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
/** 请求的唯一标识 */
private final int mDefaultTrafficStatsTag;
/** 请求的 URL */
private final String mUrl;
/** 请求的重试策略 */
private RetryPolicy mRetryPolicy;
/** 请求的成功回调监听器 */
private Listener<T> mListener;
/** 请求的错误回调监听器 */
private ErrorListener mErrorListener;
/** 请求的序列号 */
private Integer mSequence;
/** 请求所属的请求队列 */
private RequestQueue mRequestQueue;
/** 请求是否已取消的标志 */
private boolean mCanceled = false;
/** 请求是否已发送响应的标志 */
private boolean mResponseDelivered = false;
/** 用于跟踪请求执行时间的标记集合 */
private final Map<String, Long> mEventTimes = new LinkedHashMap<>();
/** 请求的优先级,默认为 NORMAL */
private Priority mPriority = Priority.NORMAL;
/** 请求是否应该被缓存的标志 */
private boolean mShouldCache = true;
/** 请求是否是重试请求的标志 */
private boolean mIsFromCache = false;
// 其他成员变量...
/**
* 创建一个新的 Request 实例
*
* @param method HTTP 请求方法
* @param url 请求的 URL
* @param listener 请求成功的回调监听器
*/
public Request(int method, String url, ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
// 设置默认的流量统计标签
mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}
/**
* 获取请求的方法(GET、POST 等)
*/
public int getMethod() {
return mMethod;
}
/**
* 获取请求的 URL
*/
public String getUrl() {
return mUrl;
}
/**
* 获取请求的缓存键,默认为 URL
*/
public String getCacheKey() {
return getUrl();
}
/**
* 获取请求的优先级,默认为 NORMAL
*/
public Priority getPriority() {
return mPriority;
}
/**
* 设置请求的优先级
*/
public void setPriority(Priority priority) {
mPriority = priority;
}
/**
* 判断请求是否应该被缓存
*/
public boolean shouldCache() {
return mShouldCache;
}
/**
* 设置请求是否应该被缓存
*/
public void setShouldCache(boolean shouldCache) {
mShouldCache = shouldCache;
}
/**
* 取消请求
*/
public void cancel() {
mCanceled = true;
}
/**
* 判断请求是否已被取消
*/
public boolean isCanceled() {
return mCanceled;
}
/**
* 设置请求的重试策略
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
mRetryPolicy = retryPolicy;
}
/**
* 获取请求的重试策略
*/
public RetryPolicy getRetryPolicy() {
return mRetryPolicy;
}
/**
* 获取请求的 Content-Type,默认为 null
*/
public String getBodyContentType() {
return null;
}
/**
* 获取请求体,默认为 null
*/
public byte[] getBody() throws AuthFailureError {
return null;
}
/**
* 获取请求头,默认为空 Map
*/
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
/**
* 解析网络响应,由子类实现
*/
protected abstract Response<T> parseNetworkResponse(NetworkResponse response);
/**
* 分发响应结果
*/
protected void deliverResponse(T response) {
if (mListener != null) {
mListener.onResponse(response);
}
}
/**
* 分发错误结果
*/
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
// 其他方法...
}
Request
类定义了请求的核心属性和行为,包括:
- 请求的基本信息(URL、方法、优先级等)。
- 请求的状态管理(是否取消、是否已分发响应等)。
- 请求的重试策略管理。
- 请求头和请求体的获取方法。
- 抽象方法
parseNetworkResponse
,由子类实现,用于解析网络响应。 - 响应和错误的分发方法。
通过继承 Request
类,JsonObjectRequest
获得了处理网络请求的基本能力,并在此基础上针对 JSON 对象请求进行了特定优化。
三、创建 JsonObjectRequest 请求实例
3.1 使用 GET 方法创建 JsonObjectRequest
在实际开发中,最常见的场景之一是使用 GET 方法从服务器获取 JSON 数据。以下是使用 JsonObjectRequest
创建 GET 请求的示例:
// 定义请求的 URL
String url = "https://api.example.com/data";
// 创建 JsonObjectRequest 实例
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET, // 请求方法为 GET
url, // 请求的 URL
null, // GET 请求没有请求体,所以为 null
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 请求成功的回调处理
try {
// 从 JSON 对象中获取数据
String name = response.getString("name");
int age = response.getInt("age");
JSONArray hobbies = response.getJSONArray("hobbies");
// 处理获取的数据
Log.d(TAG, "Name: " + name);
Log.d(TAG, "Age: " + age);
// 遍历爱好数组
for (int i = 0; i < hobbies.length(); i++) {
Log.d(TAG, "Hobby " + i + ": " + hobbies.getString(i));
}
} catch (JSONException e) {
// 处理 JSON 解析异常
e.printStackTrace();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 请求失败的回调处理
Log.e(TAG, "Error: " + error.getMessage());
// 处理错误,例如显示错误信息给用户
}
}
);
在上述示例中,我们创建了一个使用 GET 方法的 JsonObjectRequest
实例。需要注意以下几点:
- 第一个参数指定请求方法为
Request.Method.GET
。 - 第二个参数是请求的 URL。
- 第三个参数是请求体,由于 GET 方法通常不包含请求体,所以传入
null
。 - 第四个参数是请求成功的回调监听器,用于处理服务器返回的 JSON 对象。
- 第五个参数是请求失败的回调监听器,用于处理请求过程中出现的错误。
3.2 使用 POST 方法创建 JsonObjectRequest
当需要向服务器提交数据时,通常使用 POST 方法。以下是使用 JsonObjectRequest
创建 POST 请求的示例:
// 定义请求的 URL
String url = "https://api.example.com/submit";
// 创建请求的 JSON 对象
JSONObject jsonRequest = new JSONObject();
try {
// 向 JSON 对象中添加数据
jsonRequest.put("username", "john_doe");
jsonRequest.put("email", "john.doe@example.com");
jsonRequest.put("age", 30);
// 创建爱好数组
JSONArray hobbies = new JSONArray();
hobbies.put("reading");
hobbies.put("swimming");
hobbies.put("coding");
// 将爱好数组添加到 JSON 对象中
jsonRequest.put("hobbies", hobbies);
} catch (JSONException e) {
e.printStackTrace();
}
// 创建 JsonObjectRequest 实例
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.POST, // 请求方法为 POST
url, // 请求的 URL
jsonRequest, // 请求体,包含 JSON 数据
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 请求成功的回调处理
try {
// 处理服务器返回的响应
boolean success = response.getBoolean("success");
String message = response.getString("message");
Log.d(TAG, "Success: " + success);
Log.d(TAG, "Message: " + message);
// 根据响应结果执行相应操作
if (success) {
// 处理成功情况
} else {
// 处理失败情况
}
} catch (JSONException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 请求失败的回调处理
Log.e(TAG, "Error: " + error.getMessage());
// 处理错误,例如显示错误信息给用户
}
}
) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
// 设置请求头,例如添加认证信息
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Authorization", "Bearer your_access_token");
return headers;
}
};
在上述示例中,我们创建了一个使用 POST 方法的 JsonObjectRequest
实例。需要注意以下几点:
- 第一个参数指定请求方法为
Request.Method.POST
。 - 第三个参数是请求体,传入一个包含提交数据的 JSON 对象。
- 通过重写
getHeaders
方法,可以设置请求头,例如添加认证信息或指定 Content-Type。
3.3 设置请求优先级和缓存策略
JsonObjectRequest
继承了 Request
类的属性和方法,因此可以设置请求的优先级和缓存策略。以下是一个示例:
// 定义请求的 URL
String url = "https://api.example.com/data";
// 创建 JsonObjectRequest 实例
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
) {
@Override
public Priority getPriority() {
// 设置请求优先级为 HIGH
return Priority.HIGH;
}
@Override
public boolean shouldCache() {
// 设置请求不使用缓存
return false;
}
};
在上述示例中,我们通过重写 getPriority
方法将请求优先级设置为 HIGH
,并通过重写 shouldCache
方法禁用了缓存。这样可以确保该请求优先处理,并且每次都从服务器获取最新数据。
3.4 添加请求参数
在某些情况下,需要在 URL 中添加查询参数。以下是一个示例:
// 定义基础 URL
String baseUrl = "https://api.example.com/search";
// 创建查询参数
String keyword = "android";
int page = 1;
int pageSize = 20;
// 构建包含查询参数的完整 URL
StringBuilder urlBuilder = new StringBuilder(baseUrl);
urlBuilder.append("?keyword=").append(URLEncoder.encode(keyword, "UTF-8"));
urlBuilder.append("&page=").append(page);
urlBuilder.append("&pageSize=").append(pageSize);
String url = urlBuilder.toString();
// 创建 JsonObjectRequest 实例
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 处理搜索结果
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
在上述示例中,我们通过 StringBuilder
和 URLEncoder
构建了包含查询参数的完整 URL。这样可以将参数传递给服务器,获取特定的数据。
四、将 JsonObjectRequest 添加到请求队列
4.1 创建 RequestQueue 实例
在使用 JsonObjectRequest
发送请求之前,需要先创建一个 RequestQueue
实例。RequestQueue
是 Volley 框架的核心组件之一,负责管理和调度网络请求。
以下是创建 RequestQueue
实例的几种常见方法:
// 方法一:使用 Volley 工具类创建默认的 RequestQueue
RequestQueue requestQueue = Volley.newRequestQueue(context);
// 方法二:自定义缓存大小和网络栈创建 RequestQueue
int cacheSize = 10 * 1024 * 1024; // 10 MB
File cacheDir = new File(context.getCacheDir(), "volley");
Cache cache = new DiskBasedCache(cacheDir, cacheSize);
Network network = new BasicNetwork(new HurlStack());
RequestQueue customRequestQueue = new RequestQueue(cache, network);
customRequestQueue.start();
// 方法三:在 Application 类中创建单例 RequestQueue
public class MyApplication extends Application {
private static RequestQueue mRequestQueue;
@Override
public void onCreate() {
super.onCreate();
mRequestQueue = Volley.newRequestQueue(this);
}
public static RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
throw new IllegalStateException("RequestQueue not initialized");
}
return mRequestQueue;
}
}
4.2 将请求添加到队列
创建 JsonObjectRequest
实例后,需要将其添加到 RequestQueue
中才能执行。以下是添加请求的示例:
// 创建 JsonObjectRequest 实例
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
// 将请求添加到 RequestQueue
requestQueue.add(jsonObjectRequest);
4.3 RequestQueue 的工作原理
RequestQueue
的核心工作是管理和调度网络请求。以下是 RequestQueue
的主要成员变量和工作流程:
public class RequestQueue {
// 缓存请求队列,用于存放需要先检查缓存的请求
private final BlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
// 网络请求队列,用于存放需要直接进行网络请求的请求
private final BlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
// 缓存实现
private final Cache mCache;
// 网络实现
private final Network mNetwork;
// 响应分发器
private final ResponseDelivery mDelivery;
// 缓存调度线程
private CacheDispatcher mCacheDispatcher;
// 网络调度线程数组
private NetworkDispatcher[] mDispatchers;
// 请求序列号生成器
private AtomicInteger mSequenceGenerator = new AtomicInteger();
// 当前正在处理的请求集合
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
// 构造函数和其他方法...
/**
* 将请求添加到请求队列
*/
public <T> Request<T> add(Request<T> request) {
// 将请求与当前请求队列关联
request.setRequestQueue(this);
// 将请求添加到正在处理的请求集合中
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// 为请求分配唯一的序列号
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// 如果请求不需要缓存,直接添加到网络请求队列
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
// 否则,将请求添加到缓存请求队列
mCacheQueue.add(request);
return request;
}
/**
* 启动请求队列
*/
public void start() {
// 停止任何正在运行的调度器
stop();
// 创建并启动缓存调度器
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// 创建并启动网络调度器
mDispatchers = new NetworkDispatcher[mDispatchers.length];
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(
mNetworkQueue, mNetwork, mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
/**
* 停止请求队列
*/
public void stop() {
// 停止缓存调度器
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
// 停止所有网络调度器
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
// 其他方法...
}
RequestQueue
的工作流程如下:
- 当调用
add
方法添加请求时,根据请求是否需要缓存,将其放入mCacheQueue
或mNetworkQueue
。 - 调用
start
方法启动缓存调度器(CacheDispatcher
)和网络调度器(NetworkDispatcher
)。 - 缓存调度器从
mCacheQueue
中取出请求,检查缓存中是否有对应数据。如果有且未过期,则直接返回缓存数据;否则,将请求放入mNetworkQueue
。 - 网络调度器从
mNetworkQueue
中取出请求,执行实际的网络请求,并处理响应。
4.4 取消请求
在某些情况下,需要取消已添加到队列中的请求。以下是取消请求的几种方法:
// 方法一:使用请求的 tag 取消特定请求
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
url,
null,
listener,
errorListener
);
// 设置请求的 tag
jsonObjectRequest.setTag("myRequestTag");
// 将请求添加到队列
requestQueue.add(jsonObjectRequest);
// 取消带有特定 tag 的请求
requestQueue.cancelAll("myRequestTag");
// 方法二:使用回调接口取消特定请求
requestQueue.cancelAll(new RequestQueue.RequestFilter() {
@Override
public boolean apply(Request<?> request) {
// 根据请求的某些属性决定是否取消
return request.getUrl().contains("example.com");
}
});
// 方法三:在 Activity/Fragment 的生命周期方法中取消所有请求
@Override
protected void onStop() {
super.onStop();
// 取消所有请求
if (requestQueue != null) {
requestQueue.cancelAll(this);
}
}
五、缓存调度器处理 JsonObjectRequest
5.1 CacheDispatcher 类概述
CacheDispatcher
是一个线程类,负责从缓存请求队列中取出请求,并检查缓存中是否有相应的数据。如果有且未过期,则直接返回缓存数据;否则,将请求转发到网络请求队列。
以下是 CacheDispatcher
类的核心代码:
public class CacheDispatcher extends Thread {
// 缓存请求队列
private final BlockingQueue<Request<?>> mCacheQueue;
// 网络请求队列
private final BlockingQueue<Request<?>> mNetworkQueue;
// 缓存实现
private final Cache mCache;
// 响应分发器
private final ResponseDelivery mDelivery;
// 线程停止标志
private volatile boolean mQuit = false;
/**
* 创建一个新的 CacheDispatcher 实例
*/
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue,
BlockingQueue<Request<?>> networkQueue,
Cache cache,
ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
/**
* 停止调度器
*/
public void quit() {
mQuit = true;
interrupt();
}
@Override
public void run() {
// 设置线程优先级为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化缓存
mCache.initialize();
// 循环处理请求,直到线程被要求退出
while (true) {
try {
// 从缓存请求队列中取出一个请求
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 如果请求已被取消,跳过处理
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 尝试从缓存中获取数据
Cache.Entry entry = mCache.get(request.getCacheKey());
// 如果缓存中没有数据,将请求转发到网络请求队列
if (entry == null) {
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}
// 检查缓存是否已过期
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 缓存命中且未过期,解析缓存数据
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
// 检查是否需要刷新缓存
if (entry.refreshNeeded()) {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// 标记响应为中间响应
response.intermediate = true;
// 将响应分发给监听器,并在分发完成后将请求转发到网络请求队列
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// 恢复中断状态
Thread.currentThread().interrupt();
}
}
});
} else {
// 不需要刷新缓存,直接将响应分发给监听器
mDelivery.postResponse(request, response);
}
} catch (InterruptedException e) {
// 如果线程被中断,检查是否需要退出
if (mQuit) {
Thread.currentThread().interrupt();
return;
}
VolleyLog.e("Ignoring spurious interrupt of CacheDispatcher thread; " +
"use quit() to terminate it");
}
}
}
}
5.2 缓存处理流程
当 JsonObjectRequest
被添加到缓存请求队列后,CacheDispatcher
的处理流程如下:
-
从队列中取出请求:
CacheDispatcher
从mCacheQueue
中取出一个请求。 -
检查请求是否已取消:如果请求已被取消,则跳过处理。
-
尝试获取缓存数据:根据请求的缓存键(通常是 URL)从缓存中获取数据。
-
缓存未命中处理:如果缓存中没有数据,将请求转发到网络请求队列。
-
缓存过期处理:如果缓存数据已过期,将请求转发到网络请求队列,但保留缓存数据,以便在网络请求期间使用。
-
缓存命中且未过期处理:
- 解析缓存数据,生成响应对象。
- 检查是否需要刷新缓存(例如,数据可能已过时但仍可使用)。
- 如果需要刷新缓存,将响应标记为中间响应,分发给监听器,并将请求转发到网络请求队列以获取最新数据。
- 如果不需要刷新缓存,直接将响应分发给监听器。
5.3 Cache 接口与 DiskBasedCache 实现
Volley 的缓存系统基于 Cache
接口,该接口定义了缓存的基本操作:
public interface Cache {
/**
* 从缓存中获取数据
* @param key 缓存键
* @return 缓存条目,如果不存在则返回 null
*/
public Entry get(String key);
/**
* 将数据存入缓存
* @param key 缓存键
* @param entry 缓存条目
*/
public void put(String key, Entry entry);
/**
* 刷新缓存条目
* @param key 缓存键
* @param softTtl 软过期时间(毫秒)
*/
public void invalidate(String key, boolean fullExpire);
/**
* 移除缓存条目
* @param key 缓存键
*/
public void remove(String key);
/**
* 清空缓存
*/
public void clear();
/**
* 缓存条目类,包含缓存数据和元数据
*/
public static class Entry {
/** 缓存数据 */
public byte[] data;
/** ETag 响应头 */
public String etag;
/** 服务器响应时间 */
public long serverDate;
/** 最后修改时间 */
public long lastModified;
/** 软过期时间 */
public long softTtl;
/** 绝对过期时间 */
public long ttl;
/** 响应头 */
public Map<String, String> responseHeaders = Collections.emptyMap();
/**
* 判断缓存是否已过期
*/
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
/**
* 判断缓存是否需要刷新
*/
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
}
}
Volley 默认的缓存实现是 DiskBasedCache
,它将缓存数据存储在文件系统中:
public class DiskBasedCache implements Cache {
// 缓存目录
private final File mRootDirectory;
// 缓存最大大小(字节)
private final int mMaxCacheSizeInBytes;
// 当前缓存大小(字节)
private long mTotalSize = 0;
// 缓存条目映射
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<String, CacheHeader>(16, 0.75f, true);
// 其他成员变量...
/**
* 初始化缓存
*/
@Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
5.3 Cache 接口与 DiskBasedCache 实现(续)
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache directory %s", mRootDirectory.getAbsolutePath());
return;
}
}
// 列出缓存目录中的所有文件
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
// 遍历所有文件,加载缓存头信息
for (File file : files) {
FileInputStream fis = null;
try {
// 跳过不是缓存文件的文件
if (file.getName().startsWith(TEMP_FILE_PREFIX)) {
file.delete();
continue;
}
fis = new FileInputStream(file);
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
// 关闭文件输入流
closeQuietly(fis);
}
}
}
/**
* 从缓存中获取数据
*/
@Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// 如果缓存头不存在,返回 null
if (entry == null) {
return null;
}
// 获取缓存文件
File file = getFileForKey(key);
FileInputStream fis = null;
try {
// 打开文件输入流
fis = new FileInputStream(file);
// 验证文件长度是否与缓存头中记录的一致
CountingInputStream cis = new CountingInputStream(fis);
CacheHeader.readHeader(cis);
// 读取缓存数据
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
// 返回缓存条目
return entry.toCacheEntry(data);
} catch (IOException e) {
// 发生异常时,删除缓存文件并从缓存头映射中移除
remove(key);
return null;
} finally {
// 关闭文件输入流
closeQuietly(fis);
}
}
/**
* 将数据存入缓存
*/
@Override
public synchronized void put(String key, Entry entry) {
// 确保缓存目录存在
pruneIfNeeded(entry.data.length);
// 获取缓存文件
File file = getFileForKey(key);
FileOutputStream fos = null;
try {
// 创建临时文件写入缓存数据
FileOutputStream tmpFos = new FileOutputStream(createTempFileForKey(key));
// 写入缓存头
CacheHeader e = new CacheHeader(key, entry);
e.writeHeader(tmpFos);
// 写入缓存数据
tmpFos.write(entry.data);
tmpFos.close();
// 将临时文件重命名为正式缓存文件
if (!tmpFos.getFD().sync()) {
throw new IOException("Failed to sync file output stream");
}
// 原子性地重命名临时文件
if (!createTempFileForKey(key).renameTo(file)) {
throw new IOException("Rename failed!");
}
// 更新缓存头映射和总大小
putEntry(key, e);
mTotalSize += file.length();
} catch (IOException e) {
// 发生异常时,删除临时文件
File tmpFile = getTempFileForKey(key);
if (tmpFile.exists()) {
tmpFile.delete();
}
return;
} finally {
// 关闭文件输出流
closeQuietly(fos);
}
}
/**
* 刷新缓存条目
*/
@Override
public synchronized void invalidate(String key, boolean fullExpire) {
CacheHeader entry = mEntries.get(key);
if (entry != null) {
// 根据 fullExpire 参数设置缓存的过期时间
if (fullExpire) {
entry.ttl = 0;
} else {
entry.softTtl = 0;
}
// 将更新后的缓存头写入文件
putEntry(key, entry);
}
}
/**
* 移除缓存条目
*/
@Override
public synchronized void remove(String key) {
boolean deleted = false;
CacheHeader entry = mEntries.get(key);
if (entry != null) {
// 删除缓存文件
File file = getFileForKey(key);
deleted = file.delete();
// 从缓存头映射中移除
if (deleted) {
mTotalSize -= entry.size;
} else {
VolleyLog.e("Could not delete cache entry for key=%s, filename=%s",
key, getFilenameForKey(key));
}
mEntries.remove(key);
}
}
/**
* 清空缓存
*/
@Override
public synchronized void clear() {
// 列出缓存目录中的所有文件并删除
File[] files = mRootDirectory.listFiles();
if (files != null) {
for (File file : files) {
file.delete();
}
}
// 重置缓存头映射和总大小
mEntries.clear();
mTotalSize = 0;
VolleyLog.d("Cache cleared.");
}
// 其他辅助方法...
}
5.4 缓存头信息类 CacheHeader
CacheHeader
类用于存储缓存条目的元数据,包括缓存键、ETag、服务器时间等信息:
/**
* 缓存头信息类,用于存储缓存条目的元数据
*/
static class CacheHeader {
/** 缓存键 */
public String key;
/** ETag 响应头 */
public String etag;
/** 服务器响应时间(毫秒) */
public long serverDate;
/** 最后修改时间(毫秒) */
public long lastModified;
/** 软过期时间(毫秒) */
public long softTtl;
/** 绝对过期时间(毫秒) */
public long ttl;
/** 响应头 */
public Map<String, String> responseHeaders;
/** 缓存条目大小 */
public long size;
// 其他成员变量和方法...
/**
* 从输入流中读取缓存头信息
*/
public static CacheHeader readHeader(InputStream is) throws IOException {
// 创建数据输入流
DataInputStream dis = new DataInputStream(new BufferedInputStream(is, 32));
try {
// 创建缓存头对象
CacheHeader entry = new CacheHeader();
// 读取缓存头的各个字段
entry.key = dis.readUTF();
entry.etag = dis.readUTF();
if (entry.etag.equals("")) {
entry.etag = null;
}
entry.serverDate = dis.readLong();
entry.lastModified = dis.readLong();
entry.softTtl = dis.readLong();
entry.ttl = dis.readLong();
// 读取响应头
int headerCount = dis.readInt();
entry.responseHeaders = new HashMap<String, String>(headerCount);
for (int i = 0; i < headerCount; i++) {
entry.responseHeaders.put(dis.readUTF(), dis.readUTF());
}
return entry;
} catch (EOFException e) {
// 处理文件末尾异常
return null;
} finally {
// 关闭数据输入流
closeQuietly(dis);
}
}
/**
* 将缓存头信息写入输出流
*/
public void writeHeader(OutputStream os) throws IOException {
// 创建数据输出流
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(os, 32));
try {
// 写入缓存头的各个字段
dos.writeUTF(key);
dos.writeUTF(etag == null ? "" : etag);
dos.writeLong(serverDate);
dos.writeLong(lastModified);
dos.writeLong(softTtl);
dos.writeLong(ttl);
// 写入响应头
dos.writeInt(responseHeaders.size());
for (Map.Entry<String, String> header : responseHeaders.entrySet()) {
dos.writeUTF(header.getKey());
dos.writeUTF(header.getValue());
}
} finally {
// 关闭数据输出流
closeQuietly(dos);
}
}
/**
* 将缓存头转换为完整的缓存条目
*/
public Entry toCacheEntry(byte[] data) {
Entry e = new Entry();
e.data = data;
e.etag = etag;
e.serverDate = serverDate;
e.lastModified = lastModified;
e.softTtl = softTtl;
e.ttl = ttl;
e.responseHeaders = responseHeaders;
return e;
}
}
六、网络调度器处理 JsonObjectRequest
6.1 NetworkDispatcher 类概述
NetworkDispatcher
是一个线程类,负责从网络请求队列中取出请求,并执行实际的网络请求。它使用 Network
接口实现来执行网络操作,并将响应结果传递给响应分发器。
以下是 NetworkDispatcher
类的核心代码:
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;
/**
* 创建一个新的 NetworkDispatcher 实例
*/
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-canceled");
continue;
}
// 为请求设置流量统计标签
addTrafficStatsTag(request);
// 执行网络请求
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// 如果服务器返回 304(Not Modified)且请求已有缓存条目,直接使用缓存
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);
}
/**
* 为请求设置流量统计标签
*/
private void addTrafficStatsTag(Request<?> request) {
// 获取请求的流量统计标签
int trafficStatsTag = request.getTrafficStatsTag();
// 如果标签为默认值,从 URL 中提取
if (trafficStatsTag == 0) {
String url = request.getUrl();
if (url != null) {
URI uri;
try {
uri = URI.create(url);
if (uri != null) {
trafficStatsTag = TextUtils.isEmpty(uri.getHost()) ? 0 :
uri.getHost().hashCode();
}
} catch (URISyntaxException e) {
// 处理 URI 语法异常
}
}
}
// 设置流量统计标签
TrafficStats.setThreadStatsTag(trafficStatsTag);
}
}
6.2 NetworkDispatcher 处理 StringRequest 的流程
当 JsonObjectRequest
被添加到网络请求队列后,NetworkDispatcher
的处理流程如下:
-
从队列中取出请求:
NetworkDispatcher
从mNetworkQueue
中取出一个请求。 -
检查请求是否已取消:如果请求已被取消,则跳过处理。
-
设置流量统计标签:为请求设置流量统计标签,用于监控网络流量。
-
执行网络请求:调用
mNetwork.performRequest(request)
方法执行实际的网络请求,获取网络响应。 -
处理 304 状态码:如果服务器返回 304 状态码且请求已有缓存条目,表示资源未修改,直接使用缓存数据。
-
解析网络响应:调用
request.parseNetworkResponse(networkResponse)
方法解析网络响应数据。 -
缓存响应数据:如果请求需要缓存且响应包含缓存条目,将响应数据存入缓存。
-
分发响应结果:标记请求已被分发,并通过
mDelivery.postResponse(request, response)
将响应结果分发到主线程。 -
错误处理:如果在处理过程中出现错误,捕获异常并通过
parseAndDeliverNetworkError
或直接将错误信息分发到主线程。
6.3 Network 接口与 BasicNetwork 实现
Network
接口定义了执行网络请求的方法:
public interface Network {
/**
* 执行网络请求
* @param request 需要执行的请求
* @return 网络响应
* @throws VolleyError 请求过程中出现的错误
*/
NetworkResponse performRequest(Request<?> request) throws VolleyError;
}
BasicNetwork
是 Network
接口的默认实现类,其 performRequest
方法实现了具体的网络请求逻辑:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
// 记录请求开始时间
long requestStart = SystemClock.elapsedRealtime();
// 循环尝试执行请求,处理重定向等情况
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try {
// 准备请求头
Map<String, String> headers = new HashMap<>();
// 添加默认请求头
addCacheHeaders(headers, request.getCacheEntry());
// 获取额外请求头
headers.putAll(request.getHeaders());
// 执行 HTTP 请求,获取 HTTP 响应
httpResponse = mHttpStack.performRequest(request, headers);
// 获取 HTTP 状态码
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
// 获取响应头
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// 如果状态码为 304(Not Modified)
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
// 获取缓存条目
Cache.Entry entry = request.getCacheEntry();
// 如果缓存条目为空,创建一个新的 NetworkResponse 表示资源未修改
if (entry == null) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// 更新缓存条目的响应头
entry.responseHeaders.putAll(responseHeaders);
// 返回表示资源未修改的 NetworkResponse
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// 处理其他状态码的情况
if (httpResponse.getEntity() != null) {
// 获取响应内容
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// 如果没有响应内容,创建空的响应内容数组
responseContents = new byte[0];
}
// 计算网络请求耗时
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
// 记录请求耗时信息
logSlowRequests(requestLifetime, request, responseContents, statusLine);
// 如果状态码不在 200 到 299 之间,表示请求有错误
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
// 返回成功的 NetworkResponse
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
// 处理超时异常,尝试重试
attemptRetryOnException("socket", request, new TimeoutError());
} catch (MalformedURLException e) {
// 处理 URL 格式错误
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
// 如果有 HTTP 响应,获取状态码并创建 NetworkResponse
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
} else {
// 如果没有 HTTP 响应,抛出无连接错误
throw new NoConnectionError(e);
}
// 处理不同状态码的情况
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
// 处理重定向情况
String newUrl = responseHeaders.get("Location");
if (newUrl == null) {
// 如果没有新的 URL,抛出异常
throw new VolleyError("Received empty or null Location header.");
}
// 更新请求的 URL
request.setRedirectUrl(newUrl);
// 重置重试次数
request.setRetryPolicy(
new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 0, 1f));
// 继续循环,尝试重新请求新的 URL
continue;
}
// 对于其他错误状态码,创建 NetworkResponse 并尝试重试
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
// 处理认证失败的情况,尝试重试
attemptRetryOnException("auth", request,
new AuthFailureError(networkResponse));
}
// 对于其他错误,抛出服务器错误
throw new ServerError(networkResponse);
}
}
}
BasicNetwork
的 performRequest
方法首先准备请求头,包括添加缓存相关头信息和请求本身的头信息。然后通过 mHttpStack.performRequest
执行实际的 HTTP 请求,获取 HTTP 响应。接着根据响应的状态码进行不同处理:对于 304 状态码,处理缓存相关逻辑;对于其他状态码,获取响应内容并进行相应处理。如果请求过程中出现超时异常,会尝试重试;对于 URL 格式错误,抛出运行时异常;对于其他 I/O 异常,根据不同状态码进行不同处理,如重定向、认证失败等情况。最终返回一个 NetworkResponse
对象,表示网络请求的结果。
七、JsonObjectRequest 解析网络响应
7.1 parseNetworkResponse 方法实现
JsonObjectRequest
类重写了 Request
类的 parseNetworkResponse
方法,用于将网络响应解析为 JSON 对象:
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
try {
// 获取响应数据的字符集,默认为 ISO-8859-1
String charset = HttpHeaderParser.parseCharset(response.headers);
// 将响应数据的字节数组转换为字符串
String jsonString = new String(response.data, charset);
// 解析字符串为 JSON 对象
JSONObject jsonObject = new JSONObject(jsonString);
// 返回包含解析后 JSON 对象的响应
return Response.success(jsonObject,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// 处理不支持的字符集异常
return Response.error(new ParseError(e));
} catch (JSONException je) {
// 处理 JSON 解析异常
return Response.error(new ParseError(je));
}
}
在这个方法中,首先通过 HttpHeaderParser.parseCharset
方法从响应头中解析出字符集信息,默认使用 ISO-8859-1
。然后使用该字符集将响应数据的字节数组转换为字符串。接着,使用 JSONObject
构造函数将字符串解析为 JSON 对象。如果解析过程中出现不支持的字符集或 JSON 解析错误,会返回相应的错误响应。最后,通过 Response.success
方法创建一个包含解析后 JSON 对象的成功响应对象,并将解析后的缓存头信息传递给它。
7.2 HttpHeaderParser 类的作用
HttpHeaderParser
类在 Volley 框架中负责解析 HTTP 响应头,提取有用的信息,如缓存控制、字符集等。其中,parseCharset
方法用于从响应头中提取字符集信息:
/**
* 从响应头中解析字符集
* @param headers 响应头
* @return 解析出的字符集,如果未找到则返回默认字符集 ISO-8859-1
*/
public static String parseCharset(Map<String, String> headers) {
// 从响应头中获取 Content-Type 字段
String contentType = headers.get("Content-Type");
if (contentType != null) {
// 分割 Content-Type 字段,查找字符集信息
String[] params = contentType.split(";");
for (int i = 1; i < params.length; i++) {
String[] pair = params[i].trim().split("=");
if (pair.length == 2) {
if (pair[0].equalsIgnoreCase("charset")) {
return pair[1];
}
}
}
}
// 如果未找到字符集信息,返回默认字符集
return HttpHeaderParser.DEFAULT_CONTENT_CHARSET;
}
该方法首先从响应头中获取 Content-Type
字段的值,然后将其按分号分割成多个参数。接着遍历这些参数,查找名为 charset
的参数,找到后返回其值。如果未找到字符集信息,返回默认字符集 ISO-8859-1
。
7.3 缓存头解析
HttpHeaderParser
类还提供了 parseCacheHeaders
方法,用于解析响应头中的缓存控制信息:
/**
* 解析 HTTP 响应头中的缓存控制信息
* @param response 网络响应
* @return 解析后的缓存条目
*/
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
String etag = null;
// 获取 Date 头
String date = headers.get("Date");
if (date != null) {
serverDate = parseDateAsEpoch(date);
}
// 获取 ETag 头
etag = headers.get("ETag");
// 获取 Last-Modified 头
String lastModifiedStr = headers.get("Last-Modified");
if (lastModifiedStr != null) {
lastModified = parseDateAsEpoch(lastModifiedStr);
}
// 获取 Cache-Control 头
String cacheControl = headers.get("Cache-Control");
if (cacheControl != null) {
hasCacheControl = true;
// 分割 Cache-Control 指令
String[] tokens = cacheControl.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
// 如果包含 no-cache 或 no-store,直接返回 null,表示不缓存
return null;
} else if (token.startsWith("max-age=")) {
// 解析 max-age 指令
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
// 处理解析异常
}
} else if (token.startsWith("stale-while-revalidate=")) {
// 解析 stale-while-revalidate 指令
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
// 处理解析异常
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
// 处理 must-revalidate 和 proxy-revalidate 指令
maxAge = 0;
}
}
}
// 获取 Expires 头
String expires = headers.get("Expires");
if (expires != null) {
serverExpires = parseDateAsEpoch(expires);
}
// 计算缓存的软过期时间和绝对过期时间
if (hasCacheControl) {
// 如果有 Cache-Control 头,使用其中的指令计算
softExpire = now + maxAge * 1000;
finalExpire = softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// 如果没有 Cache-Control 头,但有 Date 和 Expires 头,使用它们计算
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
// 创建缓存条目
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = etag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
该方法从响应头中提取各种缓存相关信息,包括日期、ETag、最后修改时间、缓存控制指令等。然后根据这些信息计算缓存的软过期时间和绝对过期时间,最终创建并返回一个 Cache.Entry
对象,用于存储缓存数据和元数据。
八、响应分发到主线程
8.1 ResponseDelivery 接口与 ExecutorDelivery 实现
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);
}
ExecutorDelivery
是 ResponseDelivery
接口的默认实现类,它使用 Executor
将响应分发到主线程:
public class ExecutorDelivery implements ResponseDelivery {
// 用于在主线程执行任务的 Executor
private final Executor mResponsePoster;
/**
* 创建一个使用 Handler 在主线程执行任务的 ExecutorDelivery
* @param handler 用于在主线程执行任务的 Handler
*/
public ExecutorDelivery(final Handler handler) {
// 创建一个 Executor,它会将任务提交给 Handler 在主线程执行
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
/**
* 创建一个使用自定义 Executor 执行任务的 ExecutorDelivery
* @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");
// 将响应分发任务提交给 Executor 执行
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
@Override
public void postError(Request<?> request, VolleyError error) {
// 添加标记记录错误已开始分发
request.addMarker("post-error");
// 创建错误响应
Response<?> response = Response.error(error);
// 将错误响应分发任务提交给 Executor 执行
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
/**
* 用于在主线程执行的响应分发任务
*/
@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;
}
@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");
}
// 如果有额外的回调任务,执行它
if (mRunnable != null) {
mRunnable.run();
}
}
}
}
ExecutorDelivery
的构造函数可以接收一个 Handler
或自定义的 Executor
,用于在主线程执行任务。其 postResponse
和 postError
方法会将响应或错误封装到 ResponseDeliveryRunnable
中,并通过 Executor
在主线程执行。ResponseDeliveryRunnable
的 run
方法会根据响应状态调用请求的相应监听器,并处理请求的完成状态和回调任务。
8.2 请求回调的执行过程
当响应结果被分发到主线程后,会调用 JsonObjectRequest
的 deliverResponse
方法:
@Override
protected void deliverResponse(JSONObject response) {
// 调用响应监听器的 onResponse 方法,将解析后的 JSON 对象响应传递给它
if (mListener != null) {
mListener.onResponse(response);
}
}
这个方法非常简单,只是调用了在创建 JsonObjectRequest
时传入的响应监听器的 onResponse
方法,并将解析后的 JSON 对象响应作为参数传递给它。开发者在创建请求时实现的 Response.Listener<JSONObject>
接口中的 onResponse
方法会在主线程中被调用,这样开发者就可以在该方法中安全地更新 UI 或进行其他与主线程相关的操作。
同样,当请求出现错误时,会调用 JsonObjectRequest
的 deliverError
方法:
@Override
protected void deliverError(VolleyError error) {
// 调用错误监听器的 onErrorResponse 方法
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
该方法会调用错误监听器的 onErrorResponse
方法,将错误信息传递给开发者实现的错误处理逻辑。
九、错误处理机制
9.1 VolleyError 类层次结构
Volley 框架中的错误处理基于 VolleyError
类及其子类,形成了一个完整的错误类层次结构:
// VolleyError 类是所有 Volley 相关错误的基类
public class VolleyError extends Exception {
// 网络响应对象,包含错误发生时的响应信息
public final NetworkResponse networkResponse;
// 网络请求耗时
private long networkTimeMs;
// 构造函数...
/**
* 获取网络请求耗时
*/
public long getNetworkTimeMs() {
return networkTimeMs;
}
/**
* 设置网络请求耗时
*/
public void setNetworkTimeMs(long networkTimeMs) {
this.networkTimeMs = networkTimeMs;
}
}
// AuthFailureError 表示认证失败的错误
public class AuthFailureError extends VolleyError {
// 构造函数...
/**
* 获取请求头中的认证信息
*/
public Map<String, String> getRequestHeaders() throws AuthFailureError {
return null;
}
/**
* 获取请求体中的认证信息
*/
public byte[] getBody() throws AuthFailureError {
return null;
}
}
// NetworkError 表示网络连接相关的错误
public class NetworkError extends VolleyError {
// 构造函数...
}
// NoConnectionError 表示无法建立网络连接的错误
public class NoConnectionError extends NetworkError {
// 构造函数...
}
// ParseError 表示响应解析失败的错误
public class ParseError extends VolleyError {
// 构造函数...
}
// ServerError 表示服务器返回错误状态码的错误
public class ServerError extends VolleyError {
// 构造函数...
}
// TimeoutError 表示请求超时的错误
public class TimeoutError extends VolleyError {
// 构造函数...
}
这个类层次结构允许 Volley 根据不同的错误类型提供更具体的错误信息,方便开发者进行针对性的错误处理。
9.2 JsonObjectRequest 中的错误处理
在 JsonObjectRequest
的处理过程中,如果发生错误,会通过错误监听器通知开发者。例如,在 NetworkDispatcher
中处理错误的代码:
catch (VolleyError volleyError) {
// 设置网络请求耗时
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
// 解析并分发网络错误
parseAndDeliverNetworkError(request, volleyError);
}
parseAndDeliverNetworkError
方法会对错误进行进一步处理:
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
// 调用请求的 parseNetworkError 方法对错误进行解析
error = request.parseNetworkError(error);
// 将错误分发给请求的错误监听器
mDelivery.postError(request, error);
}
JsonObjectRequest
类中重写了 parseNetworkError
方法,用于处理特定的网络错误:
@Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
// 如果网络响应不为空
if (volleyError.networkResponse != null) {
// 创建一个包含响应数据的新 VolleyError
VolleyError error = new VolleyError(
volleyError.networkResponse);
// 如果原始错误有原因,设置新错误的原因
if (volleyError.getCause() != null) {
error.initCause(volleyError.getCause());
}
return error;
}
return super.parseNetworkError(volleyError);
}
最终,错误会通过 ExecutorDelivery
分发到主线程,调用开发者在创建 JsonObjectRequest
时传入的错误监听器:
@Override
public void postError(Request<?> request, VolleyError error) {
// 添加错误分发标记
request.addMarker("post-error");
// 创建错误响应
Response<?> response = Response.error(error);
// 将错误响应分发任务提交给 Executor 执行
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
在 ResponseDeliveryRunnable
的 run
方法中:
@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);
}
// 其他处理...
}
JsonObjectRequest
的 deliverError
方法实现:
@Override
protected void deliverError(VolleyError error) {
// 调用错误监听器的 onErrorResponse 方法
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
这样,开发者在创建 JsonObjectRequest
时实现的 Response.ErrorListener
接口中的 onErrorResponse
方法会在主线程中被调用,开发者可以在该方法中处理各种网络错误,如显示错误提示、记录错误日志等。
9.3 重试策略
Volley 框架提供了重试策略机制,允许在请求失败时自动重试。重试策略由 RetryPolicy
接口定义:
public interface RetryPolicy {
/**
* 获取当前超时时间(毫秒)
*/
public int getCurrentTimeout();
/**
* 获取当前重试次数
*/
public int getCurrentRetryCount();
/**
* 处理重试
* @param error 导致重试的错误
* @throws VolleyError 如果不应重试,抛出此异常
*/
public void retry(VolleyError error) throws VolleyError;
}
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;
/**
* 创建一个新的 DefaultRetryPolicy 实例
*/
public DefaultRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
/**
* 创建一个新的 DefaultRetryPolicy 实例
* @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;
}
@Override
public void retry(VolleyError error) throws VolleyError {
// 增加重试次数
mCurrentRetryCount++;
// 计算新的超时时间
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
// 如果超过最大重试次数,抛出错误
if (mCurrentRetryCount > mMaxNumRetries) {
throw error;
}
}
/**
* 获取退避乘数
*/
public float getBackoffMultiplier() {
return mBackoffMultiplier;
}
}
在请求执行过程中,如果发生可重试的错误(如超时),NetworkDispatcher
会调用重试策略的 retry
方法:
catch (SocketTimeoutException e) {
// 处理超时异常,尝试重试
attemptRetryOnException("socket", request, new TimeoutError());
}
attemptRetryOnException
方法会调用请求的重试策略:
private void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception)
throws VolleyError {
RetryPolicy retryPolicy = request.getRetryPolicy();
int oldTimeout = request.getTimeoutMs();
try {
// 调用重试策略的 retry
try {
// 调用重试策略的 retry 方法尝试重试
retryPolicy.retry(exception);
} catch (VolleyError e) {
// 无法重试,记录日志并抛出异常
request.addMarker(
String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout));
throw e;
}
// 添加重试标记
request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout));
}
在这个过程中,DefaultRetryPolicy
的 retry
方法会增加重试次数,并根据退避乘数计算新的超时时间。如果重试次数超过最大重试次数,则抛出原始错误,不再重试。
开发者可以通过以下方式自定义重试策略:
// 创建自定义重试策略,设置更长的超时时间和更多的重试次数
RetryPolicy retryPolicy = new DefaultRetryPolicy(
5000, // 初始超时时间 5 秒
3, // 最大重试次数 3 次
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT // 默认退避乘数
);
// 为请求设置自定义重试策略
jsonObjectRequest.setRetryPolicy(retryPolicy);
9.4 错误处理最佳实践
在使用 JsonObjectRequest
时,开发者可以采用以下最佳实践来处理各种错误情况:
- 实现全面的错误监听器:
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 处理成功响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 根据不同类型的错误进行不同处理
if (error instanceof TimeoutError) {
// 处理超时错误
showToast("请求超时,请重试");
} else if (error instanceof NoConnectionError) {
// 处理无网络连接错误
showToast("网络连接不可用,请检查网络设置");
} else if (error instanceof AuthFailureError) {
// 处理认证失败错误
showToast("认证失败,请重新登录");
// 可能需要跳转到登录页面
} else if (error instanceof ServerError) {
// 处理服务器错误
showToast("服务器错误,状态码: " +
(error.networkResponse != null ?
error.networkResponse.statusCode : "未知"));
} else if (error instanceof NetworkError) {
// 处理网络错误
showToast("网络错误,请检查网络连接");
} else if (error instanceof ParseError) {
// 处理解析错误
showToast("数据解析错误,请稍后重试");
} else {
// 处理其他错误
showToast("发生未知错误,请稍后重试");
}
}
}
);
- 设置合理的重试策略:
// 设置合理的超时时间和重试次数
jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(
5000, // 5 秒超时时间
2, // 最多重试 2 次
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
));
- 检查网络连接状态: 在发送请求前,检查设备的网络连接状态,避免不必要的网络请求:
// 检查网络连接状态
private boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
return false;
}
// 在发送请求前检查网络连接
if (isNetworkAvailable(context)) {
// 网络可用,发送请求
requestQueue.add(jsonObjectRequest);
} else {
// 网络不可用,提示用户
showToast("网络连接不可用,请检查网络设置");
}
- 使用进度指示器: 在发送请求时显示进度指示器,提高用户体验:
// 显示进度对话框
ProgressDialog progressDialog = ProgressDialog.show(
context, "请稍候", "正在加载数据...", true, false);
// 发送请求
requestQueue.add(jsonObjectRequest);
// 在响应监听器中隐藏进度对话框
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 处理响应
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
}
- 处理服务器返回的错误信息:
如果服务器返回了包含错误信息的 JSON 响应,可以从
NetworkResponse
中获取并解析:
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
if (error.networkResponse != null) {
try {
// 获取服务器返回的错误信息
String errorResponse = new String(
error.networkResponse.data,
HttpHeaderParser.parseCharset(error.networkResponse.headers));
// 解析错误信息
JSONObject errorJson = new JSONObject(errorResponse);
String errorMessage = errorJson.optString("message", "未知错误");
// 显示错误信息
showToast("服务器错误: " + errorMessage);
} catch (UnsupportedEncodingException | JSONException e) {
e.printStackTrace();
}
} else {
showToast("发生错误,请稍后重试");
}
}
}
十、高级用法与技巧
10.1 自定义请求头
在某些情况下,需要为请求添加自定义头信息,例如认证令牌、用户代理等。可以通过重写 JsonObjectRequest
的 getHeaders
方法来实现:
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
url,
null,
listener,
errorListener
) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
// 获取原始请求头
Map<String, String> headers = super.getHeaders();
// 创建新的请求头 Map,包含原始请求头
Map<String, String> newHeaders = new HashMap<>(headers);
// 添加自定义请求头
newHeaders.put("Authorization", "Bearer " + authToken);
newHeaders.put("User-Agent", "MyApp/1.0");
return newHeaders;
}
};
10.2 处理大文件下载
虽然 JsonObjectRequest
主要用于处理 JSON 数据,但 Volley 也可以用于大文件下载。可以通过继承 Request
类并自定义解析逻辑来实现:
public class FileDownloadRequest extends Request<File> {
private final Listener<File> mListener;
private final File mDestinationFile;
public FileDownloadRequest(int method, String url, File destinationFile,
Listener<File> listener, ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
mDestinationFile = destinationFile;
}
@Override
protected Response<File> parseNetworkResponse(NetworkResponse response) {
try {
// 创建文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(mDestinationFile);
// 将响应数据写入文件
fileOutputStream.write(response.data);
fileOutputStream.close();
// 返回成功响应
return Response.success(mDestinationFile,
HttpHeaderParser.parseCacheHeaders(response));
} catch (IOException e) {
// 处理 IO 异常
return Response.error(new VolleyError(e));
}
}
@Override
protected void deliverResponse(File response) {
// 分发响应
mListener.onResponse(response);
}
}
使用方法:
// 创建文件下载请求
File destinationFile = new File(context.getExternalFilesDir(null), "downloaded_file.jpg");
FileDownloadRequest request = new FileDownloadRequest(
Request.Method.GET,
downloadUrl,
destinationFile,
new Response.Listener<File>() {
@Override
public void onResponse(File response) {
// 文件下载成功
Toast.makeText(context, "文件下载成功", Toast.LENGTH_SHORT).show();
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 文件下载失败
Toast.makeText(context, "文件下载失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
}
}
);
// 添加到请求队列
requestQueue.add(request);
10.3 实现请求优先级控制
Volley 允许为不同请求设置不同的优先级,从而控制请求的执行顺序。可以通过重写 JsonObjectRequest
的 getPriority
方法来设置请求优先级:
JsonObjectRequest highPriorityRequest = new JsonObjectRequest(
Request.Method.GET,
highPriorityUrl,
null,
listener,
errorListener
) {
@Override
public Priority getPriority() {
// 设置为高优先级
return Priority.HIGH;
}
};
JsonObjectRequest lowPriorityRequest = new JsonObjectRequest(
Request.Method.GET,
lowPriorityUrl,
null,
listener,
errorListener
) {
@Override
public Priority getPriority() {
// 设置为低优先级
return Priority.LOW;
}
};
// 添加到请求队列
requestQueue.add(highPriorityRequest);
requestQueue.add(lowPriorityRequest);
10.4 使用请求标签管理请求
Volley 允许为请求设置标签,以便在需要时可以批量取消请求。可以在创建请求时设置标签:
// 设置请求标签
jsonObjectRequest.setTag("myRequestTag");
// 添加到请求队列
requestQueue.add(jsonObjectRequest);
// 取消带有特定标签的所有请求
requestQueue.cancelAll("myRequestTag");
// 也可以使用 RequestFilter 取消符合条件的请求
requestQueue.cancelAll(new RequestQueue.RequestFilter() {
@Override
public boolean apply(Request<?> request) {
// 取消所有 URL 包含 "example.com" 的请求
return request.getUrl().contains("example.com");
}
});
10.5 实现请求进度监听
对于大文件上传或下载,可以实现进度监听功能。以下是一个实现进度监听的示例:
public class ProgressJsonObjectRequest extends JsonObjectRequest {
private final ProgressListener mProgressListener;
public ProgressJsonObjectRequest(int method, String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener,
ProgressListener progressListener) {
super(method, url, jsonRequest, listener, errorListener);
mProgressListener = progressListener;
}
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
// 计算进度
if (mProgressListener != null) {
// 通知进度更新
mProgressListener.onProgress(response.data.length, response.data.length);
}
// 调用父类方法解析响应
return super.parseNetworkResponse(response);
}
@Override
public byte[] getBody() {
byte[] body = super.getBody();
// 计算上传进度
if (mProgressListener != null && body != null) {
// 通知进度更新
mProgressListener.onProgress(body.length, body.length);
}
return body;
}
public interface ProgressListener {
void onProgress(long transferredBytes, long totalBytes);
}
}
使用方法:
ProgressJsonObjectRequest request = new ProgressJsonObjectRequest(
Request.Method.POST,
uploadUrl,
jsonRequest,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
},
new ProgressJsonObjectRequest.ProgressListener() {
@Override
public void onProgress(long transferredBytes, long totalBytes) {
// 计算百分比
int progress = (int) ((transferredBytes * 100) / totalBytes);
// 更新进度 UI
updateProgressUI(progress);
}
}
);
// 添加到请求队列
requestQueue.add(request);
10.6 处理复杂的 JSON 结构
当服务器返回复杂的 JSON 结构时,可以使用 JSON 解析库(如 Gson、Jackson 等)来简化解析过程。以下是一个使用 Gson 解析复杂 JSON 的示例:
首先,定义数据模型类:
public class User {
private String name;
private int age;
private String email;
private List<String> hobbies;
private Address address;
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public List<String> getHobbies() { return hobbies; }
public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}
public class Address {
private String street;
private String city;
private String state;
private String zip;
// Getters and setters
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getZip() { return zip; }
public void setZip(String zip) { this.zip = zip; }
}
然后,自定义请求类使用 Gson 解析:
public class GsonRequest<T> extends Request<T> {
private final Gson gson = new Gson();
private final Class<T> clazz;
private final Map<String, String> headers;
private final Listener<T> listener;
/**
* 创建一个新的 GsonRequest 实例
*/
public GsonRequest(int method, String url, Class<T> clazz, Map<String, String> headers,
Listener<T> listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.clazz = clazz;
this.headers = headers;
this.listener = listener;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return headers != null ? headers : super.getHeaders();
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
// 获取响应数据
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
// 使用 Gson 解析 JSON 到指定类
return Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// 处理编码异常
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
// 处理 JSON 语法异常
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
// 分发响应
listener.onResponse(response);
}
}
使用方法:
// 创建 Gson 请求
GsonRequest<User> request = new GsonRequest<>(
Request.Method.GET,
userUrl,
User.class,
null,
new Response.Listener<User>() {
@Override
public void onResponse(User user) {
// 处理解析后的用户对象
processUser(user);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
}
);
// 添加到请求队列
requestQueue.add(request);
10.7 实现请求缓存控制
Volley 默认会根据 HTTP 响应头自动处理缓存,但也可以通过自定义请求类来实现更精细的缓存控制。以下是一个示例:
public class CacheControlJsonObjectRequest extends JsonObjectRequest {
private final String mCacheKey;
public CacheControlJsonObjectRequest(int method, String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener,
String cacheKey) {
super(method, url, jsonRequest, listener, errorListener);
mCacheKey = cacheKey;
}
@Override
public String getCacheKey() {
// 使用自定义缓存键
return mCacheKey != null ? mCacheKey : super.getCacheKey();
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
// 添加自定义缓存控制头
Map<String, String> headers = super.getHeaders();
headers.put("Cache-Control", "max-age=3600"); // 缓存 1 小时
return headers;
}
@Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
// 自定义解析缓存头
Response<JSONObject> parsedResponse = super.parseNetworkResponse(response);
// 修改缓存时间
if (parsedResponse.cacheEntry != null) {
// 设置缓存有效期为 24 小时
long now = System.currentTimeMillis();
parsedResponse.cacheEntry.softTtl = now + 24 * 60 * 60 * 1000;
parsedResponse.cacheEntry.ttl = parsedResponse.cacheEntry.softTtl;
}
return parsedResponse;
}
}
使用方法:
// 创建自定义缓存控制请求
CacheControlJsonObjectRequest request = new CacheControlJsonObjectRequest(
Request.Method.GET,
url,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// 处理响应
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
}
},
"custom_cache_key" // 自定义缓存键
);
// 添加到请求队列
requestQueue.add(request);
10.8 实现批量请求
有时候需要同时发送多个请求,并在所有请求完成后执行某些操作。可以使用 RequestFuture
来实现这个功能:
// 创建请求未来对象
RequestFuture<JSONObject> future1 = RequestFuture.newFuture();
RequestFuture<JSONObject> future2 = RequestFuture.newFuture();
// 创建请求
JsonObjectRequest request1 = new JsonObjectRequest(
Request.Method.GET,
url1,
null,
future1,
future1
);
JsonObjectRequest request2 = new JsonObjectRequest(
Request.Method.GET,
url2,
null,
future2,
future2
);
// 添加请求到队列
requestQueue.add(request1);
requestQueue.add(request2);
// 在后台线程中等待所有请求完成
new Thread(new Runnable() {
@Override
public void run() {
try {
// 等待第一个请求完成
JSONObject response1 = future1.get(); // 阻塞直到请求完成
// 等待第二个请求完成
JSONObject response2 = future2.get(); // 阻塞直到请求完成
// 处理两个请求的响应
processResponses(response1, response2);
} catch (InterruptedException e) {
// 处理中断异常
e.printStackTrace();
} catch (ExecutionException e) {
// 处理执行异常
e.printStackTrace();
}
}
}).start();
10.9 使用自定义 HTTP 栈
Volley 默认使用 HurlStack
作为 HTTP 栈,但也可以替换为其他实现,如 OkHttpStack
。以下是使用 OkHttp 作为 HTTP 栈的示例:
首先,添加 OkHttp 依赖:
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
然后,创建自定义 RequestQueue
:
// 创建 OkHttp 客户端
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
// 创建 OkHttp 栈
OkHttpStack okHttpStack = new OkHttpStack(okHttpClient);
// 创建自定义缓存
File cacheDir = new File(context.getCacheDir(), "volley");
Cache cache = new DiskBasedCache(cacheDir, 10 * 1024 * 1024); // 10MB 缓存
// 创建自定义请求队列
RequestQueue requestQueue = new RequestQueue(cache, okHttpStack);
requestQueue.start();
10.10 调试与性能优化
在开发过程中,可能需要对 Volley 请求进行调试和性能优化。以下是一些建议:
- 启用 Volley 日志:
// 在开发环境中启用 Volley 详细日志
VolleyLog.DEBUG = BuildConfig.DEBUG;
- 使用 NetworkDispatcher 线程数优化:
// 创建自定义请求队列,增加网络调度线程数提高并发性能
RequestQueue requestQueue = Volley.newRequestQueue(context, 4); // 使用 4 个网络调度线程
- 监控请求执行时间:
// 在请求中添加标记监控请求执行时间
jsonObjectRequest.addMarker("request-start");
// 在响应监听器中计算执行时间
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
long requestTime = System.currentTimeMillis() - requestStartTime;
Log.d(TAG, "Request took " + requestTime + "ms");
// 处理响应
}
}
- 使用 NetworkResponse 中的网络耗时信息:
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (error.networkResponse != null) {
Log.d(TAG, "Network time: " + error.getNetworkTimeMs() + "ms");
}
// 处理错误
}
}
- 分析请求标记:
// 打印请求的所有标记,用于分析请求执行流程
jsonObjectRequest.setRequestFinishedListener(new RequestQueue.RequestFinishedListener<JSONObject>() {
@Override
public void onRequestFinished(Request<JSONObject> request) {
StringBuilder markers = new StringBuilder();
for (String marker : request.getMarkers()) {
markers.append(marker).append("\n");
}
Log.d(TAG, "Request markers:\n" + markers.toString());
}
});
十一、实际应用案例
11.1 获取天气数据
以下是一个使用 JsonObjectRequest
获取天气数据的完整示例:
// 天气 API URL
String weatherUrl = "https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=YOUR_API_KEY";
// 创建请求队列
RequestQueue requestQueue = Volley.newRequestQueue(this);
// 创建 JsonObjectRequest
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
weatherUrl,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
// 解析基本信息
String cityName = response.getString("name");
double temperature = response.getJSONObject("main").getDouble("temp") - 273.15; // 转换为摄氏度
String weatherDescription = response.getJSONArray("weather").getJSONObject(0).getString("description");
// 显示天气信息
TextView cityTextView = findViewById(R.id.city_text_view);
TextView tempTextView = findViewById(R.id.temp_text_view);
TextView descTextView = findViewById(R.id.desc_text_view);
cityTextView.setText("城市: " + cityName);
tempTextView.setText("温度: " + String.format("%.1f", temperature) + "°C");
descTextView.setText("天气: " + weatherDescription);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "解析天气数据失败", Toast.LENGTH_SHORT).show();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
if (error instanceof TimeoutError) {
Toast.makeText(MainActivity.this, "请求超时,请重试", Toast.LENGTH_SHORT).show();
} else if (error instanceof NoConnectionError) {
Toast.makeText(MainActivity.this, "网络连接不可用,请检查网络设置", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "获取天气数据失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
// 设置请求头
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
return headers;
}
};
// 设置重试策略
jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(
5000, // 5 秒超时
2, // 最多重试 2 次
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
));
// 添加到请求队列
requestQueue.add(jsonObjectRequest);
11.2 用户登录示例
以下是一个使用 JsonObjectRequest
实现用户登录的完整示例:
// 登录 API URL
String loginUrl = "https://api.example.com/login";
// 创建登录请求的 JSON 对象
JSONObject loginRequest = new JSONObject();
try {
loginRequest.put("username", "john_doe");
loginRequest.put("password", "password123");
} catch (JSONException e) {
e.printStackTrace();
}
// 创建请求队列
RequestQueue requestQueue = Volley.newRequestQueue(this);
// 创建 JsonObjectRequest
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.POST,
loginUrl,
loginRequest,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
// 检查登录是否成功
boolean success = response.getBoolean("success");
if (success) {
// 登录成功,获取用户信息和令牌
String token = response.getString("token");
JSONObject user = response.getJSONObject("user");
// 保存令牌和用户信息
saveToken(token);
saveUserInfo(user);
// 跳转到主界面
Intent intent = new Intent(MainActivity.this, HomeActivity.class);
startActivity(intent);
finish();
} else {
// 登录失败,显示错误信息
String errorMessage = response.getString("message");
Toast.makeText(MainActivity.this, "登录失败: " + errorMessage, Toast.LENGTH_SHORT).show();
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "解析登录响应失败", Toast.LENGTH_SHORT).show();
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
if (error instanceof AuthFailureError) {
Toast.makeText(MainActivity.this, "认证失败,请检查用户名和密码", Toast.LENGTH_SHORT).show();
} else if (error instanceof ServerError) {
Toast.makeText(MainActivity.this, "服务器错误,请稍后重试", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "登录失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
// 设置请求头
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
return headers;
}
};
// 添加到请求队列
requestQueue.add(jsonObjectRequest);
11.3 分页加载数据
以下是一个使用 JsonObjectRequest
实现分页加载数据的完整示例:
// 数据列表
private List<DataItem> dataList = new ArrayList<>();
// 适配器
private DataAdapter dataAdapter;
// 当前页码
private int currentPage = 1;
// 是否正在加载
private boolean isLoading = false;
// 请求队列
private RequestQueue requestQueue;
// 初始化方法
private void init() {
// 创建请求队列
requestQueue = Volley.newRequestQueue(this);
// 初始化 RecyclerView
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
dataAdapter = new DataAdapter(dataList);
recyclerView.setAdapter(dataAdapter);
// 设置滚动监听器,实现分页加载
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
// 当滚动到接近底部时,加载更多数据
if (!isLoading && (visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreData();
}
}
});
// 加载第一页数据
loadData(1);
}
// 加载数据方法
private void loadData(int page) {
// 设置加载状态
isLoading = true;
// 显示加载进度
if (page == 1) {
// 第一页数据,显示全屏进度
showProgressDialog();
} else {
// 加载更多数据,在列表底部显示加载状态
dataAdapter.showLoading();
}
// 构建 API URL
String apiUrl = "https://api.example.com/data?page=" + page + "&pageSize=20";
// 创建 JsonObjectRequest
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
Request.Method.GET,
apiUrl,
null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
// 隐藏进度
hideProgressDialog();
// 解析数据
JSONArray itemsArray = response.getJSONArray("items");
// 如果是第一页,清空数据列表
if (page == 1) {
dataList.clear();
}
// 解析数据项
for (int i = 0; i < itemsArray.length(); i++) {
JSONObject itemObject = itemsArray.getJSONObject(i);
DataItem item = new DataItem();
item.setTitle(itemObject.getString("title"));
item.setDescription(itemObject.getString("description"));
item.setImageUrl(itemObject.getString("imageUrl"));
dataList.add(item);
}
// 更新适配器
dataAdapter.notifyDataSetChanged();
// 更新当前页码
currentPage = page;
// 检查是否还有更多数据
boolean hasMore = response.getBoolean("hasMore");
dataAdapter.setHasMore(hasMore);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "解析数据失败", Toast.LENGTH_SHORT).show();
} finally {
// 重置加载状态
isLoading = false;
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// 隐藏进度
hideProgressDialog();
// 处理错误
Toast.makeText(MainActivity.this, "加载数据失败: " + error.getMessage(), Toast.LENGTH_SHORT).show();
// 重置加载状态
isLoading = false;
}
}
);
// 添加到请求队列
requestQueue.add(jsonObjectRequest);
}
// 加载更多数据
private void loadMoreData() {
// 加载下一页数据
loadData(currentPage + 1);
}
// 显示进度对话框
private void showProgressDialog() {
// 实现显示进度对话框的逻辑
}
// 隐藏进度对话框
private void hideProgressDialog() {
// 实现隐藏进度对话框的逻辑
}
十二、与其他网络库的对比
12.1 与 Retrofit 的对比
Volley 的优势:
- 轻量级:Volley 体积小,适合小型项目和对 APK 大小敏感的应用。
- 内置缓存:Volley 内置了强大的缓存机制,易于使用。
- 请求优先级:支持设置请求优先级,控制请求执行顺序。
- 请求标记:可以为请求设置标记,方便批量取消请求。
- 适合小数据量请求:对于频繁的小数据量请求(如 JSON 请求),Volley 效率更高。
Retrofit 的优势:
- 基于注解:使用注解定义 API 接口,代码更简洁、清晰。
- 支持多种转换器:可以轻松集成 Gson、Jackson 等转换器,简化数据解析。
- 支持 RxJava:可以与 RxJava 结合,实现响应式编程。
- 适合复杂 API:对于复杂的 RESTful API,Retrofit 的代码结构更清晰。
- 支持协程:在 Kotlin 项目中,可以与协程结合,实现更简洁的异步代码。
12.2 与 OkHttp 的对比
Volley 的优势:
- 高层 API:Volley 提供了更高级