十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
这篇文章将为大家详细讲解有关如何在Retrofit中自定义请求参数注解,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
石林网站制作公司哪家好,找创新互联公司!从网页设计、网站建设、微信开发、APP开发、响应式网站开发等网站项目制作,到程序开发,运营维护。创新互联公司成立与2013年到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联公司。
Retrofit 中使用方式
先来看看在 Retrofit 中对于这两种请求的声明方式:
GET 请求
@GET("transporter/info") FlowablegetTransporterInfo(@Query("uid") long id);
我们使用 @Query 注解来声明查询参数,每一个参数都需要用 @Query 注解标记
POST 请求
@POST("transporter/update") FlowablechangBind(@Body Map params);
在 Post 请求中,我们通过 @Body 注解来标记需要传递给服务器的对象
Post 请求参数的声明能否更直观
以上两种常规的请求方式很普通,没有什么特别要说明的。
有次团队讨论一个问题,我们所有的请求都是声明在不同的接口中的,如官方示例:
public interface GitHubService { @GET("users/{user}/repos") Call> listRepos(@Path("user") String user); }
如果是 GET 请求还好,通过 @Query 注解我们可以直观的看到请求的参数,但如果是 POST 请求的话,我们只能够在上层调用的地方才能看到具体的参数,那么 POST 请求的参数声明能否像 GET 请求一样直观呢?
@Field 注解
先看代码,关于 @Field 注解的使用:
@FormUrlEncoded @POST("user/edit") CallupdateUser(@Field("first_name") String first, @Field("last_name") String last);
使用了 @Field 注解之后,我们将以表单的形式提交数据(first_name = XXX & last_name = yyy)。
基于约定带来的问题
看上去 @Field 注解可以满足我们的需求了,但遗憾的是之前我们和 API 约定了 POST 请求数据传输的格式为 JSON 格式,显然我们没有办法使用该注解了
Retrofit 参数注解的处理流程
这个时候我想是不是可以模仿 @Field 注解,自己实现一个注解最后使得参数以 JSON 的格式传递给 API 就好了,在此之前我们先来看看 Retrofit 中对于请求的参数是如何处理的:
ServiceMethod 中 Builder 的构造函数
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations(); }
我们关注三个属性:
methodAnnotations 方法上的注解,Annotation[] 类型
parameterTypes 参数类型,Type[] 类型
parameterAnnotationsArray 参数注解,Annotation[][] 类型
在构造函数中,我们主要对这 5 个属性赋值。
Builder 构造者的 build 方法
接着我们看看在通过 build 方法创建一个 ServiceMethod 对象的过程中发生了什么:
//省略了部分代码... public ServiceMethod build() { //1. 解析方法上的注解 for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler[parameterCount]; for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; //2. 通过循环为每一个参数创建一个参数处理器 parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); } return new ServiceMethod<>(this); }
解析方法上的注解 parseMethodAnnotation
if (annotation instanceof GET) { parseHttpMethodAndPath("GET", ((GET) annotation).value(), false); }else if (annotation instanceof POST) { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); }
我省略了大部分的代码,整段的代码其实就是来判断方法注解的类型,然后继续解析方法路径,我们仅关注 POST 这一分支:
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) { this.httpMethod = httpMethod; this.hasBody = hasBody; // Get the relative URL path and existing query string, if present. // ... }
可以看到这条方法调用链其实就是确定 httpMethod 的值(请求方式:POST),hasBody(是否含有 Body 体)等信息
创建参数处理器
在循环体中为每一个参数都创建一个 ParameterHandler:
private ParameterHandler parseParameter( int p, Type parameterType, Annotation[] annotations) { ParameterHandler result = null; for (Annotation annotation : annotations) { ParameterHandler annotationAction = parseParameterAnnotation( p, parameterType, annotations, annotation); } // 省略部分代码... return result; }
可以看到方法内部接着调用了 parseParameterAnnotation 方法来返回一个参数处理器:
对于 @Field 注解的处理
else if (annotation instanceof Field) { Field field = (Field) annotation; String name = field.value(); boolean encoded = field.encoded(); gotField = true; Converter converter = retrofit.stringConverter(type, annotations); return new ParameterHandler.Field<>(name, converter, encoded); }
获取注解的值,也就是参数名
根据参数类型选取合适的 Converter
返回一个 Field 对象,也就是 @Field 注解的处理器
ParameterHandler.Field
//省略部分代码 static final class Fieldextends ParameterHandler { private final String name; private final Converter valueConverter; private final boolean encoded; //构造函数... @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException { String fieldValue = valueConverter.convert(value); builder.addFormField(name, fieldValue, encoded); } }
通过 apply 方法将 @Filed 标记的参数名,参数值添加到了 FromBody 中
对于 @Body 注解的处理
else if (annotation instanceof Body) { Converter converter; try { converter = retrofit.requestBodyConverter(type, annotations, methodAnnotations); } catch (RuntimeException e) { // Wide exception range because factories are user code.throw parameterError(e, p, "Unable to create @Body converter for %s", type); } gotBody = true; return new ParameterHandler.Body<>(converter); }
选取合适的 Converter
gotBody 标记为 true
返回一个 Body 对象,也就是 @Body 注解的处理器
ParameterHandler.Body
static final class Bodyextends ParameterHandler { private final Converter converter; Body(Converter converter) { this.converter = converter; } @Override void apply(RequestBuilder builder, @Nullable T value) { RequestBody body; try { body = converter.convert(value); } catch (IOException e) { throw new RuntimeException("Unable to convert " + value + " to RequestBody", e); } builder.setBody(body); } }
通过 Converter 将 @Body 声明的对象转化为 RequestBody,然后设置赋值给 body 对象
apply 方法什么时候被调用
我们来看看 OkHttpCall 的同步请求 execute 方法:
//省略部分代码... @Override public Responseexecute() throws IOException { okhttp3.Call call; synchronized (this) { call = rawCall; if (call == null) { try { call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); // Do not assign a fatal error to creationFailure. creationFailure = e; throw e; } } return parseResponse(call.execute()); }
在方法的内部,我们通过 createRawCall 方法来创建一个 call 对象,createRawCall 方法内部又调用了 serviceMethod.toRequest(args);
方法来创建一个 Request 对象:
/** * 根据方法参数创建一个 HTTP 请求 */ Request toRequest(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); ParameterHandler