Volley 框架自定义请求(Request)之参数传递

一、前言

    Volley 是 Android 官方的 HTTP 请求框架 ,它是基于 HttpURLConnection 的,OKHttp 是基于 WebSocket 的,它有一些优势,也有一些缺点,更多关于 Volley的使用,可以参考官方文档:使用 Volley 传输网络数据

二、Volley 自定义请求(Request)

    使用过 Volley 的都知道,Volley 已经提供了一些请求(Request)的实现,他们分别是:

  • StringRequest:请求返回的数据是 String 类型
  • JsonObjectRequest:请求返回的数据是 JSONObject 类型
  • JsonArrayRequest:请求返回的数据是 JSONArray 类型
  • ImageRequest:请求返回的数据是图片

2.1 自定义请求(Request)简要

    一般来说,以上请求类型可以满足基本需求,如果不满足,开发者也可以根据自己的需求自定义请求(Request)。自定义请求可以继承框架现有的请求类(比如 StringRequest,可以通过继承他实现 POST 请求传递参数),也可以直接继承 Request 抽象类,自定义请求主要需要关注以下一些方法。

  • parseNetworkResponse(NetworkResponse response):解析网络返回的元数据(字节数组),并返回目标数据类型的数据。如果直接继承 Request 抽象类,这个方法必须重写。
  • deliverResponse(T response):分发相应数据,将解析的目标数据分发回调(可在此做一些相应的数据处理)。如果直接继承 Request 抽象类,这个方法必须重写。
  • getHeaders():返回请求需要添加的额外 HTTP 头数据(若提供这些值需要认证,有可能会抛出 AuthFailureError),重写该方法可以往请求中添加额外的 HTTP 请求头参数。
  • getParams():返回请求参数映射表,只对 POST 或 PUT 类型的请求有效(若提供这些值需要认证,有可能会抛出 AuthFailureError),重写该方法可以往POST 和 PUT 请求中传递参数。
  • getParamsEncoding():返回参数编码类型,Volley 默认是 utf-8 编码。重写此方法可以改变参数编码类型。
  • getBody():返回 POST 或 PUT 请求需要发送的请求体元数据(字节数组)。Request 类默认实现是针对 HTTP form 结构(application/x-www-form-urlencoded; charset=utf-8,即 key1=values1&key2=value2 表单结构),如果需要传入其他结构的数据,可以重写此方法,但是注意需要与 getBodyContentType() 方法同步修改,保持一致。
  • getBodyContentType():返回 POST 或 PUT 请求体的内容类型,对应请求头的 Content-Type。重写此方法,可改变请求体的数据结构,但是需要确保 getBody() 方法返回的请求体内容的结构与之对应。

说明:更多详细说明可以参考 Volley 源码中 Request 类的声明与注释,Volley Github项目

2.2 通过源码剖析讲解自定义请求(Request)

    下面,将使用 JsonObjectRequest 源码详细说明下自定义请求。

我们先来看看 JsonObjectRequest 的构造函数,其中参数 jsonRequest 的注释说明是需要通过请求发送的参数数据(为空时表示无参数),因为 JsonObjectRequest 不是直接继承自 Request 类,而是中间还有一层继承 JsonRequest,所以,我们同时看看 JsonObjectRequest 的构造函数中通过 super 调用父类的构造函数,需要注意的是,请求体数据类型被转换为 JSONObject 格式的字符串。

public class JsonObjectRequest extends JsonRequest<JSONObject> {
    /**
     * Creates a new request.
     *
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param jsonRequest A {@link JSONObject} to post with the request. Null indicates no
     *     parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(
            int method,
            String url,
            @Nullable JSONObject jsonRequest,
            Listener<JSONObject> listener,
            @Nullable ErrorListener errorListener) {
        super(
                method,
                url,
                jsonRequest != null ? jsonRequest.toString() : null,
                listener,
                errorListener);
    }
}

public abstract class JsonRequest<T> extends Request<T> {
    /**
     * Creates a new request.
     *
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param requestBody The content to post as the body of the request. Null indicates no
     *     parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonRequest(
            int method,
            String url,
            @Nullable String requestBody,
            Listener<T> listener,
            @Nullable ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
        mRequestBody = requestBody;
    }
}

接下来,看看 parseNetworkResponse(NetworkResponse response) 的实现,因为 JsonObjectRequest 返回值类型是 JSONObject,所以重写该方法,读取网络请求返回的元数据,将其转换成 JSONObject 类型,以及解析转换的异常处理。

public class JsonObjectRequest extends JsonRequest<JSONObject> {
    @Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            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) {
            return Response.error(new ParseError(je));
        }
    }
}

继续看对请求体内容的处理,前面知道, JsonObjectRequest 请求参数为 JSONObject 类型,在类中也是以 JSONObject 类型的字符存储,在 getBody() 方法中,直接对参数字符串转换成字节数组,也就是说请求内容体为 JSON 类型。因此,需要同步重写 getBodyContentType() 方法,返回 application/json; charset=utf-8 类型。

public abstract class JsonRequest<T> extends Request<T> {
   
    /** Default charset for JSON request. */
    protected static final String PROTOCOL_CHARSET = "utf-8";

    /** Content type for request. */
    private static final String PROTOCOL_CONTENT_TYPE =
            String.format("application/json; charset=%s", PROTOCOL_CHARSET);

