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

官網(wǎng)https://square.github.io/retrofit/

簡介:

  • Retrofit俐东,一個(gè)RESTful( 無狀態(tài) )的HTTP網(wǎng)絡(luò)請求框架(基于OkHttp)( 封裝 )
  • 注解配置網(wǎng)絡(luò)請求參數(shù)柔昼,解耦徹底,擴(kuò)展性強(qiáng)


    交互示意圖

示例

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

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

//② Retrofit實(shí)現(xiàn)創(chuàng)建的Java接口
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create()) // 設(shè)置數(shù)據(jù)解析器
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平臺(tái)
    .build();

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

//③調(diào)用(同步或異步選其一)
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組合規(guī)則
    Retrofit2baseUlr 必須以 /(斜線) 結(jié)束,不然會(huì)拋出一個(gè) IllegalArgumentException仁锯。
    所以如果看到別的教程沒有以 / 結(jié)束锣杂,那么多半是直接從 Retrofit 1.X 照搬過來的。

一畅厢、Retrofit注解

Retrofit注解

1冯痢、@HTTP

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

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()

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

  • 區(qū)別在于使用場景和使用方式
      1. 使用場景:@Header用于添加不固定的請求頭历恐,@Headers用于添加固定的請求頭
      1. 使用方式:@Header作用于方法的參數(shù)寸癌;@Headers作用于方法

4、@Body

  • @Body 的參數(shù)如果是一個(gè)Map 弱贼,則作用相當(dāng)于@Field
    不過Map要經(jīng)過 FormBody.Builder 類處理成為符合 Okhttp 格式的表單蒸苇,如:
FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");

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

  • @Field@FieldMap 配合使用填渠,在發(fā)送 Post 請求時(shí)提交請求的表單字段
  • @Part@Field 的區(qū)別:功能相同,但攜帶的參數(shù)類型更加豐富鸟辅,包括數(shù)據(jù)流氛什,所以適用于 有文件上傳 的場景

6、@Query和@QueryMap

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

二匪凉、Converter

  • 1枪眉、Retrofit支持多種數(shù)據(jù)解析方式

    解析器

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

  • 2、說明:

    • Convert.Factoy 的具體作用就是獲取一個(gè) Convert
    • Converter 是對于 Call<T>T 的轉(zhuǎn)換
    • addConverterFactory 是有先后順序的再层,如果有多個(gè) ConverterFactory 都支持同一種類型贸铜,那么就是只有第一個(gè)才會(huì)被使用,而 GsonConverterFactory 是不判斷是否支持的聂受,所以這里交換了順序還會(huì)有一個(gè)異常拋出蒿秦,原因是類型不匹配。
  • 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> {
  // 實(shí)現(xiàn)從 F(rom) 到 T(o)的轉(zhuǎn)換
  T convert(F value) throws IOException;

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

    // 在這里創(chuàng)建 從自定類型到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對于上面的幾個(gè)注解默認(rèn)使用的是調(diào)用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進(jìn)行數(shù)據(jù)轉(zhuǎn)換的轉(zhuǎn)換器
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    //將未知數(shù)據(jù)轉(zhuǎn)換成RequestBody的轉(zhuǎn)換器
    @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中的方法
    //將未知數(shù)據(jù)轉(zhuǎn)換成String類型的數(shù)據(jù)轉(zhuǎn)換器
    public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
*/

三、CallAdapter

  • 1僚纷、說明:
    • CallAdapter 則可以對 Call 轉(zhuǎn)換矩距,這樣的話 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();

//接口設(shè)計(jì)
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> {

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

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

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

    // 用于獲取泛型的參數(shù) 如 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);
    }
  }
}

四趣兄、相關(guān)鏈接:

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绽左,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子艇潭,更是在濱河造成了極大的恐慌拼窥,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋凝,死亡現(xiàn)場離奇詭異鲁纠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鳍寂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門改含,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人迄汛,你說我怎么就攤上這事捍壤。” “怎么了鞍爱?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵鹃觉,是天一觀的道長。 經(jīng)常有香客問我睹逃,道長盗扇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任唯卖,我火速辦了婚禮粱玲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拜轨。我一直安慰自己,他們只是感情好允青,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布橄碾。 她就那樣靜靜地躺著卵沉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪法牲。 梳的紋絲不亂的頭發(fā)上史汗,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音拒垃,去河邊找鬼停撞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛悼瓮,可吹牛的內(nèi)容都是我干的戈毒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼横堡,長吁一口氣:“原來是場噩夢啊……” “哼埋市!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起命贴,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤道宅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后胸蛛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體污茵,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年葬项,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泞当。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玷室,死狀恐怖零蓉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情穷缤,我是刑警寧澤敌蜂,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站津肛,受9級(jí)特大地震影響章喉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜身坐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一秸脱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧部蛇,春花似錦摊唇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽有序。三九已至,卻和暖如春岛请,著一層夾襖步出監(jiān)牢的瞬間旭寿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工崇败, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盅称,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓后室,卻偏偏與公主長得像缩膝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子咧擂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355