Android主流三方庫Retrofit使用深入解析

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:&gt;POST/uploadHTTP/1.1(277bytebody)D/HttpLoggingInterceptorLogger: --&gt; POST /upload HTTP/1.1 (277-byte body) 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方法動態添加消息報頭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章