探秘 Android Volley JsonObjectRequest(7)

3 阅读49分钟

探秘 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 类主要完成了以下工作:

  1. 定义了默认的字符集和 Content-Type,用于处理 JSON 数据。
  2. 在构造函数中接收并保存请求的 JSON 数据。
  3. 重写了 getBodyContentType 方法,返回 JSON 数据的 Content-Type。
  4. 重写了 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 类定义了请求的核心属性和行为,包括:

  1. 请求的基本信息(URL、方法、优先级等)。
  2. 请求的状态管理(是否取消、是否已分发响应等)。
  3. 请求的重试策略管理。
  4. 请求头和请求体的获取方法。
  5. 抽象方法 parseNetworkResponse,由子类实现,用于解析网络响应。
  6. 响应和错误的分发方法。

通过继承 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 实例。需要注意以下几点:

  1. 第一个参数指定请求方法为 Request.Method.GET
  2. 第二个参数是请求的 URL。
  3. 第三个参数是请求体,由于 GET 方法通常不包含请求体,所以传入 null
  4. 第四个参数是请求成功的回调监听器,用于处理服务器返回的 JSON 对象。
  5. 第五个参数是请求失败的回调监听器,用于处理请求过程中出现的错误。

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 实例。需要注意以下几点:

  1. 第一个参数指定请求方法为 Request.Method.POST
  2. 第三个参数是请求体,传入一个包含提交数据的 JSON 对象。
  3. 通过重写 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) {
                // 处理错误
            }
        }
);

在上述示例中,我们通过 StringBuilderURLEncoder 构建了包含查询参数的完整 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 的工作流程如下:

  1. 当调用 add 方法添加请求时,根据请求是否需要缓存,将其放入 mCacheQueuemNetworkQueue
  2. 调用 start 方法启动缓存调度器(CacheDispatcher)和网络调度器(NetworkDispatcher)。
  3. 缓存调度器从 mCacheQueue 中取出请求,检查缓存中是否有对应数据。如果有且未过期,则直接返回缓存数据;否则,将请求放入 mNetworkQueue
  4. 网络调度器从 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 的处理流程如下:

  1. 从队列中取出请求CacheDispatchermCacheQueue 中取出一个请求。

  2. 检查请求是否已取消:如果请求已被取消,则跳过处理。

  3. 尝试获取缓存数据:根据请求的缓存键(通常是 URL)从缓存中获取数据。

  4. 缓存未命中处理:如果缓存中没有数据,将请求转发到网络请求队列。

  5. 缓存过期处理:如果缓存数据已过期,将请求转发到网络请求队列,但保留缓存数据,以便在网络请求期间使用。

  6. 缓存命中且未过期处理

    • 解析缓存数据,生成响应对象。
    • 检查是否需要刷新缓存(例如,数据可能已过时但仍可使用)。
    • 如果需要刷新缓存,将响应标记为中间响应,分发给监听器,并将请求转发到网络请求队列以获取最新数据。
    • 如果不需要刷新缓存,直接将响应分发给监听器。

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 的处理流程如下:

  1. 从队列中取出请求NetworkDispatchermNetworkQueue 中取出一个请求。

  2. 检查请求是否已取消:如果请求已被取消,则跳过处理。

  3. 设置流量统计标签:为请求设置流量统计标签,用于监控网络流量。

  4. 执行网络请求:调用 mNetwork.performRequest(request) 方法执行实际的网络请求,获取网络响应。

  5. 处理 304 状态码:如果服务器返回 304 状态码且请求已有缓存条目,表示资源未修改,直接使用缓存数据。

  6. 解析网络响应:调用 request.parseNetworkResponse(networkResponse) 方法解析网络响应数据。

  7. 缓存响应数据:如果请求需要缓存且响应包含缓存条目,将响应数据存入缓存。

  8. 分发响应结果:标记请求已被分发,并通过 mDelivery.postResponse(request, response) 将响应结果分发到主线程。

  9. 错误处理:如果在处理过程中出现错误,捕获异常并通过 parseAndDeliverNetworkError 或直接将错误信息分发到主线程。

6.3 Network 接口与 BasicNetwork 实现

Network 接口定义了执行网络请求的方法:

public interface Network {
    /**
     * 执行网络请求
     * @param request 需要执行的请求
     * @return 网络响应
     * @throws VolleyError 请求过程中出现的错误
     */
    NetworkResponse performRequest(Request<?> request) throws VolleyError;
}

