亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Retrofit自定義請求參數注解的實現思路

2019-10-21 21:34:06
字體:
來源:轉載
供稿:網友

前言

目前我們的項目中僅使用到 GET 和 POST 兩種請求方式,對于 GET 請求,請求的參數會拼接在 Url 中;對于 POST 請求來說,我們可以通過 Body 或表單來提交一些參數信息。

Retrofit 中使用方式

先來看看在 Retrofit 中對于這兩種請求的聲明方式:

GET 請求

@GET("transporter/info")Flowable<Transporter> getTransporterInfo(@Query("uid") long id);

我們使用 @Query 注解來聲明查詢參數,每一個參數都需要用 @Query 注解標記

POST 請求

@POST("transporter/update")Flowable<ResponseBody> changBind(@Body Map<String,Object> params);

在 Post 請求中,我們通過 @Body 注解來標記需要傳遞給服務器的對象

Post 請求參數的聲明能否更直觀

以上兩種常規的請求方式很普通,沒有什么特別要說明的。

有次團隊討論一個問題,我們所有的請求都是聲明在不同的接口中的,如官方示例:

public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user);}

如果是 GET 請求還好,通過 @Query 注解我們可以直觀的看到請求的參數,但如果是 POST 請求的話,我們只能夠在上層調用的地方才能看到具體的參數,那么 POST 請求的參數聲明能否像 GET 請求一樣直觀呢?

@Field 注解

先看代碼,關于 @Field 注解的使用:

@FormUrlEncoded@POST("user/edit")Call<User> 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) { 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<?, String> converter = retrofit.stringConverter(type, annotations); return new ParameterHandler.Field<>(name, converter, encoded);}
  • 獲取注解的值,也就是參數名
  • 根據參數類型選取合適的 Converter
  • 返回一個 Field 對象,也就是 @Field 注解的處理器

ParameterHandler.Field

//省略部分代碼static final class Field<T> extends ParameterHandler<T> { private final String name; private final Converter<T, String> 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<?, RequestBody> 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 Body<T> extends ParameterHandler<T> { private final Converter<T, RequestBody> converter; Body(Converter<T, RequestBody> 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 方法:

//省略部分代碼...@Overridepublic Response<T> execute() 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<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0; if (argumentCount != handlers.length) { throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")"); } for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return requestBuilder.build();}

可以看到在 for 循環中執行了每個參數對應的參數處理器的 apply 方法,給 RequestBuilder 中相應的屬性賦值,最后通過 build 方法來構造一個 Request 對象,在 build 方法中還有至關重要的一步:就是確認我們最終的 Body 對象的來源,是來自于 @Body 注解聲明的對象還是來自于其他

