1.Retrofit基本用法
1.1 使用前的準備工作
首先配置build.gradle,如下
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
}
最後一行是爲了增加支持返回值爲Gson類型數據所需要的依賴包。如果想增加其他類型的數據支持,可以添加其他依賴包。
當然,不要完了在manifest中加入訪問網絡的權限。
1.2 Retrofit的註解分類
Retrofit與其他請求框架不同的是,它使用了註解。Retrofit的註解分爲三大類,分別是HTTP請求方法註解、標記類註解和參數註解。其中,HTTP請求方法註解有8種,它們是GET、POST、PUT、DELETE、HEAD、PATCH、OPTION和HTTP。其前7中分別對應HTTP的請求方法:HTTP可以替換以上7種,也可以擴展請求方法。標記類註解有3種,它們是FormUrlEncoded、Multipart、Streaming。FromUrlEncoded和Multipart後面會講到;Streaming是代表響應數據以流的形式返回。如果不使用它,會默認把全部數據加載到內存,所以下載大文件時需要加上這個註解。參數註解有Header、Headers、Body、Path、Field、FieldMap、Part、PartMap、Query和QueryMap等,下面會介紹幾種參數類註解的用法。
3、GET請求訪問網絡
首先實現用GET請求方式類訪問網絡,這裏我們訪問淘寶IP庫。實體類的編寫就不再贅述了。首先編寫請求網絡接口,如下:
public interface IpService {
@GET("getIpInfo.php?ip=59.108.54.37")
Call<IpModel> getIpMsg();
}
Retrofit提供的請求方式註解有@GET和@POST等,分別代表GET請求和POST請求,我們在這裏用的是GET請求,訪問的地址是"getIpInfo.php?ip=59.108.54.37"。另外定義了getIpMsg方法,這個方法返回Call<IpModel>
類型的參數。接下來創建Retrofit,並創建接口文件,代碼如下所示:
String url = "http://ip.taobao.com/service/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url) .addConverterFactory(GsonConverterFactory.create())
.build();
IpServiceForQuery ipService = retrofit.create(IpServiceForQuery.class);
Call<IpModel> call = ipService.getIpMsg();
Retrofit是通過建造者模式構建出來的。請求URL是拼接而成的,它是由baseUrl傳入URL加上請求網絡接口的@GET(“getIpInfo.php?ip=59.108.54.37”)中的URL拼接而成的。接下來用Retrofit動態代理獲取到定義的接口,並調用該接口定義的getIpMsg方法得到Call對象。接下來用Call請求網絡並處理回調,代碼如下:
call.enqueue(new Callback<IpModel>() {
@Override
public void onResponse(Call<IpModel> call, Response<IpModel> response) {
String country = response.body().getData().getCountry();
Log.i("wangshu", "country" + country);
Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<IpModel> call, Throwable t) {
}
});
這裏是異步請求網絡,回調的Callback是運行在UI線程中的。得到返回的Response後將返回數據的country字段用Toast顯示出來。如果想同步請求網絡,可以使用call.execute();如果想中斷網絡請求,可以使用call.cancle();
動態配置URL地址:@Path
Retrofit提供了很多請求參數註解,這使得請求網絡時更加便捷。其中,@Path用來動態配置URL地址。請求網絡接口代碼如下所示:
public interface IpServiceForPath {
@GET("{path}/getIpInfo.php?ip=59.108.54.37")
Call<IpModel> getIpMsg(@Path("path") String path);
}
在GET註解中包含了{path},它對應着@path中的"path",而用來替換{path}的正是需要傳入的"String path"的值。請求網絡的代碼如下:
String url = "http://ip.taobao.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
IpServiceForPath ipService = retrofit.create(IpServiceForPath.class);
Call<IpModel> call = ipService.getIpMsg(path);
call.enqueue(new Callback<IpModel>() {
@Override
public void onResponse(Call<IpModel> call, Response<IpModel> response) {
String country = response.body().getData().getCountry();
Log.i("wangshu", "country" + country);
Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<IpModel> call, Throwable t) {
}
});
動態指定查詢條件:@Query
之前的例子就是爲了查詢ip的地址,每次查詢更換不同的ip就可以了,可以用@Qurey來動態地指定ip的值。請求網絡接口的代碼如下:
public interface IpServiceForQuery{
@GET("getIpInfo.php")
Call<IpModel> getIpMsg(@Query("ip")String ip);
}
請求網絡的時候,只需要傳入想要查詢的ip值就可以了。
動態指定查詢條件組:@QureyMap
在網絡請求中一般爲了更精確的查找到我們所需要的數據,需要傳入很多查詢參數。如果用@Query會比較麻煩,這時我們可以採用@QueryMap,將所有的參數集成在一個Map中統一傳遞,如下所示:
public interface IpServiceForQueryMap {
@GET("getIpInfo.php")
Call<IpModel> getIpMsg(@QueryMap Map<String, String> options);
}
如果需要詳細的 日誌信息,
網絡請求日誌
調試網絡請求的時候經常需要關注一下請求參數和返回值,以便判斷和定位問題出在哪裏,Retrofit官方提供了一個很方便查看日誌的Interceptor,你可以控制你需要的打印信息類型,使用方法也很簡單。
首先需要在build.gradle文件中引入logging-interceptor
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
添加到OkHttpClient創建處即可,完整的示例代碼如下:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(loggingInterceptor);
String url = "http://ip.taobao.com/service/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(builder.build())
.build();
HttpLoggingInterceptor提供了4中控制打印信息類型的等級,分別是NONE,BASIC,HEADERS,BODY,接下來分別來說一下相應的打印信息類型。
- NONE
沒有任何日誌信息 - Basic
打印請求類型,URL,請求體大小,返回值狀態以及返回值的大小
D/HttpLoggingInterceptorLogger: <-- HTTP/1.1 200 OK (543ms, -1-byte body)
- Headers
打印返回請求和返回值的頭部信息,請求類型,URL以及返回值狀態碼
<-- 200 OK https://api.douban.com/v2/book/search?q=小王子&start=0&count=3&token=tokenValue (3787ms)
D/OkHttp: Date: Sat, 06 Aug 2016 14:26:03 GMT
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Connection: keep-alive
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: Pragma: no-cache
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Set-Cookie: bid=D6UtQR5N9I4; Expires=Sun, 06-Aug-17 14:26:03 GMT; Domain=.douban.com; Path=/
D/OkHttp: X-DOUBAN-NEWBID: D6UtQR5N9I4
D/OkHttp: X-DAE-Node: dis17
D/OkHttp: X-DAE-App: book
D/OkHttp: Server: dae
D/OkHttp: <-- END HTTP
- Body
打印請求和返回值的頭部和body信息
– 200 OK https://api.douban.com/v2/book/search?q=小王子&tag=&start=0&count=3&token=tokenValue (3583ms)
D/OkHttp: Connection: keep-alive
D/OkHttp: Date: Sat, 06 Aug 2016 14:29:11 GMT
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Pragma: no-cache
D/OkHttp: Connection: keep-alive
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Keep-Alive: timeout=30
D/OkHttp: Set-Cookie: bid=ESnahto1_Os; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
D/OkHttp: Vary: Accept-Encoding
D/OkHttp: X-DOUBAN-NEWBID: ESnahto1_Os
D/OkHttp: Expires: Sun, 1 Jan 2006 01:00:00 GMT
D/OkHttp: X-DAE-Node: dis5
D/OkHttp: Pragma: no-cache
D/OkHttp: X-DAE-App: book
D/OkHttp: Cache-Control: must-revalidate, no-cache, private
D/OkHttp: Server: dae
D/OkHttp: Set-Cookie: bid=5qefVyUZ3KU; Expires=Sun, 06-Aug-17 14:29:11 GMT; Domain=.douban.com; Path=/
D/OkHttp: X-DOUBAN-NEWBID: 5qefVyUZ3KU
D/OkHttp: X-DAE-Node: dis17
D/OkHttp: X-DAE-App: book
D/OkHttp: Server: dae
D/OkHttp: {“count”:3,“start”:0,“total”:778,“books”:[{“rating”:{“max”:10,“numRaters”:202900,“average”:“9.0”,“min”:0},“subtitle”:"",“author”:["[法] 聖埃克蘇佩裏"],“pubdate”:“2003-8”,“tags”:[{“count”:49322,“name”:“小王子”,“title”:“小王子”},{“count”:41381,“name”:“童話”,“title”:“童話”},{“count”:19773,“name”:“聖埃克蘇佩裏”,“title”:“聖埃克蘇佩裏”}
D/OkHttp: <-- END HTTP (13758-byte body)
4.POST請求訪問網絡
傳輸數據類型爲鍵值對:@Field
傳輸數據類型爲鍵值對,這是我們最常用的POST請求數據類型,淘寶IP庫支持數據類型爲兼職對的POST請求。請求網絡接口的代碼如下所示:
public interface IpServiceForPost {
@FormUrlEncoded
@POST("getIpInfo.php")
Call<IpModel> getIpMsg(@Field("ip") String first);
}
首先用@FormUrlEncoded註解來標明這是一個表單請求,然後再getIpMsg方法中使用@Field註解來標示所對應的String類型數據的鍵,從而組成一組鍵值對進行傳遞。請求網絡代碼如下:
String url = "http://ip.taobao.com/service/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
IpServiceForPost ipService = retrofit.create(IpServiceForPost.class);
Call<IpModel> call = ipService.getIpMsg(ip);
call.enqueue(new Callback<IpModel>() {
@Override
public void onResponse(Call<IpModel> call, Response<IpModel> response) {
String country = response.body().getData().getCountry();
Log.i("wangshu", "country" + country);
Toast.makeText(getApplicationContext(), country, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<IpModel> call, Throwable t) {
}
});
傳輸數據類型JSON字符串:@Body
我們也可以用POST方式將字符串作爲請求體發送到服務器,請求網絡接口的代碼如下:
public interface IpServiceForPostBody {
@POST("getIpInfo.php")
Call<IpModel> getIpMsg(@Body Ip ip);
}
用@Body這個註解標識參數即可,Retrofit會將Ip對象轉換爲字符串:
public class Ip {
private String ip;
public Ip(String ip) {
this.ip = ip;
}
}
請求網絡的代碼基本一致:
...
IpServiceForPostBody ipService = retrofit.create(IpServiceForPostBody.class);
Call<IpModel> call = ipService.getIpMsg(new Ip(ip));
...
抓包可以看到,請求數據實際上是一個json字符串
單個文件上傳:@Part
public interface UploadFileForPart {
@Multipart
@POST("user/photo")
Call<User> updateUser(@Part MultipartBody.Part photo, @Part("description")RequestBody description);
}
Multipart註解允許多個@Part。updateUser方法的第一個參數是準備上傳的圖片文件,使用了MultipartBody類型;另一個參數是RequestBody類型,它用來傳遞簡單的鍵值對。請求網絡代碼如下:
...
File file = new File(Environment.getExternalStorageDirectory(), "devyu.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file);
MultipartBody.Part photo = MultipartBody.Part.createFormData("photos", "devyu.png", photoRequestBody);
UploadFileForPart uploadFilefile = retrofit.create(UploadFileForPart.class);
Call<User> call=uploadFilefile.updateUser(photo,RequestBody.create(null,"devyu"));
...
多個文件上傳@PartMap
@Multipart
@POST("user/photo")
Call<IpModel> updateUser(@PartMap Map<String,RequestBody> photos, @Part("description")RequestBody description);
這和單個文件是類似的,只不過用了Map封裝了上傳的文件,並用@PartMap註解來標識。其他的都和單文件上傳一樣
5.消息報頭
在HTTP請求中,爲了防止攻擊或者過濾掉不安全的訪問,或者添加特殊加密的訪問等,以便減輕服務器的壓力和請求的安全,通常會在消息報頭攜帶一些特殊的消息頭。Retrofit也提供了@Header來添加消息報頭。添加消息報頭有兩種方式:一種是靜態的,一種是動態的。
靜態的如下所示:
interface SomeService{
@GET("some/endpoint")
@Headers("Accept-Encoding:application/json")
Call<ResponseBody> getCarType();
}
使用@Headers註解添加消息報頭。如果需要添加多個消息報頭,則可以使用{}括起來:
interface SomeService{
@GET("some/endpoint")
@Headers({"Accept-Encoding:application/json",
"User-Agent:MoonRetrofit"
})
Call<ResponseBody> getCarType();
}
以動態方式添加消息報頭方式如下:
interface SomeService{
@GET("some/endpoint")
Call<ResponseBody> getCarType(@Header("location") String location);
}
使用@Header註解,可以通過getCarType方法動態添加消息報頭。