說一下一年以來使用Retrofit的心得和使用姿勢(shì).
- Retrofit 是什么:
A type-safe HTTP client for Android and Java

關(guān)于什么是REST ful架構(gòu), 這里不詳細(xì)說明,其實(shí)我也不會(huì)詳細(xì)說明,簡(jiǎn)單來說就是客戶端通過四個(gè)HTTP 動(dòng)詞獲取資源的過程
稍微說明一下HTTP報(bào)文的請(qǐng)求組成: 不論是請(qǐng)求報(bào)文還是響應(yīng)報(bào)文
header和body無疑是最重要的兩個(gè)部分.請(qǐng)求報(bào)文的header中會(huì)攜帶 Cookie,Cookie就是服務(wù)端來驗(yàn)證和區(qū)別客戶端的手段,這個(gè)客戶端是否是有效登陸的/是否是 成功注冊(cè)過得等等. 響應(yīng)報(bào)文中也會(huì)攜帶一些Cookie , 一些瀏覽器會(huì)使用一些方法儲(chǔ)存和保護(hù)和使用這些Cookie . 在Retrofit 和 Okhttp中表現(xiàn)為CookieJar .什么是Okhttp: Okhttp 也是Retrofit的 開發(fā)公司Square的作品.Retrofit2封裝了Okhttp的 一些東西,使得網(wǎng)絡(luò)請(qǐng)求更加的方便快捷.Retrofit 和 Okhttp這兩者是密不可分的.
進(jìn)入正題:
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
基本配置.
發(fā)送一個(gè)GET請(qǐng)求總是很簡(jiǎn)單的,我使用官網(wǎng)的POST請(qǐng)求為例說明一下.(我稍微修改了一下)
這個(gè)邏輯是這樣子的,如果向服務(wù)端發(fā)送一個(gè)Task的相關(guān)信息,服務(wù)端就會(huì)返回給你這個(gè)Task的一些相關(guān)的信息.
服務(wù)端返回的是JSON數(shù)據(jù)格式
第一步:
首先寫一個(gè)接口 里面有這樣的一個(gè)函數(shù):
public interface TaskService {
@POST("/tasks")
Call<Task> createTask(@Body Task task);
}
根據(jù)JSON格式 手寫一個(gè)這樣的Java 類 叫做Task
public class Task {
private long id;
private String text;
public Task(long id, String text) {
this.id = id;
this.text = text;
}
}
因?yàn)槲椰F(xiàn)在要進(jìn)行一個(gè)POST請(qǐng)求,so:
Task task = new Task(1,"this is a simple task");
//使用Retrofit 的實(shí)例創(chuàng)建一個(gè)TaskService 的實(shí)例
TaskService taskService = retroft.createTask(TaskService.class);
Call<Task> call = taskService.createTask(task);
接著進(jìn)行請(qǐng)求并且獲取請(qǐng)求的結(jié)果:
//進(jìn)行一個(gè)異步請(qǐng)求
call.enqueue(.....);
//或者進(jìn)行一個(gè)同步請(qǐng)求
taskService.createTask(task).execute();
好的,以上就是這樣:
但是
握草這些是什么鬼?看完了這一些代碼片段之后一定有這些疑問:
1) @POST? @Body? 而且 @POST()括號(hào)里面的是什么?
2) JSON就JSON數(shù)據(jù)好了 直接寫了一個(gè)類是什么意思? 我怎么取出數(shù)據(jù)呢?
3) 同步和異步又是什么鬼? 我是否需要 在主線程開一個(gè)線程進(jìn)行網(wǎng)絡(luò)請(qǐng)求?
4) 如果我需要登錄header,我應(yīng)該放在哪里呢?
5) 這段代碼沒有給出創(chuàng)建一個(gè)Retrofit實(shí)例的過程,Retrofit應(yīng)該如何創(chuàng)建呢?
6) 為什么需要在一個(gè)接口中寫這個(gè)函數(shù)?
來一步步解釋一下(基本操作)
- 為什么需要在接口中寫一個(gè)函數(shù):
如果自己寫一個(gè)函數(shù)來進(jìn)行網(wǎng)絡(luò)請(qǐng)求不就增加了難度,所以借助已經(jīng)創(chuàng)建好了的Retrofit的實(shí)例來給出接口的實(shí)例來進(jìn)行網(wǎng)絡(luò)請(qǐng)求是最簡(jiǎn)潔的. - 如何創(chuàng)建一個(gè)Retrofit實(shí)例:
//interceptor 是 打印網(wǎng)絡(luò)請(qǐng)求的log 并且設(shè)置log的層級(jí) Level.BODY的層級(jí)是比較全面的
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(25, TimeUnit.SECONDS)
.connectTimeout(25, TimeUnit.SECONDS)
.writeTimeout(25,TimeUnit.SECONDS)
.addInterceptor(interceptor)
// .cookieJar(cookieJar)
.build();
//Retrofit實(shí)例的創(chuàng)建過程使用了建造者模式:
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl("https://something.best.com/api/")
.build();
之前說過Retrofit的基礎(chǔ)是Okhttp所以,構(gòu)建Retrofit的時(shí)候需要傳入一個(gè)OkhttpClient的實(shí)例 在這個(gè)client的實(shí)例中可以設(shè)置interceptor 和 CookieJar
在創(chuàng)建Retrofit的實(shí)例的過程中baseUrl()就是需要請(qǐng)求的url的一部分, Retrofit在請(qǐng)求的過程中會(huì)將@POST括號(hào)中的部分 拼接在baseUrl的后面形成一個(gè)完整的requestUrl.
這里@POST 是Retrofit的特色之一,使用注解來"標(biāo)注"請(qǐng)求的類型: POST GET等 而@Body表示后面的數(shù)據(jù)不是攜帶完整數(shù)據(jù)的一個(gè)類.
接口中的這個(gè)函數(shù)的返回值是一個(gè)被包括在Call<T>(CallAdapter)這個(gè)泛型T所對(duì)應(yīng)的實(shí)際類型,這里就是Task 注意:這里請(qǐng)求時(shí)上傳的值和返回的類型都是一樣的都是Task,在其他情況可能不一樣.
這里需要注意的是callAdapter可能不止一種,還有對(duì)RxJava的支持Observable<T>也是經(jīng)常見到的.
但是返回的數(shù)據(jù)明明是JSON數(shù)據(jù),為什么變成了一個(gè)類 ,這里就需要多謝之前配置的依賴converter-gson提供的一個(gè)類GsonConverter,這個(gè)類將JSON數(shù)據(jù)轉(zhuǎn)化成了一個(gè)類
{
"city":"北京",
"size":16800,
"population":1600
}
好比這一段json數(shù)據(jù) 如果你使用GsonFormat這個(gè)Android Studio 插件的話 就會(huì)生成
String city;
int size;
int population;
這三個(gè)量分別對(duì)應(yīng)三個(gè)數(shù)據(jù).
之后可以使用一些getter() setter()來取出數(shù)據(jù).
在進(jìn)行數(shù)據(jù)請(qǐng)求的時(shí)候也新建一個(gè)這樣的類的實(shí)例的話,在構(gòu)造函數(shù)通填入數(shù)據(jù)就可以了(有一種 key-value的感覺對(duì)吧!)
但是如果需要的不只是響應(yīng)的body中的內(nèi)容 還需要響應(yīng)報(bào)文中的其他東西,可以使用Call<ResponseBody>這樣不止返回Body 還有其他內(nèi)容,但是這樣的話就沒有通過converter轉(zhuǎn)化為一個(gè)類了; 同理,如果不想管返回類型,可以使用Call<Void>
此外Retrofit還提供了很多其他的注解@Query/ @QueryMap注解可以用于查詢參數(shù)的場(chǎng)景
https://www.google.com/search?q=something&aqs=somethingelse&sourceid=chrome&ie=UTF-8
代碼可以寫成:
@Query("q") String arg1,@Query("ags") String args2, @Query("sourceid") String args3, @Query("id") String args4;
只需要key和查詢參數(shù)的key一一對(duì)應(yīng)就好了
還有@Path注解和其他注解 這里就不一一贅述了
如果使用的是enqueue()函數(shù)的話 進(jìn)行的是一個(gè)異步的操作,可以再回調(diào)函數(shù)中操作UI線程
Call<ResponseBody> call = mCcnuService.performLibLogin2();
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
});
這個(gè)是一個(gè)同步的操作 注意不要阻塞了主線程
retrofit2.Response<ResponseBody> responseBody2 = mCcnuService.performSystemLogin().execute();
最后說一下在使用過程中經(jīng)常出現(xiàn)的問題和一些常見的使用場(chǎng)景的解決策略:
1) 需要訪問url A 獲取數(shù)據(jù)之后 在訪問 url B 使用url A 里面拿到的數(shù)據(jù)訪問 url C 獲取最后想要的數(shù)據(jù):
推薦使用Retrofit的同步操作,這樣的話比較符合邏輯上面的順次進(jìn)行的概念 如果使用異步操作的話可能會(huì)造成回調(diào)地獄

- 為什么命名onSuccess執(zhí)行了 但是沒有取出想要的數(shù)據(jù)?
這個(gè)情況多半是在Call<T>中 T 類型的變量名 和JSON數(shù)據(jù)中的不一樣. 這很好理解,key-value 如果key錯(cuò)誤了就不能取出正確的數(shù)據(jù)了.
在使用@Header注解的時(shí)候也要注意這個(gè)問題 token/Cookie的key也要和后端提供的一樣.使用postman調(diào)試過,拿到JSON數(shù)據(jù)在使用GsonFormat生成會(huì)大大避免這個(gè)問題