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