BasicNetworkNetwork 接口的默认实现类,其 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);
        }
    }
}

BasicNetworkperformRequest 方法首先准备请求头,包括添加缓存相关头信息和请求本身的头信息。然后通过 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);
}

ExecutorDeliveryResponseDelivery 接口的默认实现类,它使用 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,用于在主线程执行任务。其 postResponsepostError 方法会将响应或错误封装到 ResponseDeliveryRunnable 中,并通过 Executor 在主线程执行。ResponseDeliveryRunnablerun 方法会根据响应状态调用请求的相应监听器,并处理请求的完成状态和回调任务。

8.2 请求回调的执行过程

当响应结果被分发到主线程后,会调用 JsonObjectRequestdeliverResponse 方法:

@Override
protected void deliverResponse(JSONObject response) {
    // 调用响应监听器的 onResponse 方法,将解析后的 JSON 对象响应传递给它
    if (mListener != null) {
        mListener.onResponse(response);
    }
}

这个方法非常简单,只是调用了在创建 JsonObjectRequest 时传入的响应监听器的 onResponse 方法,并将解析后的 JSON 对象响应作为参数传递给它。开发者在创建请求时实现的 Response.Listener<JSONObject> 接口中的 onResponse 方法会在主线程中被调用,这样开发者就可以在该方法中安全地更新 UI 或进行其他与主线程相关的操作。

同样,当请求出现错误时,会调用 JsonObjectRequestdeliverError 方法:

@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));
}

ResponseDeliveryRunnablerun 方法中:

@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);
    }
    
    // 其他处理...
}

JsonObjectRequestdeliverError 方法实现:

@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));
}

在这个过程中,DefaultRetryPolicyretry 方法会增加重试次数,并根据退避乘数计算新的超时时间。如果重试次数超过最大重试次数,则抛出原始错误,不再重试。

开发者可以通过以下方式自定义重试策略:

// 创建自定义重试策略,设置更长的超时时间和更多的重试次数
RetryPolicy retryPolicy = new DefaultRetryPolicy(
        5000, // 初始超时时间 5 秒
        3,    // 最大重试次数 3 次
        DefaultRetryPolicy.DEFAULT_BACKOFF_MULT // 默认退避乘数
);

// 为请求设置自定义重试策略
jsonObjectRequest.setRetryPolicy(retryPolicy);

9.4 错误处理最佳实践

在使用 JsonObjectRequest 时,开发者可以采用以下最佳实践来处理各种错误情况:

  1. 实现全面的错误监听器
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("发生未知错误,请稍后重试");
                }
            }
        }
);
  1. 设置合理的重试策略
// 设置合理的超时时间和重试次数
jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(
        5000, // 5 秒超时时间
        2,    // 最多重试 2 次
        DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
));
  1. 检查网络连接状态: 在发送请求前,检查设备的网络连接状态,避免不必要的网络请求:
// 检查网络连接状态
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("网络连接不可用,请检查网络设置");
}
  1. 使用进度指示器: 在发送请求时显示进度指示器,提高用户体验:
// 显示进度对话框
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();
        }
    }
}
  1. 处理服务器返回的错误信息: 如果服务器返回了包含错误信息的 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 自定义请求头

在某些情况下,需要为请求添加自定义头信息,例如认证令牌、用户代理等。可以通过重写 JsonObjectRequestgetHeaders 方法来实现:

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 允许为不同请求设置不同的优先级,从而控制请求的执行顺序。可以通过重写 JsonObjectRequestgetPriority 方法来设置请求优先级:

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 请求进行调试和性能优化。以下是一些建议:

  1. 启用 Volley 日志
// 在开发环境中启用 Volley 详细日志
VolleyLog.DEBUG = BuildConfig.DEBUG;
  1. 使用 NetworkDispatcher 线程数优化
// 创建自定义请求队列,增加网络调度线程数提高并发性能
RequestQueue requestQueue = Volley.newRequestQueue(context, 4); // 使用 4 个网络调度线程
  1. 监控请求执行时间
// 在请求中添加标记监控请求执行时间
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");
        
        // 处理响应
    }
}
  1. 使用 NetworkResponse 中的网络耗时信息
new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        if (error.networkResponse != null) {
            Log.d(TAG, "Network time: " + error.getNetworkTimeMs() + "ms");
        }
        
        // 处理错误
    }
}
  1. 分析请求标记
// 打印请求的所有标记,用于分析请求执行流程
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 提供了更高级