2023年7月25日发(作者:)
android⾃定义带参接⼝,Retrofit⾃定义请求参数注解的实现思路前⾔⽬前我们的项⽬中仅使⽤到 GET 和 POST 两种请求⽅式,对于 GET 请求,请求的参数会拼接在 Url 中;对于 POST 请求来说,我们可以通过 Body 或表单来提交⼀些参数信息。Retrofit 中使⽤⽅式先来看看在 Retrofit 中对于这两种请求的声明⽅式:GET 请求@GET("transporter/info")Flowable getTransporterInfo(@Query("uid") long id);我们使⽤ @Query 注解来声明查询参数,每⼀个参数都需要⽤ @Query 注解标记POST 请求@POST("transporter/update")Flowable changBind(@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")Call updateUser(@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) {it = retrofit; = method;Annotations = otations();terTypes = ericParameterTypes();terAnnotationsArray = ameterAnnotations();}我们关注三个属性:methodAnnotations ⽅法上的注解,Annotation[] 类型parameterTypes 参数类型,Type[] 类型parameterAnnotationsArray 参数注解,Annotation[][] 类型在构造函数中,我们主要对这 5 个属性赋值。Builder 构造者的 build ⽅法接着我们看看在通过 build ⽅法创建⼀个 ServiceMethod 对象的过程中发⽣了什么://省略了部分代码...public ServiceMethod build() {//1. 解析⽅法上的注解for (Annotation annotation : methodAnnotations) {parseMethodAnnotation(annotation);}int parameterCount = ;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);}解析⽅法上的注解 parseMethodAnnotationif (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) {thod = httpMethod;y = 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 = ();boolean encoded = d();gotField = true;Converter, String> converter = Converter(type, annotations);return new <>(name, converter, encoded);}获取注解的值,也就是参数名根据参数类型选取合适的 Converter返回⼀个 Field 对象,也就是 @Field 注解的处理器//省略部分代码static final class Field extends ParameterHandler {private final String name;private final Converter valueConverter;private final boolean encoded;//构造函数...@Overridevoid apply(RequestBuilder builder, @Nullable T value) throws IOException {String fieldValue = t(value);mField(name, fieldValue, encoded);}}通过 apply ⽅法将 @Filed 标记的参数名,参数值添加到了 FromBody 中对于 @Body 注解的处理else if (annotation instanceof Body) {Converter, RequestBody> converter;try {converter = tBodyConverter(type, annotations, methodAnnotations);} catch (RuntimeException e) {// Wide exception range because factories are user parameterError(e, p, "Unable to create @Body converter for%s", type);}gotBody = true;return new <>(converter);}选取合适的 ConvertergotBody 标记为 true返回⼀个 Body 对象,也就是 @Body 注解的处理器atic final class Body extends ParameterHandler {private final Converter converter;Body(Converter converter) {ter = converter;}@Overridevoid apply(RequestBuilder builder, @Nullable T value) {RequestBody body;try {body = t(value);} catch (IOException e) {throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);}y(body);}}通过 Converter 将 @Body 声明的对象转化为 RequestBody,然后设置赋值给 body 对象apply ⽅法什么时候被调⽤我们来看看 OkHttpCall 的同步请求 execute ⽅法://省略部分代码...@Overridepublic Response execute() throws IOException { 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 onFailure = e;throw e;}}return parseResponse(e());}在⽅法的内部,我们通过 createRawCall ⽅法来创建⼀个 call 对象,createRawCall ⽅法内部⼜调⽤了est(args);⽅法来创建⼀个 Request 对象:/*** 根据⽅法参数创建⼀个 HTTP 请求*/Request toRequest(@ args) throws IOException {RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody,isFormEncoded, isMultipart);ParameterHandler[] handlers = (ParameterHandler[]) parameterHandlers;int argumentCount = args != null ? : 0;if (argumentCount != ) {throw new IllegalArgumentException("Argument count (" + argumentCount+ ") doesn't match expected count (" + + ")");}for (int p = 0; p < argumentCount; p++) {handlers[p].apply(requestBuilder, args[p]);}return ();}可以看到在 for 循环中执⾏了每个参数对应的参数处理器的 apply ⽅法,给 RequestBuilder 中相应的属性赋值,最后通过 build ⽅法来构造⼀个 Request 对象,在 build ⽅法中还有⾄关重要的⼀步:就是确认我们最终的 Body 对象的来源,是来⾃于 @Body 注解声明的对象还是来⾃于其他RequestBody body = ;if (body == null) {// Try to pull from one of the (formBuilder != null) {body = ();} else if (multipartBuilder != null) {body = ();} else if (hasBody) {// Body is absent, make an empty = (null, new byte[0]);}}⾃定义 POST 请求的参数注解 @BodyQuery根据上述流程,想要⾃定义⼀个参数注解的话,涉及到以下改动点:新增类 @BodyQuery 参数注解新增类 BodyQuery ⽤来处理 @BodyQuery 声明的参数ServiceMethod 中的 parseParameterAnnotation ⽅法新增对 @BodyQuery 的处理分⽀RequestBuilder 类,新增 boolean 值 hasBodyQuery,表⽰是否使⽤了 @BodyQuery 注解,以及⼀个 Map 对象 hasBodyQuery,⽤来存储 @BodyQuery 标记的参数@BodyQuery 注解public @interface BodyQuery {/*** The query parameter name.*/String value();/*** Specifies whether the parameter {@linkplain #value() name} and value are already URL encoded.*/boolean encoded() default false;}没有什么特殊的,copy 的 @Query 注解的代码BodyQuery 注解处理器static final class BodyQuery extends ParameterHandler {private final String name;private final Converter valueConverter;BodyQuery(String name, Converter valueConverter) { = checkNotNull(name, "name == null");onverter = valueConverter;}@Overridevoid apply(RequestBuilder builder, @Nullable T value) throws IOException {String fieldValue = t(value);yQueryParams(name, fieldValue);}}在 apply ⽅法中我们做了两件事模仿 Field 的处理,获取到 @BodyQuery 标记的参数值将键值对添加到⼀个 Map 中// 在 RequestBuilder 中新增的⽅法void addBodyQueryParams(String name, String value) {(name, value);}针对 @BodyQuery 新增的分⽀处理else if (annotation instanceof BodyQuery) {BodyQuery field = (BodyQuery) annotation;String name = ();hasBodyQuery = true;Converter, String> converter = Converter(type, annotations);return new ery<>(name, converter);}我省略对于参数化类型的判断,可以看到这⾥的处理和对于 @Field 的分⽀处理基本⼀致,只不过是返回的 ParameterHandler 对象类型不同⽽已RequestBuilder之前我们说过在 RequestBuilder#build()⽅法中最重要的⼀点是确定 body 的值是来⾃于 @Body 还是表单还是其他对象,这⾥需要新增⼀种来源,也就是我们的 @BodyQuery 注解声明的参数值:RequestBody body = ;if (body == null) {// Try to pull from one of the (formBuilder != null) {body = ();} else if (multipartBuilder != null) {body = ();} else if (hasBodyQuery) {body = (("application/json; charset=UTF-8"),Bytes(eryMaps));} else if (hasBody) {// Body is absent, make an empty = (null, new byte[0]);}}在 hasBodyQuery 的分⽀,我们会将 bodyQueryMaps 转换为 JSON 字符串然后构造⼀个 RequestBody 对象赋值给 body。最后通过⼀个例⼦来看⼀下 @BodyQuery 注解的使⽤:@Testpublic void simpleBodyQuery(){class Example{@POST("/foo")Call method(@BodyQuery("A") String foo,@BodyQuery("B") String ping){return null;}}Request request = buildRequest(,"hello","world");assertBody((), "{"A":"hello","B":"world"}");}由于 Retrofit 中并没有提供这些类的修改和扩展的权限,因此这⾥仅仅是⼀个思路的扩展,我也仅仅是顺着 Retrofit 中对于ParameterHandler 的处理,扩展了⼀套新的注解类型⽽已。总结以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,如果有疑问⼤家可以留⾔交流,谢谢⼤家对脚本之家的⽀持。
发布者:admin,转转请注明出处:http://www.yc00.com/news/1690216337a316152.html
评论列表(0条)