RequestBody body = this.body;if (body == null) { // Try to pull from one of the builders. if (formBuilder != null) { body = formBuilder.build(); } else if (multipartBuilder != null) { body = multipartBuilder.build(); } else if (hasBody) { // Body is absent, make an empty body. body = RequestBody.create(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<T> extends ParameterHandler<T> { private final String name; private final Converter<T, String> valueConverter; BodyQuery(String name, Converter<T, String> valueConverter) { this.name = checkNotNull(name, "name == null"); this.valueConverter = valueConverter; } @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException { String fieldValue = valueConverter.convert(value); builder.addBodyQueryParams(name, fieldValue); }}

在 apply 方法中我們做了兩件事

  • 模仿 Field 的處理,獲取到 @BodyQuery 標記的參數值
  • 將鍵值對添加到一個 Map 中
// 在 RequestBuilder 中新增的方法void addBodyQueryParams(String name, String value) { bodyQueryMaps.put(name, value);}

針對 @BodyQuery 新增的分支處理

else if (annotation instanceof BodyQuery) { BodyQuery field = (BodyQuery) annotation; String name = field.value(); hasBodyQuery = true; Converter<?, String> converter = retrofit.stringConverter(type, annotations); return new ParameterHandler.BodyQuery<>(name, converter);}

我省略對于參數化類型的判斷,可以看到這里的處理和對于 @Field 的分支處理基本一致,只不過是返回的 ParameterHandler 對象類型不同而已

RequestBuilder

之前我們說過在 RequestBuilder#build() 方法中最重要的一點是確定 body 的值是來自于 @Body 還是表單還是其他對象,這里需要新增一種來源,也就是我們的 @BodyQuery 注解聲明的參數值:

RequestBody body = this.body;if (body == null) { // Try to pull from one of the builders. if (formBuilder != null) { body = formBuilder.build(); } else if (multipartBuilder != null) { body = multipartBuilder.build(); } else if (hasBodyQuery) { body = RequestBody.create(MediaType.parse("application/json; charset=UTF-8"), JSON.toJSONBytes(this.bodyQueryMaps)); } else if (hasBody) { // Body is absent, make an empty body. body = RequestBody.create(null, new byte[0]); }}

在 hasBodyQuery 的分支,我們會將 bodyQueryMaps 轉換為 JSON 字符串然后構造一個 RequestBody 對象賦值給 body。

最后

通過一個例子來看一下 @BodyQuery 注解的使用:

@Testpublic void simpleBodyQuery(){ class Example{ @POST("/foo") Call<ResponseBody> method(@BodyQuery("A") String foo,@BodyQuery("B") String ping){  return null; } } Request request = buildRequest(Example.class,"hello","world"); assertBody(request.body(), "{/"A/":/"hello/",/"B/":/"world/"}");}

由于 Retrofit 中并沒有提供這些類的修改和擴展的權限,因此這里僅僅是一個思路的擴展,我也僅僅是順著 Retrofit 中對于 ParameterHandler 的處理,擴展了一套新的注解類型而已。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美性色视频在线| 91精品久久久久久久久久入口| 国产精品亚洲美女av网站| 日韩精品久久久久久福利| 欧美精品一区二区三区国产精品| 日韩高清av一区二区三区| 2025国产精品视频| 国产视频欧美视频| 国产视频观看一区| 欧美性猛交xxxxx免费看| 国产在线视频不卡| 日韩欧美视频一区二区三区| 亚洲国产精品专区久久| 亚洲国产成人91精品| 亚洲国产天堂网精品网站| 日韩精品一区二区视频| 国产美女精品视频免费观看| 成人夜晚看av| 亚洲精品成人av| 91视频免费在线| 亚洲激情第一页| 国外成人在线播放| 欧美—级a级欧美特级ar全黄| 在线精品视频视频中文字幕| 欧美性xxxxhd| 国产欧美久久一区二区| 97人人爽人人喊人人模波多| 欧美精品videosex牲欧美| 亚洲国产精品中文| 国产精品一区二区三区毛片淫片| 成人中心免费视频| 日韩va亚洲va欧洲va国产| 日韩免费视频在线观看| 国产日韩欧美中文在线播放| 欧美另类xxx| 欧美亚洲国产日韩2020| 亚洲美女中文字幕| 欧美黄色三级网站| 97超碰国产精品女人人人爽| 午夜精品久久久久久久久久久久| 亚洲欧美在线一区二区| xvideos国产精品| 亚洲区在线播放| 日韩视频精品在线| 中文字幕欧美精品在线| 国内精品400部情侣激情| 92裸体在线视频网站| 爱福利视频一区| 色婷婷综合久久久久中文字幕1| 一本一道久久a久久精品逆3p| 日韩精品久久久久久久玫瑰园| 成人在线视频网站| 精品久久久免费| 欧美成人sm免费视频| 日韩成人在线电影网| 91久久国产综合久久91精品网站| 91精品国产乱码久久久久久蜜臀| 国产女人精品视频| 最近的2019中文字幕免费一页| 国产91网红主播在线观看| 96sao精品视频在线观看| 欧美成人国产va精品日本一级| 欧美整片在线观看| 久久精品国产欧美激情| 国产精品亚洲片夜色在线| 欧美成人激情图片网| 91精品国产沙发| 国产精品高潮呻吟久久av野狼| 亚洲黄页视频免费观看| 成人一区二区电影| 亚洲成**性毛茸茸| 一本色道久久综合狠狠躁篇的优点| 国产a级全部精品| 国产欧美久久一区二区| 国产精品黄视频| 亚洲国模精品一区| 色yeye香蕉凹凸一区二区av| 91精品国产乱码久久久久久蜜臀| 欧美丝袜一区二区| 亚洲第一区在线| 热草久综合在线| 亚洲免费一级电影| 91在线国产电影| 日韩在线视频中文字幕| 欧美激情精品久久久久久蜜臀| 日韩中文字幕在线免费观看| 国产精品爱啪在线线免费观看| 韩国19禁主播vip福利视频| 欧美在线xxx| 国产亚洲欧美日韩精品| 日韩精品亚洲元码| 国产精品亚洲精品| 欧美人在线观看| 欧美精品videosex性欧美| 久久亚洲精品一区二区| 中文综合在线观看| 国产一区二区三区视频在线观看| 日韩激情视频在线| 亚洲aa中文字幕| 久久天天躁狠狠躁夜夜躁| 国产精品情侣自拍| 国产精品爽黄69| 久久久91精品| 国产精品igao视频| 久久久久久久久久久久久久久久久久av| 亚洲精品v欧美精品v日韩精品| 国产欧美韩国高清| 欧洲中文字幕国产精品| 欧美丝袜第一区| 亚洲区一区二区| 欧美成人激情图片网| 日韩中文字幕在线精品| 久久乐国产精品| 久久人人爽人人爽人人片av高请| 国产精品久久网| 日韩中文在线中文网三级| 亚洲视频电影图片偷拍一区| 另类少妇人与禽zozz0性伦| 成人激情在线播放| 91视频国产一区| 国内精品久久久久影院 日本资源| 欧美大奶子在线| 亚洲美女动态图120秒| 欧美裸体视频网站| 欧美日韩一区二区精品| 国产香蕉一区二区三区在线视频| 美女久久久久久久久久久| 亚洲一区二区福利| 久久精品电影一区二区| 日韩免费不卡av| 欧美xxxwww| 国模精品视频一区二区三区| 亚洲国产精品久久久久秋霞不卡| 91精品国产91久久久久久最新| 国产精品尤物福利片在线观看| 亚洲激情视频网站| 亚洲综合在线做性| 免费97视频在线精品国自产拍| 成人免费看片视频| 亚洲aaa激情| 精品久久久久久中文字幕一区奶水| 亚洲欧美中文另类| 国产成人jvid在线播放| 久久久久久久激情视频| 国产69久久精品成人| 日韩国产欧美精品在线| 成人春色激情网| 欧美极品少妇全裸体| 91精品中文在线| 久久精品国产免费观看| 精品国产一区二区三区在线观看| 国内精品久久久久影院 日本资源| 亚洲最大在线视频| 日韩人体视频一二区| 成人有码视频在线播放| 亚洲美女精品成人在线视频| 国产一区二区丝袜高跟鞋图片| 亚洲精品美女在线观看播放| 日韩久久精品成人| 国产精品av电影| 国产伦精品免费视频| 色偷偷av亚洲男人的天堂| 一区三区二区视频| 精品调教chinesegay|