最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Retrofit自定義請求參數(shù)注解的實現(xiàn)思路

 更新時間:2018年12月09日 15:31:23   作者:realxz  
這篇文章主要給大家介紹了Retrofit自定義請求參數(shù)注解的實現(xiàn)思路,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

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

Retrofit 中使用方式

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

GET 請求

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

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

POST 請求

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

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

Post 請求參數(shù)的聲明能否更直觀

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

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

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

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

@Field 注解

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

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);

使用了 @Field 注解之后,我們將以表單的形式提交數(shù)據(jù)(first_name = XXX & last_name = yyy)。

基于約定帶來的問題

看上去 @Field 注解可以滿足我們的需求了,但遺憾的是之前我們和 API 約定了 POST 請求數(shù)據(jù)傳輸?shù)母袷綖?JSON 格式,顯然我們沒有辦法使用該注解了

Retrofit 參數(shù)注解的處理流程

這個時候我想是不是可以模仿 @Field 注解,自己實現(xiàn)一個注解最后使得參數(shù)以 JSON 的格式傳遞給 API 就好了,在此之前我們先來看看 Retrofit 中對于請求的參數(shù)是如何處理的:

ServiceMethod 中 Builder 的構造函數(shù)

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 參數(shù)類型,Type[] 類型
  • parameterAnnotationsArray 參數(shù)注解,Annotation[][] 類型

在構造函數(shù)中,我們主要對這 5 個屬性賦值。

Builder 構造者的 build 方法

接著我們看看在通過 build 方法創(chuàng)建一個 ServiceMethod 對象的過程中發(fā)生了什么:

//省略了部分代碼...

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. 通過循環(huán)為每一個參數(shù)創(chuàng)建一個參數(shù)處理器
 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);
} 

我省略了大部分的代碼,整段的代碼其實就是來判斷方法注解的類型,然后繼續(xù)解析方法路徑,我們僅關注 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 體)等信息

創(chuàng)建參數(shù)處理器

在循環(huán)體中為每一個參數(shù)都創(chuàng)建一個 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 方法來返回一個參數(shù)處理器:

對于 @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);

}
  • 獲取注解的值,也就是參數(shù)名
  • 根據(jù)參數(shù)類型選取合適的 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;

 //構造函數(shù)...

 @Override
 void apply(RequestBuilder builder, @Nullable T value) throws IOException {
 String fieldValue = valueConverter.convert(value);
 builder.addFormField(name, fieldValue, encoded);
 }
}

通過 apply 方法將 @Filed 標記的參數(shù)名,參數(shù)值添加到了 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 方法:

//省略部分代碼...
@Override
public 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 方法來創(chuàng)建一個 call 對象,createRawCall 方法內部又調用了 serviceMethod.toRequest(args);方法來創(chuàng)建一個 Request 對象:

/**
 * 根據(jù)方法參數(shù)創(chuàng)建一個 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 循環(huán)中執(zhí)行了每個參數(shù)對應的參數(shù)處理器的 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 請求的參數(shù)注解 @BodyQuery

根據(jù)上述流程,想要自定義一個參數(shù)注解的話,涉及到以下改動點:

  • 新增類 @BodyQuery 參數(shù)注解
  • 新增類 BodyQuery 用來處理 @BodyQuery 聲明的參數(shù)
  • ServiceMethod 中的 parseParameterAnnotation 方法新增對 @BodyQuery 的處理分支
  • RequestBuilder 類,新增 boolean 值 hasBodyQuery,表示是否使用了 @BodyQuery 注解,以及一個 Map 對象 hasBodyQuery,用來存儲 @BodyQuery 標記的參數(shù)

@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 標記的參數(shù)值
  • 將鍵值對添加到一個 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);
}

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

RequestBuilder

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

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 注解的使用:

@Test
public 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 的處理,擴展了一套新的注解類型而已。

總結

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

相關文章

  • Android?TabLayout?自定義樣式及使用詳解

    Android?TabLayout?自定義樣式及使用詳解

    這篇文章主要為大家介紹了Android?TabLayout?自定義樣式及使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • MacBook M1 Flutter環(huán)境搭建的實現(xiàn)步驟

    MacBook M1 Flutter環(huán)境搭建的實現(xiàn)步驟

    本文主要介紹了MacBook M1 Flutter環(huán)境搭建,F(xiàn)lutter官方和各項配套的軟件環(huán)境也還沒有成熟,導致搭建環(huán)境時碰到了不少坑,本文就詳細的介紹一下
    2021-08-08
  • android如何獲取聯(lián)系人所有信息

    android如何獲取聯(lián)系人所有信息

    這篇文章主要為大家詳細介紹了android如何獲取聯(lián)系人所有信息,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Android變形(Transform)之Matrix用法

    Android變形(Transform)之Matrix用法

    Android的2D變形(包括縮放,扭曲,平移,旋轉等)可以通過Matrix來實現(xiàn),本文研究了一下;接下來就將我這倆天研究的東西和大家分享下,先來看看Matrix的用法感興趣的你可不要錯過了哈
    2013-02-02
  • Android開發(fā)中include控件用法分析

    Android開發(fā)中include控件用法分析

    這篇文章主要介紹了Android開發(fā)中include控件用法,結合實例形式分析了Android界面布局中include控件的使用技巧,需要的朋友可以參考下
    2016-10-10
  • Android ANR原理分析

    Android ANR原理分析

    ANR即Application Not Responding,顧名思義就是應用程序無響應。在Android中,一般情況下,四大組件均是工作在主線程中的,Android會隨時監(jiān)控應用程序的響應情況,如果因為一些耗時操作,那么系統(tǒng)就會顯示ANR對話框提示用戶對應的應用處于無響應狀態(tài)
    2021-06-06
  • Android中外接鍵盤的檢測的實現(xiàn)

    Android中外接鍵盤的檢測的實現(xiàn)

    這篇文章主要介紹了Android中外接鍵盤的檢測的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • Android Studio一直處于Building的兩種解決方法

    Android Studio一直處于Building的兩種解決方法

    很多朋友都遇到過打開別人的項目一直處于Building‘XXX’Gradle project info的情況。下面小編給大家?guī)砹薃ndroid Studio一直處于Building的解決方法,感興趣的朋友一起看看吧
    2018-08-08
  • 詳解Android輕量型數(shù)據(jù)庫SQLite

    詳解Android輕量型數(shù)據(jù)庫SQLite

    這篇文章主要為大家詳細介紹了Android輕量型數(shù)據(jù)庫SQLite,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • zxing二維碼位矩陣轉換成Bitmap位圖的實戰(zhàn)教程

    zxing二維碼位矩陣轉換成Bitmap位圖的實戰(zhàn)教程

    二維碼的應用已經(jīng)可以說是非常廣泛了,下面這篇文章主要給大家介紹了關于zxing二維碼位矩陣轉換成Bitmap位圖的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-09-09

最新評論

威海市| 丰镇市| 马龙县| 巫溪县| 榆树市| 漠河县| 明溪县| 江永县| 大方县| 绥宁县| 恩施市| 江永县| 乳源| 山阳县| 祁连县| 天祝| 神木县| 元氏县| 广德县| 泸定县| 林西县| 饶阳县| 汶川县| 西畴县| 陈巴尔虎旗| 张家口市| 平顶山市| 辽阳县| 龙江县| 永善县| 金平| 临海市| 正安县| 哈尔滨市| 沙湾县| 富源县| 资溪县| 内黄县| 建始县| 阆中市| 泉州市|