    @Override
    public String getBodyContentType() {
        return PROTOCOL_CONTENT_TYPE;
    }

    @Override
    public byte[] getBody() {
        try {
            return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
        } catch (UnsupportedEncodingException uee) {
            VolleyLog.wtf(
                    "Unsupported Encoding while trying to get the bytes of %s using %s",
                    mRequestBody, PROTOCOL_CHARSET);
            return null;
        }
    }
}

注意事项:在自定义请求时,对请求体的数据类型,必须是服务器支持的类型,否则服务端将无法识别请求体中的参数数据。

三、Volley 自定义请求(Request)的参数传递

    在前面通过源码剖析 JsonObjectRequest 请求时已经提及参数传递的方法,但是都有限定条件,POST 或 PUT 请求。下面,将介分别介绍 GET 请求和 POST 请求传递参数的方法。

2.1 GET 请求

    GET 请求的传参无法通过请求体(body)进行传递,只能通过请求头部(Header)或者 URL 参数进行传递,但是这个必须根据提供接口的服务端所支持的方式来传递参数,否则服务端将无法识别参数,导致接口请求失败。以下是通过继承 StringRequest 实现 GET 请求传递参数的示例:

class StringGetRequest(
    url: String,
    private val headerParams: HashMap<String,String>?,
    listener: Response.Listener<String>,
    errorListener: Response.ErrorListener
) : StringRequest(Method.GET, url, listener, errorListener) {
    override fun getHeaders(): MutableMap<String, String> {
        return headers ?: super.getHeaders()
    }
}

@Test
fun testStringGetReq() {
    val context = InstrumentationRegistry.getInstrumentation().targetContext

    val vq = Volley.newRequestQueue(context)

    val url = "https://api.test.com?key1=value1&key2=value2" // 在 URL 中使用 URL 参数传参

    // 也可通过头部传递参数到服务端
    val header= HashMap<String, String>()
    header.put("key1", "value1")
    header.put("key2", "value2")

    val req = StringGetRequest(url, null,
        { response ->
            println(response.toString())
        },
        { error ->
            error.printStackTrace()
            println(error?.localizedMessage)
        }
    )

    vq.add(req)
}

说明:示例代码中的 URL 为示例接口链接,非真实接口,如需要测试,请改为自己的接口 API 地址。

2.2 POST 请求

    POST 请求的传参方式比较多,可以和GET请求一样通过通过请求头部(Header)或者 URL 参数进行传递(需要供接口的服务端支持),还可以通过请求体(body)进行传递。以下示例是一个使用 Gson 框架解析数据的 POST 请求,该请求通过HTTP 表单格式通过请求体(body)传递参数:

class GsonRequest<T>(
    url: String,
    private val clazz: Class<T>,
    private val headers: MutableMap<String, String>?,
    private val paramsMap: MutableMap<String, String>?,
    private val listener: Response.Listener<T>,
    errorListener: Response.ErrorListener
) : Request<T>(Method.GET, url, errorListener) {
    private val gson = Gson()

    override fun getHeaders(): MutableMap<String, String> = headers ?: super.getHeaders()
    
    // Request 类默认 body 数据格式为 application/x-www-form-urlencoded; charset=utf-8,只需要重写 getParams() 方法即可
    override fun getParams(): MutableMap<String, String> = paramsMap ?: super.getParams()

    override fun deliverResponse(response: T) = listener.onResponse(response)

    override fun parseNetworkResponse(response: NetworkResponse?): Response<T> {
        return try {
            val json = String(
                response?.data ?: ByteArray(0),
                Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
            Response.success(
                gson.fromJson(json, clazz)/* 使用Gson 将数据转换成目标数据实体类 */, 
                HttpHeaderParser.parseCacheHeaders(response))
        } catch (e: UnsupportedEncodingException) {
            Response.error(ParseError(e))
        } catch (e: JsonSyntaxException) {
            Response.error(ParseError(e))
        }
    }
}

@Test
fun testGsonReq() {

    val context = InstrumentationRegistry.getInstrumentation().targetContext

    val vq = Volley.newRequestQueue(context)

    val url = "https://api.test.com/init/"

    val params = HashMap<String, String>()
    params.put("key1", "value1")
    params.put("key2", "value2")
    params.put("key3", "value3")

    val req = GsonRequest<InitData>(url, InitData::class.java, null, params,
        { response ->
            println(response.toString())
        },
        { error ->
            error.printStackTrace()
            println(error?.localizedMessage)
        }
    )
    vq.add(req)
}

// 定义 GsonRequest 返回数据实体类
data class InitData(val code: Int, val msg: String, val data : Any)

示例讲解:Request 类默认 body 数据格式为 application/x-www-form-urlencoded; charset=utf-8,并且在 getBody() 已经默认实现 HTTP 表单格式数据拼接,若使用默认的数据格式传递参数,只需要重写 getParams() 方法,返回你需要传递的参数映射表(请求被调用时,会通过该方法获取需要传递的参数)。如果需要改变 body 的数据格式,需要重写 getBodyContentType() 定义 body 的数据格式,并在 getBody() 方法中返回对应数据格式的 body 数据,但是要确保提供接口 API 的服务端支持定义的数据格式,可参考下源码中 JsonObjectRequest 的实现。

四、编后语

    Volley 框架还有许多特点,本篇幅主要是讲解自定义请求以及如何通过自定义请求传递参数,以后遇到有趣的特性,再跟大家分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值