☰
Android 官方培训中文版:用 Volley 实现自定义网络请求(Request 子类与 GsonRequest 实战)
2026/10/5 6:51:06 网站建设 项目流程
  • 文档
  • 教程
  • 移动开发

【免费下载链接】android-training-course-in-chinese

Android官方培训课程中文版

项目地址:https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese
点击查看免费下载

本文改编自 Android 官方培训课程中文版 实现自定义的网络请求,讲解当 Volley 内置的 String、图片、JSON 请求类型无法满足业务需求时,如何通过继承泛型基类Request<T>编写自己的请求类型。你将掌握parseNetworkResponse()与deliverResponse()两个抽象方法的完整语义、基于 Gson 的 JSON 请求类完整写法,以及自定义请求如何接入 请求队列 与单例架构,最终具备把任意响应格式(XML、Protobuf、自定义文本协议等)封装为可复用请求类的能力。

什么时候需要自定义请求

Volley 的 "工具箱"(toolbox)已经覆盖了绝大多数常见场景。在 创建标准的网络请求 一课中可以看到,以下开箱即用的请求类型足够应付日常开发:

  • StringRequest:指定 URL,在响应回调中接收原始字符串;
  • ImageRequest:指定 URL,接收解码后的Bitmap,并支持按指定尺寸重采样;
  • JsonObjectRequest/JsonArrayRequest(二者均为JsonRequest的子类):接收JSONObject或JSONArray。

如果响应数据只是 string、image 或 JSON 中的一种,不需要自己实现请求类。只有当响应格式不在上述范围内——例如需要把 JSON 直接映射成 Java 对象、解析 XML、Protobuf 或其他自定义协议时——才需要自定义请求类型。

自定义请求只需完成两步:

  1. 继承Request<T>类。泛型参数<T>表示解析后响应对象的数据类型。例如要解析出String,就继承Request<String>。官方建议参考工具箱中的StringRequest与ImageRequest来学习继承方式(更完整的参考是 Volley 中JsonRequest及其子类对请求正文与响应正文的处理)。
  2. 实现两个抽象方法:parseNetworkResponse()与deliverResponse(),下面详细介绍。

parseNetworkResponse:在工作线程中解析响应

parseNetworkResponse()是自定义请求的核心。它的签名与职责如下:

@Override protected Response<T> parseNetworkResponse( NetworkResponse response) { try { String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response)); } // handle errors ... }

理解这段代码需要抓住三个关键点:

1. 参数NetworkResponse的结构NetworkResponse以byte[]、HTTP status code 以及 response headers 三种形态携带响应负载:

  • response.data:HTTP 响应正文的原始字节数组;
  • response.statusCode:HTTP 状态码(可从文档结构推断,如 200、404、500 等);
  • response.headers:响应头集合(Map<String, String>),其中Content-Type等字段决定了字符集编码。

2. 返回值Response<T>方法必须返回一个Response<T>,它要么包含"指定类型的响应对象 + 缓存元数据",要么包含一个错误:

  • 成功:Response.success(myDecodedObject, HttpHeaderParser.parseCacheHeaders(response));
  • 失败:Response.error(new ParseError(e))这类错误封装。

Response<T>把"解析结果"与"网络传输细节"彻底隔离,上层(deliverResponse回调或ErrorListener)无需关心字节流层面的细节。

3.HttpHeaderParser的两个关键工具方法

  • parseCharset(response.headers):依据Content-Type头解析响应字符集,并给出合理默认值(通常为 UTF-8)。new String(response.data, charset)用它把字节数组正确转成字符串,避免乱码;
  • parseCacheHeaders(response):把 HTTP 响应头(Cache-Control、Expires、ETag、Last-Modified等)翻译成 Volley 内部的Cache.Entry,从而让自定义请求自动享受 请求队列 中的磁盘缓存能力。

如果你的协议没有标准的 HTTP 缓存机制,可以自行构造一个Cache.Entry返回;但大多数请求都可以用文档推荐的方式处理:

return Response.success(myDecodedObject, HttpHeaderParser.parseCacheHeaders(response));

线程模型是本节的核心价值:Volley 在工作线程中执行parseNetworkResponse()。这意味着 decode 一张 JPEG 成 Bitmap、Gson 反序列化大对象这类耗时操作不会阻塞 UI 线程——这正是 发送简单的网络请求 一课所描述的生命周期中"网络线程解析响应"环节的具体实现点。结合上图的请求生命周期:请求入队后由缓存线程/网络线程处理,耗时的解析发生在这些工作线程上,解析结果最终才被投递到主线程。

deliverResponse:把结果带回主线程

parseNetworkResponse()产出的Response<T>并不能直接操作 UI。Volley 会把该结果带到主线程的回调中,这一步由deliverResponse()完成:

protected void deliverResponse(T response) { listener.onResponse(response); }

这里listener是构造请求时传入的Response.Listener<T>。由于deliverResponse()运行在主线程,回调里可以直接安全地更新控件——这也是 Volley "任何线程都可以发起请求、响应统一回到主线程" 承诺的实现保证。

两个方法的职责边界:

方法执行线程职责
parseNetworkResponse(NetworkResponse)工作线程(缓存/网络线程)把字节流解析为业务类型 T,并附带缓存元数据或错误
deliverResponse(T)主线程把解析结果转发给Listener.onResponse(),允许直接更新 UI

完整示例:GsonRequest

Gson 是一个支持 JSON 与 Java 对象之间相互转换的库。只需定义与 JSON keys 名称对应的 Java 对象,把对象类传给 Gson,Gson 就会自动填充字段值。下面是文档给出的完整示例:一个使用 Gson 解析 Volley 响应数据的自定义请求类:

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; /** * Make a GET request and return a parsed object from JSON. * * @param url URL of the request to make * @param clazz Relevant class object, for Gson's reflection * @param headers Map of request headers */ public GsonRequest(String url, Class<T> clazz, Map<String, String> headers, Listener<T> listener, ErrorListener errorListener) { super(Method.GET, 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 void deliverResponse(T response) { listener.onResponse(response); } @Override protected Response<T> parseNetworkResponse(NetworkResponse response) { try { String json = new String( response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success( gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } } }

逐段拆解这个类,可以看到自定义请求的完整骨架:

构造器与字段

  • clazz(Class<T>):目标 Java 对象的类型字面量,Gson 靠它做反射映射;
  • headers:自定义请求头集合,可选;
  • listener:成功回调;errorListener透传给父类构造器super(Method.GET, url, errorListener),从而让 Volley 统一管理错误回调;
  • super(Method.GET, url, errorListener):指定请求方法为 GET,同时把 URL 与错误监听交给基类。

覆盖getHeaders()默认实现返回空映射;这里通过headers != null ? headers : super.getHeaders()支持注入自定义头。这对需要携带Authorization、Accept、X-API-Key等认证/协商头的接口至关重要(AuthFailureError是认证失败场景下由基类抛出的错误类型)。

实现deliverResponse(T)直接转发给listener.onResponse(response),与上一节讲解的通用写法完全一致。

实现parseNetworkResponse(NetworkResponse)解析流程:

  1. 用HttpHeaderParser.parseCharset(response.headers)得到字符集,把response.data转成 JSON 字符串;
  2. gson.fromJson(json, clazz)反射填充目标对象的字段;
  3. Response.success(..., HttpHeaderParser.parseCacheHeaders(response))把对象与缓存元数据一起返回;
  4. 捕获两类异常并统一包装为ParseError:UnsupportedEncodingException(字符集不可识别,理论上parseCharset带默认值,属防御性捕获)与JsonSyntaxException(JSON 结构不合法或字段类型不匹配,这是最常见的运行时错误来源)。

接入请求队列与单例架构

自定义请求的使用方式与内置请求完全一致:构造请求对象,通过add()加入RequestQueue即可。

// 单例写法参考 request-queue.md 中的 MySingleton GsonRequest<User> request = new GsonRequest<>( "https://api.example.com/user/me", User.class, null, // 无需自定义头时可传 null new Response.Listener<User>() { @Override public void onResponse(User user) { // 主线程回调,可直接更新 UI } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // 统一错误处理 } }); MySingleton.getInstance(context).addToRequestQueue(request);

两个集成要点(来自 建立请求队列):

  • 用单例持有RequestQueue:自定义请求依赖队列来执行解析线程与主线程回跳,队列必须用 Application context 创建并贯穿 app 生命周期,避免 Activity 重建(如屏幕旋转)导致队列失效、请求被丢弃;
  • 结合cancel()与 tag 机制:发送简单的网络请求 一课说明,对请求调用cancel()后响应 Handler 不会被执行。自定义请求同样可以setTag()后按范围取消(例如在onStop()里cancelAll(tag)),防止过期响应更新已销毁的界面。

能不自定义就别自定义

正如文档在结尾提醒的:如果你愿意使用,Volley 直接提供了现成的JsonArrayRequest与JsonObjectRequest类(文档原文中的JsonArrayObject应为JsonObjectRequest,后者允许传入可选JSONObject作为请求正文)。在动手继承Request<T>之前,先对照 创建标准的网络请求 确认内置类型是否已经覆盖需求;只有内置类型无法表达"响应数据的业务形态"时,才走上文这条自定义路线。

整体脉络回顾:自定义请求 = 继承Request<T>(泛型决定解析结果类型)+ 在工作线程的parseNetworkResponse()里完成字节流到业务对象的转换(用HttpHeaderParser处理字符集与缓存)+ 在主线程的deliverResponse()里把结果交给监听器。掌握这条骨架,任何响应格式都可以被封装成与StringRequest一样简洁、可复用的 Volley 请求组件。

  • 文档
  • 教程
  • 移动开发

【免费下载链接】android-training-course-in-chinese

Android官方培训课程中文版

项目地址:https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese
点击查看免费下载

相关推荐

上一篇:Velero(Heptio Ark)云厂商接入实战:AWS/GCP/Azure 配置与备份恢复指南
下一篇:es-toolkit 函数式编程 difference:用 pipe 优雅实现数组差集过滤

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询