一、Android Volley Request请求对象基础概述
1.1 Volley框架简介
Android Volley是Google在2013年I/O大会上推出的一个轻量级网络通信框架,专为Android应用设计,旨在简化网络请求并提高其效率。它在处理高频、小数据量的网络请求场景中表现出色,例如获取JSON数据、加载图片等。Volley的核心优势包括自动请求调度、请求优先级处理、缓存机制、强大的错误处理以及与主线程的无缝集成。
1.2 Request请求对象在Volley中的核心地位
Request请求对象是Volley框架的核心组件之一,它代表了一个网络请求的抽象。所有类型的网络请求(如JSON请求、图片请求等)都继承自这个基类。Request类负责定义请求的基本属性(如URL、方法、头信息等)、处理请求的生命周期、解析响应数据以及错误处理。理解Request类的工作原理对于深入掌握Volley框架至关重要。
1.3 Request请求对象的基本架构
Request类是一个抽象类,位于com.android.volley
包中。它定义了网络请求的基本结构和行为,具体的请求类型需要继承这个类并实现特定的方法。以下是Request类的基本架构:
package com.android.volley;
import android.os.Handler;
import android.os.Looper;
import java.util.Collections;
import java.util.Map;
/**
* 表示Volley中的一个网络请求的抽象基类。
* 所有具体的请求类型都应该继承这个类。
*/
public abstract class Request<T> implements Comparable<Request<T>> {
// 请求的唯一标识
private final int mSequence;
// 请求的URL
private final String mUrl;
// 请求的重试策略
private RetryPolicy mRetryPolicy;
// 请求完成后的监听器
private RequestFinishedListener mRequestFinishedListener;
// 请求的优先级
private Priority mPriority = Priority.NORMAL;
// 请求是否已取消
private boolean mCanceled = false;
// 请求是否已发送响应
private boolean mResponseDelivered = false;
// 用于标记请求的静默重试
private boolean mShouldCache = true;
// 请求的标记,可用于取消请求
private Object mTag;
// 响应解析器
private ResponseParser mResponseParser;
// 构造函数,初始化请求的基本属性
public Request(int method, String url, ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
setRetryPolicy(new DefaultRetryPolicy());
}
// 抽象方法,必须由子类实现,用于解析网络响应
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
// 抽象方法,必须由子类实现,用于分发解析后的响应
abstract protected void deliverResponse(T response);
// 取消请求
public void cancel() {
mCanceled = true;
}
// 检查请求是否已取消
public boolean isCanceled() {
return mCanceled;
}
// 获取请求的方法(GET、POST等)
public int getMethod() {
return mMethod;
}
// 获取请求的URL
public String getUrl() {
return mUrl;
}
// 获取请求的优先级
public Priority getPriority() {
return mPriority;
}
// 设置请求的优先级
public void setPriority(Priority priority) {
mPriority = priority;
}
// 获取请求的重试策略
public RetryPolicy getRetryPolicy() {
return mRetryPolicy;
}
// 设置请求的重试策略
public void setRetryPolicy(RetryPolicy retryPolicy) {
mRetryPolicy = retryPolicy;
}
// 检查请求是否应该被缓存
public boolean shouldCache() {
return mShouldCache;
}
// 设置请求是否应该被缓存
public void setShouldCache(boolean shouldCache) {
mShouldCache = shouldCache;
}
// 获取请求头
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
// 获取请求体
public byte[] getBody() throws AuthFailureError {
return null;
}
// 获取请求体的内容类型
public String getBodyContentType() {
return "application/octet-stream";
}
// 比较两个请求的优先级
@Override
public int compareTo(Request<T> other) {
Priority left = this.getPriority();
Priority right = other.getPriority();
// 首先比较优先级,如果优先级相同则比较序列号
return left == right ?
this.mSequence - other.mSequence :
right.ordinal() - left.ordinal();
}
// 分发错误响应
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
// 标记请求已完成
void finish(final String tag) {
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
if (mRequestFinishedListener != null) {
mRequestFinishedListener.onRequestFinished(this);
}
}
// 请求优先级的枚举类型
public enum Priority {
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
}
从上面的代码可以看出,Request类定义了网络请求的基本结构和行为,包括请求的URL、方法、优先级、重试策略等。它还定义了两个抽象方法parseNetworkResponse
和deliverResponse
,这两个方法必须由子类实现,用于解析网络响应和分发解析后的结果。
二、Request请求对象的核心属性与方法
2.1 核心属性详解
2.1.1 请求标识与状态属性
// 请求的唯一序列号,用于排序和标识请求
private final int mSequence;
// 请求的URL地址
private final String mUrl;
// 请求的方法(GET、POST、PUT、DELETE等)
private final int mMethod;
// 请求是否已被取消的标志
private boolean mCanceled = false;
// 请求是否已分发响应的标志
private boolean mResponseDelivered = false;
2.1.2 优先级与缓存控制属性
// 请求的优先级,默认为NORMAL
private Priority mPriority = Priority.NORMAL;
// 指示请求是否应该被缓存的标志
private boolean mShouldCache = true;
// 请求的缓存键,默认为URL
private String mCacheKey;
2.1.3 错误处理与重试属性
// 请求失败时的错误监听器
private final Response.ErrorListener mErrorListener;
// 请求的重试策略
private RetryPolicy mRetryPolicy;
// 请求已重试的次数
private int mCurrentRetryCount = 0;
// 请求的超时时间(毫秒)
private int mTimeoutMs = DefaultRetryPolicy.DEFAULT_TIMEOUT_MS;
2.1.4 请求头与请求体属性
// 请求头信息
private Map<String, String> mHeaders = Collections.emptyMap();
// 请求体数据
private byte[] mBody;
// 请求体的内容类型
private String mBodyContentType;
2.2 核心方法详解
2.2.1 构造方法
/**
* 构造一个新的请求
* @param method 请求方法(GET、POST等)
* @param url 请求的URL
* @param listener 请求失败时的错误监听器
*/
public Request(int method, String url, Response.ErrorListener listener) {
mMethod = method;
mUrl = url;
mErrorListener = listener;
// 设置默认的重试策略
setRetryPolicy(new DefaultRetryPolicy());
}
2.2.2 抽象方法
/**
* 必须由子类实现的方法,用于解析网络响应数据
* @param response 从网络获取的原始响应
* @return 解析后的响应对象
*/
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
/**
* 必须由子类实现的方法,用于将解析后的响应分发到主线程
* @param response 解析后的响应数据
*/
abstract protected void deliverResponse(T response);
2.2.3 请求生命周期管理方法
/**
* 取消请求
* 标记请求为已取消状态,实际网络请求可能仍在进行,但结果会被忽略
*/
public void cancel() {
mCanceled = true;
}
/**
* 检查请求是否已被取消
* @return 如果请求已取消返回true,否则返回false
*/
public boolean isCanceled() {
return mCanceled;
}
/**
* 标记请求已完成
* @param tag 完成标记的标签,用于调试
*/
void finish(final String tag) {
// 从请求队列中移除该请求
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
// 通知请求完成监听器
if (mRequestFinishedListener != null) {
mRequestFinishedListener.onRequestFinished(this);
}
}
2.2.4 优先级管理方法
/**
* 获取请求的优先级
* @return 请求的优先级枚举值
*/
public Priority getPriority() {
return mPriority;
}
/**
* 设置请求的优先级
* @param priority 新的优先级
*/
public void setPriority(Priority priority) {
mPriority = priority;
}
/**
* 比较两个请求的优先级,用于请求队列排序
* @param other 要比较的另一个请求
* @return 比较结果:负数表示当前请求优先级高,正数表示另一个请求优先级高
*/
@Override
public int compareTo(Request<T> other) {
Priority left = this.getPriority();
Priority right = other.getPriority();
// 首先比较优先级,如果优先级相同则比较序列号
return left == right ?
this.mSequence - other.mSequence :
right.ordinal() - left.ordinal();
}
2.2.5 缓存管理方法
/**
* 判断请求是否应该被缓存
* @return 如果应该缓存返回true,否则返回false
*/
public boolean shouldCache() {
return mShouldCache;
}
/**
* 设置请求是否应该被缓存
* @param shouldCache 是否缓存的标志
*/
public void setShouldCache(boolean shouldCache) {
mShouldCache = shouldCache;
}
/**
* 获取请求的缓存键
* 默认使用请求的URL作为缓存键
* @return 缓存键字符串
*/
public String getCacheKey() {
return mCacheKey != null ? mCacheKey : mUrl;
}
/**
* 设置请求的缓存键
* @param cacheKey 新的缓存键
*/
public void setCacheKey(String cacheKey) {
mCacheKey = cacheKey;
}
2.2.6 请求头与请求体管理方法
/**
* 获取请求头信息
* 子类可以重写此方法来添加自定义请求头
* @return 请求头的键值对映射
* @throws AuthFailureError 如果认证失败
*/
public Map<String, String> getHeaders() throws AuthFailureError {
return Collections.emptyMap();
}
/**
* 获取请求体数据
* 子类可以重写此方法来提供请求体
* @return 请求体的字节数组
* @throws AuthFailureError 如果认证失败
*/
public byte[] getBody() throws AuthFailureError {
return null;
}
/**
* 获取请求体的内容类型
* @return 请求体的内容类型字符串
*/
public String getBodyContentType() {
return mBodyContentType != null ? mBodyContentType : "application/octet-stream";
}
2.2.7 错误处理方法
/**
* 分发错误响应到主线程
* @param error 发生的错误
*/
public void deliverError(VolleyError error) {
// 标记响应已分发
mResponseDelivered = true;
// 如果有错误监听器,调用其回调方法
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
/**
* 处理网络错误
* @param error 发生的网络错误
* @return 处理后的错误,可能会根据重试策略决定是否重试
*/
public VolleyError parseNetworkError(VolleyError volleyError) {
return volleyError;
}
三、Request请求对象的生命周期
3.1 请求的创建与初始化
当我们需要发起一个网络请求时,首先会创建一个Request对象。以下是一个创建StringRequest的示例:
// 创建一个GET请求,请求返回字符串数据
StringRequest stringRequest = new StringRequest(
Request.Method.GET, // 请求方法为GET
"https://api.example.com/data", // 请求的URL
new Response.Listener<String>() { // 请求成功的监听器
@Override
public void onResponse(String response) {
// 处理响应数据
Log.d(TAG, "Response: " + response);
}
},
new Response.ErrorListener() { // 请求失败的监听器
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
Log.e(TAG, "Error: " + error.getMessage());
}
}
);
// 设置请求的优先级
stringRequest.setPriority(Request.Priority.HIGH);
// 设置请求不使用缓存
stringRequest.setShouldCache(false);
在创建Request对象时,会调用其构造方法进行初始化:
/**
* StringRequest的构造方法
* @param method 请求方法
* @param url 请求的URL
* @param listener 请求成功的监听器
* @param errorListener 请求失败的监听器
*/
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener); // 调用父类Request的构造方法
mListener = listener;
}
/**
* Request类的构造方法
* @param method 请求方法
* @param url 请求的URL
* @param listener 请求失败的监听器
*/
public Request(int method, String url, ErrorListener listener) {
mMethod = method; // 初始化请求方法
mUrl = url; // 初始化请求URL
mErrorListener = listener; // 初始化错误监听器
setRetryPolicy(new DefaultRetryPolicy()); // 设置默认重试策略
}
3.2 请求的排队与调度
创建Request对象后,需要将其添加到RequestQueue中进行排队和调度:
// 获取RequestQueue实例
RequestQueue requestQueue = Volley.newRequestQueue(context);
// 将请求添加到请求队列
requestQueue.add(stringRequest);
RequestQueue的add方法实现如下:
/**
* 将请求添加到请求队列
* @param request 要添加的请求
* @return 返回添加的请求,便于链式调用
*/
public <T> Request<T> add(Request<T> request) {
// 将请求标记为属于此请求队列
request.setRequestQueue(this);
// 为请求分配一个序列号
synchronized (mSequenceGenerator) {
request.setSequence(mSequenceGenerator.incrementAndGet());
}
// 标记请求为未取消状态
request.addMarker("add-to-queue");
// 如果请求应该被缓存,检查是否有相同缓存键的请求正在等待
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) {
// 如果有相同缓存键的请求正在等待,将此请求加入等待队列
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// 如果没有相同缓存键的请求正在等待,标记该缓存键有请求在处理
mWaitingRequests.put(cacheKey, null);
// 将请求添加到缓存队列
mCacheQueue.add(request);
}
return request;
}
}
// 如果请求不应该被缓存,直接添加到网络队列
mNetworkQueue.add(request);
return request;
}
3.3 请求的执行与响应处理
RequestQueue中有两种分发器线程负责处理请求:CacheDispatcher处理缓存请求,NetworkDispatcher处理网络请求。
3.3.1 缓存分发器处理流程
CacheDispatcher的run方法实现如下:
@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) {
return;
}
continue;
}
}
}
3.3.2 网络分发器处理流程
NetworkDispatcher的run方法实现如下:
@Override
public void run() {
// 设置线程优先级为后台线程
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// 从网络队列中获取请求,如果队列为空则阻塞
request = mNetworkQueue.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");
// 检查服务器返回的重定向
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);
}
}
}
3.4 请求的完成与资源释放
无论请求是成功还是失败,最终都会调用finish方法完成请求并释放资源:
/**
* 标记请求已完成,并从请求队列中移除
* @param tag 完成标记的标签,用于调试
*/
void finish(final String tag) {
// 添加完成标记
addMarker(tag);
// 从请求队列中移除该请求
if (mRequestQueue != null) {
mRequestQueue.finish(this);
}
// 通知请求完成监听器
if (mRequestFinishedListener != null) {
mRequestFinishedListener.onRequestFinished(this);
}
}
RequestQueue的finish方法实现如下:
/**
* 标记请求已完成,并从所有相关队列和集合中移除
* @param request 已完成的请求
*/
<T> void finish(Request<T> request) {
// 从当前处理的请求集合中移除
synchronized (mCurrentRequests) {
mCurrentRequests.remove(request);
}
// 如果请求应该被缓存,检查是否有等待相同缓存键的请求
if (request.shouldCache()) {
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue<Request<?>> waitingRequests = mWaitingRequests.get(cacheKey);
if (waitingRequests != null) {
// 如果有等待的请求,检查是否有其他请求仍在处理中
if (mWaitingRequests.get(cacheKey) == null) {
// 没有其他请求在处理,这个不应该发生
return;
}
// 从等待队列中取出一个请求
Request<?> nextRequest = waitingRequests.poll();
if (waitingRequests.isEmpty()) {
// 如果等待队列为空,移除该缓存键的记录
mWaitingRequests.remove(cacheKey);
}
// 将取出的请求添加到缓存队列
mCacheQueue.add(nextRequest);
}
}
}
}
四、Request请求对象的子类实现
4.1 StringRequest类分析
StringRequest是Request的一个具体子类,用于请求文本响应数据:
/**
* 用于请求字符串响应的Request子类
*/
public class StringRequest extends Request<String> {
// 响应监听器
private final Listener<String> mListener;
/**
* 创建一个GET请求,返回字符串响应
* @param url 请求的URL
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
/**
* 创建一个指定方法的字符串请求
* @param method 请求方法(GET、POST等)
* @param url 请求的URL
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* 解析网络响应数据为字符串
* @param response 网络响应
* @return 解析后的字符串响应
*/
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
// 根据响应头中的字符集解码响应数据
String charset = getCharsetFromHeaders(response.headers);
parsed = new String(response.data, charset);
} catch (UnsupportedEncodingException e) {
// 如果字符集不支持,使用默认字符集
parsed = new String(response.data);
}
// 返回解析成功的响应
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
/**
* 从响应头中提取字符集信息
* @param headers 响应头
* @return 字符集名称,如果未找到则返回默认字符集
*/
private String getCharsetFromHeaders(Map<String, String> headers) {
String contentType = headers.get("Content-Type");
if (contentType != null) {
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].equals("charset")) {
return pair[1];
}
}
}
}
// 默认使用UTF-8
return "UTF-8";
}
/**
* 将解析后的响应分发到主线程
* @param response 解析后的字符串响应
*/
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
}
4.2 JsonRequest类分析
JsonRequest是一个抽象类,用于请求JSON数据,它有两个具体子类:JsonObjectRequest和JsonArrayRequest。
/**
* 用于请求JSON数据的抽象Request子类
* 可以是JSONObject或JSONArray
*/
public abstract class JsonRequest<T> extends Request<T> {
/** 默认字符集 */
private static final String PROTOCOL_CHARSET = "utf-8";
/** 内容类型头 */
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json; charset=%s", PROTOCOL_CHARSET);
// 请求体
private final String mRequestBody;
/**
* 构造一个JSON请求
* @param method 请求方法
* @param url 请求的URL
* @param requestBody 请求体(JSON格式字符串)
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public JsonRequest(int method, String url, String requestBody,
Listener<T> listener, ErrorListener errorListener) {
super(method, url, errorListener);
setShouldCache(false);
mListener = listener;
mRequestBody = requestBody;
}
/**
* 获取请求体的内容类型
* @return JSON内容类型字符串
*/
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
/**
* 获取请求体
* @return 请求体的字节数组
* @throws AuthFailureError 如果认证失败
*/
@Override
public byte[] getBody() {
try {
// 将请求体字符串转换为字节数组
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;
}
}
/**
* 解析网络响应数据
* @param response 网络响应
* @return 解析后的响应
*/
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
// 从响应数据中解析JSON字符串
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
// 由子类实现具体的解析逻辑
return Response.success(
parseJson(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// 返回解析错误
return Response.error(new ParseError(e));
} catch (JSONException je) {
// 返回解析错误
return Response.error(new ParseError(je));
}
}
/**
* 由子类实现的解析JSON字符串的方法
* @param jsonString JSON字符串
* @return 解析后的JSON对象
* @throws JSONException 如果解析失败
*/
protected abstract T parseJson(String jsonString) throws JSONException;
/**
* 将解析后的响应分发到主线程
* @param response 解析后的JSON响应
*/
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
}
4.3 JsonObjectRequest类分析
JsonObjectRequest是JsonRequest的具体子类,用于请求JSON对象:
/**
* 用于请求JSON对象的Request子类
*/
public class JsonObjectRequest extends JsonRequest<JSONObject> {
/**
* 创建一个GET请求获取JSONObject
* @param url 请求的URL
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public JsonObjectRequest(String url, Listener<JSONObject> listener, ErrorListener errorListener) {
this(Method.GET, url, null, listener, errorListener);
}
/**
* 创建一个带有JSONObject请求体的请求
* @param method 请求方法
* @param url 请求的URL
* @param jsonRequest 请求体JSON对象(可为null)
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
Listener<JSONObject> listener, ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(),
listener, errorListener);
}
/**
* 解析JSON字符串为JSONObject
* @param jsonString JSON字符串
* @return 解析后的JSONObject
* @throws JSONException 如果解析失败
*/
@Override
protected JSONObject parseJson(String jsonString) throws JSONException {
return new JSONObject(jsonString);
}
}
4.4 JsonArrayRequest类分析
JsonArrayRequest是JsonRequest的具体子类,用于请求JSON数组:
/**
* 用于请求JSON数组的Request子类
*/
public class JsonArrayRequest extends JsonRequest<JSONArray> {
/**
* 创建一个GET请求获取JSONArray
* @param url 请求的URL
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public JsonArrayRequest(String url, Listener<JSONArray> listener, ErrorListener errorListener) {
this(Method.GET, url, null, listener, errorListener);
}
/**
* 创建一个带有JSONArray请求体的请求
* @param method 请求方法
* @param url 请求的URL
* @param jsonRequest 请求体JSON数组(可为null)
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public JsonArrayRequest(int method, String url, JSONArray jsonRequest,
Listener<JSONArray> listener, ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(),
listener, errorListener);
}
/**
* 解析JSON字符串为JSONArray
* @param jsonString JSON字符串
* @return 解析后的JSONArray
* @throws JSONException 如果解析失败
*/
@Override
protected JSONArray parseJson(String jsonString) throws JSONException {
return new JSONArray(jsonString);
}
}
4.5 ImageRequest类分析
ImageRequest是Request的具体子类,用于请求图片:
/**
* 用于请求图片的Request子类
*/
public class ImageRequest extends Request<Bitmap> {
// 默认的最大图片宽度
private static final int DEFAULT_MAX_WIDTH = 0;
// 默认的最大图片高度
private static final int DEFAULT_MAX_HEIGHT = 0;
// 图片解码器配置
private static final Bitmap.Config DEFAULT_BITMAP_CONFIG = Bitmap.Config.RGB_565;
// 响应监听器
private final Listener<Bitmap> mListener;
// 最大宽度
private final int mMaxWidth;
// 最大高度
private final int mMaxHeight;
// 图片配置
private final Bitmap.Config mDecodeConfig;
/**
* 创建一个图片请求
* @param url 请求的URL
* @param listener 响应监听器
* @param maxWidth 最大宽度(0表示无限制)
* @param maxHeight 最大高度(0表示无限制)
* @param decodeConfig 图片解码配置
* @param errorListener 错误监听器
*/
public ImageRequest(String url, Listener<Bitmap> listener, int maxWidth, int maxHeight,
Bitmap.Config decodeConfig, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS,
2, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
}
/**
* 获取图片的缩放类型
* @param maxWidth 最大宽度
* @param maxHeight 最大高度
* @return 缩放类型
*/
private static ScaleType getDefaultScaleType(int maxWidth, int maxHeight) {
return (maxWidth == 0 && maxHeight == 0) ? ScaleType.CENTER_INSIDE :
ScaleType.CENTER_CROP;
}
/**
* 解析网络响应为Bitmap
* @param response 网络响应
* @return 解析后的响应
*/
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
// 同步锁,防止同时进行多个图片解码操作
synchronized (sDecodeLock) {
try {
// 解码图片
return doParse(response);
} catch (OutOfMemoryError e) {
// 内存不足错误处理
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
/**
* 实际执行图片解码的方法
* @param response 网络响应
* @return 解析后的响应
*/
private Response<Bitmap> doParse(NetworkResponse response) {
byte[] data = response.data;
// 先获取图片的尺寸信息
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
// 获取原始图片尺寸
int actualWidth = options.outWidth;
int actualHeight = options.outHeight;
// 计算目标尺寸
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
actualWidth, actualHeight, getDefaultScaleType(mMaxWidth, mMaxHeight));
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
actualHeight, actualWidth, getDefaultScaleType(mMaxWidth, mMaxHeight));
// 计算采样率
options.inJustDecodeBounds = false;
options.inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
// 如果需要,调整图片尺寸
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)) {
Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap,
desiredWidth, desiredHeight, true);
tempBitmap.recycle();
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
} else {
return Response.success(tempBitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
/**
* 计算调整后的尺寸
* @param maxPrimary 主要维度的最大值
* @param maxSecondary 次要维度的最大值
* @param actualPrimary 主要维度的实际值
* @param actualSecondary 次要维度的实际值
* @param scaleType 缩放类型
* @return 调整后的尺寸
*/
private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
int actualSecondary, ScaleType scaleType) {
// 如果没有限制,返回实际尺寸
if ((maxPrimary == 0) && (maxSecondary == 0)) {
return actualPrimary;
}
// 如果只有一个维度有限制,返回该维度的实际尺寸
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}
if (maxSecondary == 0) {
return maxPrimary;
}
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
// 如果需要保持纵横比,调整尺寸
if (scaleType == ScaleType.FIT_XY) {
// 不保持纵横比
} else if (scaleType == ScaleType.CENTER_CROP) {
// 保持纵横比,裁剪图片
if ((resized * ratio) < maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
} else {
// 保持纵横比,缩放图片
if ((resized * ratio) > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
}
return resized;
}
/**
* 找到最佳的采样率
* @param actualWidth 实际宽度
* @param actualHeight 实际高度
* @param desiredWidth 目标宽度
* @param desiredHeight 目标高度
* @return 最佳采样率
*/
static int findBestSampleSize(
int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float n = 1.0f;
while ((n * 2) <= ratio) {
n *= 2;
}
return (int) n;
}
/**
* 将解析后的响应分发到主线程
* @param response 解析后的Bitmap响应
*/
@Override
protected void deliverResponse(Bitmap response) {
mListener.onResponse(response);
}
// 缩放类型枚举
private enum ScaleType {
CENTER_INSIDE,
CENTER_CROP,
FIT_XY
}
// 解码锁,防止同时进行多个图片解码操作
private static final Object sDecodeLock = new Object();
}
五、Request请求对象的高级用法
5.1 自定义Request子类
在某些情况下,Volley提供的内置Request子类可能无法满足需求,这时可以创建自定义的Request子类。以下是一个自定义的GsonRequest示例,用于直接将JSON响应解析为Java对象:
/**
* 使用Gson解析JSON响应的自定义Request
* @param <T> 响应数据的类型
*/
public class GsonRequest<T> extends Request<T> {
// Gson实例,用于JSON解析
private final Gson gson;
// 响应数据的类型Token
private final Type type;
// 响应监听器
private final Listener<T> listener;
// 请求体
private final String requestBody;
/**
* 构造一个新的GsonRequest
* @param method 请求方法
* @param url 请求的URL
* @param type 响应数据的类型Token
* @param requestBody 请求体(JSON格式字符串)
* @param listener 响应监听器
* @param errorListener 错误监听器
*/
public GsonRequest(int method, String url, Type type, String requestBody,
Listener<T> listener, ErrorListener errorListener) {
super(method, url, errorListener);
this.gson = new Gson();
this.type = type;
this.listener = listener;
this.requestBody = requestBody;
}
/**
* 获取请求体的内容类型
* @return JSON内容类型字符串
*/
@Override
public String getBodyContentType() {
return "application/json; charset=utf-8";
}
/**
* 获取请求体
* @return 请求体的字节数组
* @throws AuthFailureError 如果认证失败
*/
@Override
public byte[] getBody() throws AuthFailureError {
try {
return requestBody == null ? null : requestBody.getBytes("utf-8");
} catch (UnsupportedEncodingException uee) {
VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
requestBody, "utf-8");
return null;
}
}
/**
* 解析网络响应数据
* @param response 网络响应
* @return 解析后的响应
*/
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
// 获取响应数据的字符串表示
String json = new String(
response.data,
HttpHeaderParser.parseCharset(response.headers));
// 使用Gson将JSON字符串解析为指定类型的对象
T parsedObject = gson.fromJson(json, type);
// 返回解析成功的响应
return Response.success(
parsedObject,
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
// 返回解析错误
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
// 返回解析错误
return Response.error(new ParseError(e));
}
}
/**
* 将解析后的响应分发到主线程
* @param response 解析后的响应对象
*/
@Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}
}
5.2 使用自定义Request的示例
以下是如何使用上面的GsonRequest的示例:
// 定义一个数据模型类
public class User {
private String name;
private int age;
private String email;
// 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; }
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + ", email='" + email + "'}";
}
}
// 使用GsonRequest请求用户数据
public void fetchUserData() {
// 创建请求URL
String url = "https://api.example.com/user/123";
// 创建GsonRequest
GsonRequest<User> request = new GsonRequest<>(
Request.Method.GET, // 请求方法
url, // 请求URL
new TypeToken<User>() {}.getType(), // 响应类型
null, // 请求体(GET请求为null)
new Response.Listener<User>() { // 响应监听器
@Override
public void onResponse(User user) {
// 处理响应数据
Log.d(TAG, "User data: " + user.toString());
}
},
new Response.ErrorListener() { // 错误监听器
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误
Log.e(TAG, "Error fetching user data: " + error.getMessage());
}
}
);
// 添加请求到队列
requestQueue.add(request);
}
5.3 请求优先级控制
Volley允许为每个请求设置优先级,从而控制请求的执行顺序。以下是如何设置请求优先级的示例:
// 创建一个高优先级的请求
StringRequest highPriorityRequest = new StringRequest(
Request.Method.GET,
"https://api.example.com/high_priority_data",
response -> {
// 处理高优先级响应
},
error -> {
// 处理错误
}
) {
@Override
public Priority getPriority() {
return Priority.HIGH;
}
};
// 创建一个低优先级的请求
StringRequest lowPriorityRequest = new StringRequest(
Request.Method.GET,
"https://api.example.com/low_priority_data",
response -> {
// 处理低优先级响应
},
error -> {
// 处理错误
}
) {
@Override
public Priority getPriority() {
return Priority.LOW;
}
};
// 添加请求到队列
requestQueue.add(highPriorityRequest);
requestQueue.add(lowPriorityRequest);
5.4 请求重试策略
Volley提供了灵活的请求重试机制,可以为每个请求设置不同的重试策略。以下是如何自定义重试策略的示例:
// 创建自定义重试策略
RetryPolicy retryPolicy = new DefaultRetryPolicy(
5000, // 初始超时时间(毫秒)
3, // 最大重试次数
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT // 退避乘数
);
// 创建请求并设置重试策略
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/data",
response -> {
// 处理响应
},
error -> {
// 处理错误
}
);
// 设置重试策略
request.setRetryPolicy(retryPolicy);
// 添加请求到队列
requestQueue.add(request);
5.5 请求标记与取消
可以为请求设置标记,然后根据标记取消相关请求:
// 创建请求并设置标记
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/data",
response -> {
// 处理响应
},
error -> {
// 处理错误
}
);
// 设置请求标记
request.setTag("my_request_tag");
// 添加请求到队列
requestQueue.add(request);
// 在需要取消请求的地方
requestQueue.cancelAll("my_request_tag");
// 也可以使用回调来更精细地控制取消
requestQueue.cancelAll(new RequestQueue.RequestFilter() {
@Override
public boolean apply(Request<?> request) {
// 返回true表示取消该请求
return request.getTag() != null && request.getTag().equals("my_request_tag");
}
});
5.6 自定义响应解析器
除了自定义Request子类外,还可以通过自定义响应解析器来处理特殊格式的响应:
// 创建一个自定义响应解析器
public class CustomResponseParser implements ResponseParser {
@Override
public Response<?> parseNetworkResponse(NetworkResponse response) {
try {
// 获取响应数据
String data = new String(response.data, "UTF-8");
// 自定义解析逻辑
MyCustomData customData = parseCustomData(data);
// 返回解析结果
return Response.success(customData, HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (Exception e) {
return Response.error(new ParseError(e));
}
}
// 自定义解析方法
private MyCustomData parseCustomData(String data) {
// 实现自定义解析逻辑
return new MyCustomData();
}
}
// 在自定义Request中使用自定义响应解析器
public class CustomRequest extends Request<MyCustomData> {
private final ResponseParser mParser;
public CustomRequest(int method, String url, Response.Listener<MyCustomData> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mParser = new CustomResponseParser();
}
@Override
protected Response<MyCustomData> parseNetworkResponse(NetworkResponse response) {
return (Response<MyCustomData>) mParser.parseNetworkResponse(response);
}
@Override
protected void deliverResponse(MyCustomData response) {
// 分发响应
}
}
六、Request请求对象的性能优化
6.1 缓存策略优化
合理使用缓存可以显著提高应用性能,减少网络请求:
// 创建一个请求并禁用缓存
StringRequest noCacheRequest = new StringRequest(
Request.Method.GET,
"https://api.example.com/no_cache_data",
response -> {
// 处理响应
},
error -> {
// 处理错误
}
);
// 禁用缓存
noCacheRequest.setShouldCache(false);
// 创建一个请求并设置自定义缓存时间
StringRequest cacheRequest = new StringRequest(
Request.Method.GET,
"https://api.example.com/cache_data",
response -> {
// 处理响应
},
error -> {
// 处理错误
}
) {
@Override
public Priority getPriority() {
return Priority.HIGH;
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
Response<String> resp = super.parseNetworkResponse(response);
// 自定义缓存时间
Cache.Entry entry = resp.cacheEntry;
if (entry != null) {
// 设置缓存10分钟后过期
entry.softTtl = System.currentTimeMillis() + 10 * 60 * 1000;
// 设置缓存1小时后必须刷新
entry.ttl = System.currentTimeMillis() + 60 * 60 * 1000;
}
return resp;
}
};
// 添加请求到队列
requestQueue.add(noCacheRequest);
requestQueue.add(cacheRequest);
6.2 请求合并与批量处理
对于需要同时发送多个请求的场景,可以考虑合并请求或批量处理:
// 创建一个请求批处理器
public class RequestBatchProcessor {
private final RequestQueue mRequestQueue;
private final List<Request<?>> mRequests = new ArrayList<>();
private int mCompletedRequests = 0;
private final Object mLock = new Object();
private BatchListener mListener;
public interface BatchListener {
void onBatchCompleted(List<Request<?>> successfulRequests, List<Request<?>> failedRequests);
}
public RequestBatchProcessor(RequestQueue requestQueue) {
mRequestQueue = requestQueue;
}
public void setListener(BatchListener listener) {
mListener = listener;
}
public void addRequest(Request<?> request) {
mRequests.add(request);
// 设置请求完成监听器
request.setRequestFinishedListener(new RequestFinishedListener<Object>() {
@Override
public void onRequestFinished(Request<Object> request) {
synchronized (mLock) {
mCompletedRequests++;
// 检查是否所有请求都已完成
if (mCompletedRequests == mRequests.size() && mListener != null) {
List<Request<?>> successful = new ArrayList<>();
List<Request<?>> failed = new ArrayList<>();
for (Request<?> req : mRequests) {
if (req.hasHadResponseDelivered() && !req.isCanceled()) {
successful.add(req);
} else {
failed.add(req);
}
}
mListener.onBatchCompleted(successful, failed);
}
}
}
});
}
public void execute() {
// 将所有请求添加到队列
for (Request<?> request : mRequests) {
mRequestQueue.add(request);
}
}
}
// 使用请求批处理器的示例
public void processBatchRequests() {
RequestBatchProcessor processor = new RequestBatchProcessor(requestQueue);
// 添加多个请求
processor.addRequest(createRequest1());
processor.addRequest(createRequest2());
processor.addRequest(createRequest3());
// 设置批处理完成监听器
processor.setListener(new RequestBatchProcessor.BatchListener() {
@Override
public void onBatchCompleted(List<Request<?>> successfulRequests, List<Request<?>> failedRequests) {
// 处理批处理结果
Log.d(TAG, "Successful requests: " + successfulRequests.size());
Log.d(TAG, "Failed requests: " + failedRequests.size());
}
});
// 执行批处理
processor.execute();
}
6.3 避免内存泄漏
在Activity或Fragment中使用Volley时,要特别注意避免内存泄漏:
public class MyActivity extends AppCompatActivity {
private RequestQueue mRequestQueue;
private StringRequest mRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
// 获取请求队列
mRequestQueue = Volley.newRequestQueue(this);
// 创建请求
mRequest = new StringRequest(
Request.Method.GET,
"https://api.example.com/data",
response -> {
// 处理响应
},
error -> {
// 处理错误
}
);
// 设置请求标记
mRequest.setTag(this);
// 添加请求到队列
mRequestQueue.add(mRequest);
}
@Override
protected void onStop() {
super.onStop();
// 取消所有标记为当前Activity的请求
if (mRequestQueue != null) {
mRequestQueue.cancelAll(this);
}
}
}
6.4 优化大文件请求
对于大文件请求,应考虑使用专门的下载库或自定义Request:
// 自定义文件下载Request
public class FileDownloadRequest extends Request<File> {
private final File mDestinationFile;
private final Listener<File> mListener;
public FileDownloadRequest(String url, File destinationFile,
Listener<File> listener, ErrorListener errorListener) {
super(Method.GET, url, errorListener);
mDestinationFile = destinationFile;
mListener = listener;
}
@Override
protected Response<File> parseNetworkResponse(NetworkResponse response) {
try {
// 将响应数据写入文件
FileOutputStream fos = new FileOutputStream(mDestinationFile);
fos.write(response.data);
fos.close();
// 返回成功响应
return Response.success(mDestinationFile, HttpHeaderParser.parseCacheHeaders(response));
} catch (IOException e) {
// 返回错误响应
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(File response) {
mListener.onResponse(response);
}
@Override
public Priority getPriority() {
return Priority.LOW;
}
}
// 使用自定义文件下载Request的示例
public void downloadFile() {
String url = "https://example.com/large_file.zip";
File destinationFile = new File(getExternalFilesDir(null), "large_file.zip");
FileDownloadRequest request = new FileDownloadRequest(
url,
destinationFile,
file -> {
// 下载成功
Log.d(TAG, "File downloaded successfully: " + file.getAbsolutePath());
},
error -> {
// 下载失败
Log.e(TAG, "Error downloading file: " + error.getMessage());
}
);
// 添加请求到队列
requestQueue.add(request);
}
6.5 使用合适的请求方法
根据业务需求选择合适的请求方法(GET、POST等),并合理设置请求头和请求体:
// 创建一个POST请求并设置请求体
StringRequest postRequest = new StringRequest(
Request.Method.POST,
"https://api.example.com/submit_data",
response -> {
// 处理响应
},
error -> {
// 处理错误
}
) {
@Override
protected Map<String, String> getParams() {
// 设置POST请求参数
Map<String, String> params = new HashMap<>();
params.put("key1", "value1");
params.put("key2", "value2");
return params;
}
@Override
public Map<String, String> getHeaders() {
// 设置请求头
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/x-www-form-urlencoded");
return headers;
}
};
// 添加请求到队列
requestQueue.add(postRequest);
七、Request请求对象的常见问题与解决方案
7.1 请求重复发送问题
问题描述:相同请求被多次发送,浪费网络资源。
可能原因:
- 界面刷新频繁触发重复请求
- 没有正确管理请求生命周期
- 没有实现请求去重机制
解决方案:
- 使用请求标记管理请求
// 设置请求标记
request.setTag("unique_tag");
// 取消相同标记的请求
requestQueue.cancelAll("unique_tag");
- 实现请求去重逻辑
// 在添加请求前检查是否已有相同请求
private boolean hasRequestWithUrl(String url) {
synchronized (mCurrentRequests) {
for (Request<?> request : mCurrentRequests) {
if (request.getUrl().equals(url)) {
return true;
}
}
}
return false;
}
7.2 内存泄漏问题
问题描述:Activity或Fragment销毁后,请求仍然持有对它们的引用,导致内存泄漏。
可能原因:
- 请求的回调持有Activity或Fragment的引用
- 请求没有在Activity或Fragment销毁时取消
解决方案:
- 使用弱引用或静态内部类
public class MyActivity extends AppCompatActivity {
private static class MyListener implements Response.Listener<String> {
private final WeakReference<MyActivity> activityRef;
public MyListener(MyActivity activity) {
activityRef = new WeakReference<>(activity);
}
@Override
public void onResponse(String response) {
MyActivity activity = activityRef.get();
if (activity != null) {
// 更新UI
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 使用静态内部类的监听器
StringRequest request = new StringRequest(
Request.Method.GET,
"https://api.example.com/data",
new MyListener(this),
error -> {
// 处理错误
}
);
requestQueue.add(request);
}
}
- 在Activity或Fragment的onStop或onDestroy方法中取消请求
@Override
protected void onStop() {
super.onStop();
if (requestQueue != null) {
requestQueue.cancelAll(this);
}
}
7.3 请求超时问题
问题描述:请求长时间没有响应,导致用户体验不佳。
可能原因:
- 网络状况不佳
- 服务器响应慢
- 默认超时时间设置过短
解决方案:
- 增加超时时间
request.setRetryPolicy(new DefaultRetryPolicy(
10000, // 超时时间10秒
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
));
- 实现指数退避重试策略
request.setRetryPolicy(new DefaultRetryPolicy(
5000, // 初始超时时间5秒
3, // 最多重试3次
2.0f // 退避乘数,每次重试超时时间加倍
));
7.4 缓存不生效问题
问题描述:设置了缓存但请求仍然每次都访问网络。
可能原因:
- 没有正确设置shouldCache属性
- 服务器响应头中包含不缓存指令
- 缓存键冲突
解决方案:
- 确保正确设置shouldCache属性
request.setShouldCache(true);
- 检查服务器响应头
// 在parseNetworkResponse方法中检查响应头
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
// 检查响应头中的缓存控制信息
Map<String, String> headers = response.headers;
String cacheControl = headers.get("Cache-Control");
// 处理缓存头信息
Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
// 返回解析结果
return Response.success(
new String(response.data),
entry
);
}
- 自定义缓存键
@Override
public String getCacheKey() {
// 使用URL和额外参数生成唯一缓存键
return super.getCacheKey() + "_" + getExtraParam();
}
7.5 图片加载内存溢出问题
问题描述:加载大量图片时出现内存溢出。
可能原因:
- 图片没有正确压缩
- 图片缓存过大
- 没有及时回收不再使用的图片资源
解决方案:
- 使用合适的图片压缩比例
// 在ImageRequest中设置图片配置和尺寸
ImageRequest request = new ImageRequest(
imageUrl,
listener,
100, // 最大宽度
100, // 最大高度
Bitmap.Config.RGB_565, // 使用RGB_565格式,比ARGB_8888节省一半内存
errorListener
);
- 实现图片缓存清理策略
// 限制图片缓存大小
int cacheSize = 10 * 1024 * 1024; // 10MB
LruCache<String, Bitmap> imageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
- 在适当的时候回收图片资源
// 在图片不再使用时回收
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
八、Request请求对象的最佳实践
8.1 使用单例模式管理RequestQueue
为了避免创建多个RequestQueue实例,建议使用单例模式:
public class VolleySingleton {
private static VolleySingleton instance;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static Context mCtx;
private VolleySingleton(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
mImageLoader = new ImageLoader(mRequestQueue,
new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap>
cache = new LruCache<String, Bitmap>(20);
@Override
public Bitmap getBitmap(String url) {
return cache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
cache.put(url, bitmap);
}
});
}
public static synchronized VolleySingleton getInstance(Context context) {
if (instance == null) {
instance = new VolleySingleton(context.getApplicationContext());
}
return instance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
// 创建请求队列
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
}
8.2 封装常用请求
为了提高代码复用性,建议封装常用的请求类型:
public class ApiRequestManager {
private static ApiRequestManager instance;
private RequestQueue mRequestQueue;
private Context mContext;
private ApiRequestManager(Context context) {
mContext = context;
mRequestQueue = VolleySingleton.getInstance(context).getRequestQueue();
}
public static synchronized ApiRequestManager getInstance(Context context) {
if (instance == null) {
instance = new ApiRequestManager(context);
}
return instance;
}
/**
* 获取用户信息
* @param userId 用户ID
* @param listener 成功监听器
* @param errorListener 错误监听器
*/
public void getUserInfo(String userId, Response.Listener<User> listener,
Response.ErrorListener errorListener) {
String url = "https://api.example.com/users/" + userId;
GsonRequest<User> request = new GsonRequest<>(
Request.Method.GET,
url,
new TypeToken<User>() {}.getType(),
null,
listener,
errorListener
);
mRequestQueue.add(request);
}
/**
* 提交表单数据
* @param formData 表单数据
* @param listener 成功监听器
* @param errorListener 错误监听器
*/
public void submitForm(Map<String, String> formData, Response.Listener<String> listener,
Response.ErrorListener errorListener) {
String url = "https://api.example.com/submit_form";
StringRequest request = new StringRequest(
Request.Method.POST,
url,
listener,
errorListener
) {
@Override
protected Map<String, String> getParams() {
return formData;
}
};
mRequestQueue.add(request);
}
/**
* 加载图片
* @param imageUrl 图片URL
* @param imageView 要显示图片的ImageView
*/
public void loadImage(String imageUrl, ImageView imageView) {
ImageLoader imageLoader = VolleySingleton.getInstance(mContext).getImageLoader();
imageLoader.get(imageUrl, ImageLoader.getImageListener(
imageView,
R.drawable.default_image, // 默认图片
R.drawable.error_image // 错误图片
));
}
}
8.3 错误处理与日志记录
良好的错误处理和日志记录可以帮助快速定位问题:
// 创建一个自定义Request类,处理常见错误
public class CustomRequest<T> extends Request<T> {
private final Listener<T> mListener;
public CustomRequest(int method, String url, Listener<T> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
// 解析响应
return Response.success(
parseResponseData(new String(response.data)),
HttpHeaderParser.parseCacheHeaders(response));
} catch (Exception e) {
// 记录错误日志
Log.e("CustomRequest", "Error parsing network response", e);
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
@Override
public void deliverError(VolleyError error) {
// 记录错误信息
Log.e("CustomRequest", "Error: " + error.getMessage());
// 处理常见错误类型
if (error instanceof TimeoutError) {
// 处理超时错误
} else if (error instanceof NoConnectionError) {
// 处理无网络连接错误
} else if (error instanceof AuthFailureError) {
// 处理认证失败错误
} else if (error instanceof ServerError) {
// 处理服务器错误
} else if (error instanceof NetworkError) {
// 处理网络错误
} else if (error instanceof ParseError) {
// 处理解析错误
}
super.deliverError(error);
}
// 由子类实现的解析响应数据方法
protected abstract T parseResponseData(String responseData);
}
8.4 处理网络请求与UI生命周期
确保网络请求与Activity或Fragment的生命周期正确关联:
public class MyFragment extends Fragment {
private RequestQueue mRequestQueue;
private StringRequest mCurrentRequest;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRequestQueue = VolleySingleton.getInstance(getActivity()).getRequestQueue();
}
@Override
public void onResume() {
super.onResume();
// 在Fragment可见时发起请求
fetchData();
}
@Override
public void onPause() {
super.onPause();
// 在Fragment不可见时取消正在进行的请求
if (mCurrentRequest != null) {
mCurrentRequest.cancel();
}
}
private void fetchData() {
String url = "https://api.example.com/data";
mCurrentRequest = new StringRequest(
Request.Method.GET,
url,
response -> {
// 更新UI
updateUI(response);
},
error -> {
// 处理错误
showError(error.getMessage());
}
);
// 设置请求标记
mCurrentRequest.setTag(this);
// 添加请求到队列
mRequestQueue.add(mCurrentRequest);
}
@Override
public void onDestroy() {
super.onDestroy();
// 取消所有标记为当前Fragment的请求
mRequestQueue.cancelAll(this);
}
}
8.5 优化图片加载
对于图片加载,考虑使用专门的图片加载库,但如果使用Volley,也可以进行优化:
// 创建一个优化的ImageLoader
public class OptimizedImageLoader extends ImageLoader {
private static final int MAX_IMAGE_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
public OptimizedImageLoader(RequestQueue queue, ImageCache imageCache) {
super(queue, imageCache);
}
// 创建带有最佳配置的ImageLoader
public static OptimizedImageLoader newInstance(Context context, RequestQueue requestQueue) {
// 使用LruCache作为图片缓存
ImageCache imageCache = new ImageCache() {
private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(MAX_IMAGE_CACHE_SIZE) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
};
return new OptimizedImageLoader(requestQueue, imageCache);
}
// 加载图片并自动调整尺寸
public ImageContainer loadImage(String requestUrl, final ImageListener listener,
int maxWidth, int maxHeight) {
// 如果没有指定最大尺寸,使用默认值
if (maxWidth <= 0 && maxHeight <= 0) {
maxWidth = DEFAULT_IMAGE_MAX_WIDTH;
maxHeight = DEFAULT_IMAGE_MAX_HEIGHT;
}
return super.loadImage(requestUrl, listener, maxWidth, maxHeight);
}
private static final int DEFAULT_IMAGE_MAX_WIDTH = 400;
private static final int DEFAULT_IMAGE_MAX_HEIGHT = 400;
}
通过以上最佳实践,可以更高效、更安全地使用Volley的Request请求对象,提升应用的网络性能和用户体验。