<源碼系列> Retrofit之一:用法介紹

官網https://square.github.io/retrofit/

簡介:

  • Retrofit,一個RESTful( 無狀態 )的HTTP網絡請求框架(基于OkHttp)( 封裝 )
  • 注解配置網絡請求參數,解耦徹底,擴展性強


    交互示意圖

示例

  • 集成
//build.gradle 引入
implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'

//AndroidManifest.xml 中添加權限
<uses-permission android:name="android.permission.INTERNET"/>
  • 使用
//① 創建Java接口
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

//② Retrofit實現創建的Java接口
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create()) // 設置數據解析器
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平臺
    .build();

GitHubService service = retrofit.create(GitHubService.class);

//③調用(同步或異步選其一)
Call<List<Repo>> repos = service.listRepos("octocat");

//同步請求
Response<ResponseBody> res = repos.execute();

//或異步請求
repos.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        t.printStackTrace();
    }
});
注意:
  • 1、Retrofit的Url組合規則
    Retrofit2baseUlr 必須以 /(斜線) 結束,不然會拋出一個 IllegalArgumentException
    所以如果看到別的教程沒有以 / 結束,那么多半是直接從 Retrofit 1.X 照搬過來的。

一、Retrofit注解

Retrofit注解

1、@HTTP

  • 有三個屬性:methodpathhasBody
    /**
     * method 表示請求的方法,區分大小寫
     * path表示路徑
     * hasBody表示是否有請求體
     */
    @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getBlog(@Path("id") int id);
  • 注: method 的值 retrofit 不會做處理,所以要自行保證其準確性,

2、@Path

path注解說明

3、@Header & @Headers

  • 添加請求頭 &添加不固定的請求頭
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()

說明:
以上的效果是一致的。

  • 區別在于使用場景和使用方式
      1. 使用場景:@Header用于添加不固定的請求頭,@Headers用于添加固定的請求頭
      1. 使用方式:@Header作用于方法的參數;@Headers作用于方法

4、@Body

  • @Body 的參數如果是一個Map ,則作用相當于@Field
    不過Map要經過 FormBody.Builder 類處理成為符合 Okhttp 格式的表單,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");

5、@Field & @FieldMap;@Part & @PartMap

  • @Field@FieldMap 配合使用,在發送 Post 請求時提交請求的表單字段
  • @Part@Field 的區別:功能相同,但攜帶的參數類型更加豐富,包括數據流,所以適用于 有文件上傳 的場景

6、@Query和@QueryMap

  • 用于 @GET 方法的查詢參數(Query 相當于 Url 中 ‘?’ 后面的 key-value)

二、Converter

  • 1、Retrofit支持多種數據解析方式

    解析器

    注:以上版本可能不是最新版本

  • 2、說明:

    • Convert.Factoy 的具體作用就是獲取一個 Convert
    • Converter 是對于 Call<T>T 的轉換
    • addConverterFactory 是有先后順序的,如果有多個 ConverterFactory 都支持同一種類型,那么就是只有第一個才會被使用,而 GsonConverterFactory 是不判斷是否支持的,所以這里交換了順序還會有一個異常拋出,原因是類型不匹配。
  • 3、使用:

Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      // 我們自定義的一定要放在Gson這類的Converter前面 
      .addConverterFactory(StringConverterFactory.create())
      .addConverterFactory(GsonConverterFactory.create())
      .build();
  • 4、Converter 源碼
public interface Converter<F, T> {
  // 實現從 F(rom) 到 T(o)的轉換
  T convert(F value) throws IOException;

  // 用于向Retrofit提供相應Converter的工廠
  abstract class Factory {
    // 這里創建從ResponseBody其它類型的Converter,如果不能處理返回null
    // 主要用于對響應體的處理
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
      return null;
    }

    // 在這里創建 從自定類型到ResponseBody 的Converter,不能處理就返回null,
    // 主要用于對Part、PartMap、Body注解的處理
    public Converter<?, RequestBody> requestBodyConverter(Type type,
    Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    // 這里用于對Field、FieldMap、Header、Path、Query、QueryMap注解的處理
    // Retrfofit對于上面的幾個注解默認使用的是調用toString方法
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
      return null;
    }
  }
}
  • 5、定義Gson的Converter
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;


public final class GsonConverterFactory extends Converter.Factory {
    /**
     * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static GsonConverterFactory create() {
        return create(new Gson());
    }

    /**
     * Create an instance using {@code gson} for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static GsonConverterFactory create(Gson gson) {
        return new GsonConverterFactory(gson);
    }

    private final Gson gson;

    private GsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    //對ResponseBody進行數據轉換的轉換器
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    //將未知數據轉換成RequestBody的轉換器
    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}

/*
//Converter.Factory中的方法
    //將未知數據轉換成String類型的數據轉換器
    public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
*/

三、CallAdapter

  • 1、說明:
    • CallAdapter 則可以對 Call 轉換,這樣的話 Call<T> 中的 Call 也是可以被替換的。
    • addCallAdapterFactoryaddConverterFactory 同理,也有先后順序。
  • 2、使用:
//引入RxJava2支持:
compile 'com.squareup.retrofit2:adapter-rxjava:2.6.1'

注:以上版本可能不是最新版本

//通過RxJavaCallAdapterFactory為Retrofit添加RxJava支持:
Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
      // 針對rxjava2.x
      .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
      .build();

//接口設計
public interface BlogService {
  @POST("/blog")
  Observable<Result<List<Blog>>> getBlogs();
}

//使用:
BlogService service = retrofit.create(BlogService.class);
service.getBlogs(1)
  .subscribeOn(Schedulers.io())
  .subscribe(new Subscriber<Result<List<Blog>>>() {
      @Override
      public void onCompleted() {
        System.out.println("onCompleted");
      }

      @Override
      public void onError(Throwable e) {
        System.err.println("onError");
      }

      @Override
      public void onNext(Result<List<Blog>> blogsResult) {
        System.out.println(blogsResult);
      }
  });
  • 3、CallAdapter源碼:
public interface CallAdapter<T> {

  // 直正數據的類型 如Call<T> 中的 T
  // 這個 T 會作為Converter.Factory.responseBodyConverter 的第一個參數
  // 可以參照上面的自定義Converter
  Type responseType();

  <R> T adapt(Call<R> call);

  // 用于向Retrofit提供CallAdapter的工廠類
  abstract class Factory {
    // 在這個方法中判斷是否是我們支持的類型,returnType 即Call<Requestbody>和`Observable<Requestbody>`
    // RxJavaCallAdapterFactory 就是判斷returnType是不是Observable<?> 類型
    // 不支持時返回null
    public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,
    Retrofit retrofit);

    // 用于獲取泛型的參數 如 Call<Requestbody> 中 Requestbody
    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    // 用于獲取泛型的原始類型 如 Call<Requestbody> 中的 Call
    // 上面的get方法需要使用該方法。
    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
}

四、相關鏈接:

Retrofit 2.0 使用教程
Retrofit2詳解
你真的會用Retrofit2嗎?Retrofit2完全教程
Retrofit2源碼解析
Retrofit之Converter簡單解析

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,367評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,001評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,213評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,535評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,317評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,868評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,963評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,090評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,599評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,549評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,712評論 1 367
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,233評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,353評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,607評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,321評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,686評論 2 370