Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android網絡請求庫 - Say hello to retrofit

Android網絡請求庫 - Say hello to retrofit

編輯:關於Android編程

之前對Android裡常用的網絡請求庫OkHttp與Volley做了簡單的學習歸納與總結,這裡看這個系列中的最後一篇,來認識一下Retrofit。

Retrofit可以認為是OkHttp的“升級版”。之所以這麼說,是因為其內部默認正是基於OkHttp來進行封裝的。這點從Retrofit這個命名就可以看出端倪。

回顧一下OkHttp,我們會發現雖然是封裝過後的庫,但OkHttp的封裝是比較“碎片化”的。所以如果不自己再進行封裝,使用時代碼就比較容易耦合。

而Retrofit作為其升級版,有一個最吸引人的特色就是:將所有的請求封裝為interface,並通過“注解”來聲明api的相關信息。讓你爽到停不下來。


RESTful API

在正式開始了解Retrofit的使用之前,我們有必要先了解一個概念,即RESTful。這是因為Retrofit的初衷就是根據RESTful風格的API來進行封裝的。
關於RESTful的學習,可以參考一下RESTful API 設計指南此文。相信看完之後,就會對Restful有一個基本的認識和理解了。
但我們這裡可以對RESTful的核心思想做一個最簡練的總結,那就是:所謂”資源”,就是網絡上的一個實體,或者說是網絡上的一個具體信息。那麼我們訪問API的本質就是與網絡上的某個資源進行互動而已。那麼,因為我們實質是訪問資源,所以RESTful設計思想的提出者Fielding認為:
URI當中不應當出現動詞,因為”資源“表示一種實體,所以應該用名詞表示,而動詞則應該放在HTTP協議當中。那麼舉個最簡單的例子:

xxx.com/api/createUser xxx.com/api/getUser xxx.com/api/updateUser xxx.com/api/deleteUser

這樣的API風格我們應該很熟悉,但如果要遵循RESTful的設計思想,那麼它們就應該變為類似下面這樣:

[POST]xxx.com/api/User [GET] xxx.com/api/User [PUT]xxx.com/api/User [DELETE]xxx.com/api/User

也就是說:因為這四個API都是訪問服務器的USER表,所以在RESTful裡URL是相同的,而是通過HTTP不同的RequestMethod來區分增刪改查的行為。
而有的時候,如果某個API的行為不好用請求方法描述呢?比如說,A向B轉賬500元。那麼,可能會出現如下設計:

POST /accounts/1/transfer/500/to/2

在RESTful的理念裡,如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。可以像下面這樣使用它:

  POST /transaction HTTP/1.1
  Host: 127.0.0.1
  
  from=1&to=2&amount=500.00

好了,當然實際來說RESTful肯定不是就這點內容。這裡我們只是了解一下RESTful最基本和核心的設計理念。


從官方介紹初識Retrofit

當我們要去學習一樣新的技術,還有什麼是比官方的資料更好的了呢?所以,第一步我們打開網址http://square.github.io/retrofit/。然後開始閱讀:
我們發現官方的介紹非常簡單粗暴,一上來就通過一個Introduction來展示了如何使用Retrofit來進行一個最基本的請求。下面我們就逐步的分析一下:
這裡寫圖片描述vcHL0ru49rnYvPy1xMu1w/fQxc+io7o8c3Ryb25nPlJldHJvZml0u+G9q8TjtcRIVFRQIEFQSdequ7vOqkphdmHW0LXEaW50ZXJmYWNltcTQzsq9PC9zdHJvbmc+oaNPS6OsvdPXxb+0o7o8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160831/20160831091344555.png" title="\" />
這裡我們讀到的描述是:Retrofit類可以針對之前定義的interface生成一個具體實現。我們發現官方對此解釋的很言簡意赅,但更通俗的來講的話:
也就是說雖然我們之前將此次請求的API信息封裝為了一個接口,但我們也知道Java中接口是不能產生對象的,這時Retrofit類就站出來扮演了這個角色。
我們可以將Retrofit類看作是一個“工廠類”的角色,我們在接口中提供了此次的“產品”的生產規格信息,而Retrofit則通過信息負責為我們生產。
這裡寫圖片描述
這裡我們看到了一個重要的東西“Call”:通過之前封裝的請求接口對象創建的任一的Call都可以發起一個同步(或異步)的HTTP請求到遠程服務器
之後說了一些通過注解來描述request的好處,然後這個簡單的Introduction就結束了。那麼,現在我們來簡單總結一下,目前為止我們的感受如何。
我覺得就僅從以上簡單的介紹當中我們起碼有兩點直觀感受:那就是解耦明確使用簡單。通過注解的方式描述request讓人眼前一亮。但與此同時:
我們發現讀完Introduction後,僅僅是這個基本的用例中,都仍然有很多小細節需要我們通過實際使用之後才能搞明白。這可能在一定程度上說明了:
為什麼說Retrofit的使用門檻相對於其它庫來說要更高一些。不過沒關系,我們自己先來寫一個比官方Introduction更簡單的用例,再逐步深入。


動手寫第一個Demo來碰坑

現在我們已經閱讀完了官方的用例介紹,乍看之下沒什麼,但實際用起來說不定就得碰坑。為方便測試,仍然通過servlet在本地簡單的實現服務器。
我們現在的設想可能是這樣的,我只是想要寫一個demo來測試一下用Retrofit來成功發起一次最基本的get請求,所以最初的doGet無比簡單:

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.print("{\"describe\":\"請求成功\"}");
        out.flush();
        out.close();
    }

完成了以上代碼的編寫,然後我們把該servlet的URL配置一下,言簡意赅的,就配置為“/api/retrofitTesting”好了。這時服務器就准備完畢了。
很顯然,下面我們就可以把焦點放在Android客戶端的實現上來了。為了使用Retrofit,所以我們的第一步工作自然就是在自己的項目中設置依賴

這裡寫圖片描述

配置好了依賴,接著我們就可以開始編碼工作了。還記得官方用例的第一步嗎?所以我們要做的顯然是模仿它也把我們自己的HTTP API封裝成interface。

public interface DemoService {
    @GET("api/retrofitTesting")
    Call testHttpGet();
}
// GSON - BEAN
class ResponseInfo {
    String describe;
}

現在我們再來看這個所謂的API接口,可能就更加明確一點了。首先是通過注解@GET來聲明本次請求方式為GET以及注明API-URL。
而就之後聲明的方法來說:從其為Call的返回類型不難猜想多半與請求的發起有關,因為如果我們對OkHttp有所了解的話,一定就記得下面這樣的代碼:

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        Response response = client.newCall(request).execute();

newCall方法實際就是返回一個Call類型的實例。而Retrofit中的Call接口相對於OkHttp添加了一個泛型,該泛型用於說明本次請求響應的數據解析類型。
那麼這裡的泛型為什麼是我們自己建立的一個實體類呢?其實回憶一下之前服務器在response中返回的內容(JSON),就不難猜想到與GSON有關。
好的,我們繼續按照官方用例中接下來的步驟去完善我們的demo。封裝好了interface,接下來自然就是調用了,最終的代碼如下:

    private void testRetrofitHttpGet() {
        // step1
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.2.100:8080/TestServer/")
                .build();
        // step2
        DemoService service = retrofit.create(DemoService.class);
        // step3
        Call call = service.testHttpGet();
        // step4
        call.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                Log.d(TAG,response.body().describe);
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                Log.d(TAG, t.getMessage());
            }
        });
   }

有了之前的說明並對照官方示例,現在會發現以上代碼很容易理解。而我們發現官方沒有給出的step4其實也很熟悉,因為它和OkHttp的使用是相同的。
這個時候看上去我們的准備工作就已經完成了,於是興致勃勃的編譯並運行demo來查看一下效果,卻發現收到了如下的一個異常:

這裡寫圖片描述

為什麼會出現這種情況呢?蛋疼啊!別急,通過異常信息的描述,我們得知這似乎與類型的轉換相關,然後帶著這個疑問再去查看官方文檔,於是發現:

這裡寫圖片描述

從上述信息我們得知Retrofit默認只能將響應體轉換為OkHttp中的ResponseBody,而我們之前為Call設置的泛型類型是自定義的類型ResponseInfo 。
將JSON格式的數據轉換為Java-BEAN,很自然就會想到GSON。而Retrofit如果要執行這種轉換是要依賴於另一個庫的,所以我們還得在項目中配置另一個依賴:

這裡寫圖片描述

之後,在構造Retrofit對象的時候加上一句代碼就可以了:

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.2.100:8080/TestServer/")
                .addConverterFactory(GsonConverterFactory.create()) // 加上這一句哦,親
                .build();

這個時候,當我們再次運行程序就沒問題了,成功的得到如下的日志打印:

這裡寫圖片描述

現在,經過我們的一番摸索和折騰,關於Retrofit很基本的第一個demo就搗鼓出來了。
其實這是有意義的,因為回想一下會發現:現在我們對於Retrofit大致的使用套路,在心裡已經有個一二三了。


回到官方文檔繼續學習

前面我們說到對於Retrofit的使用已經有了一個基本的認識和了解,接下來要做的自然就是深入和繼續學習更多的使用細節。那麼很顯然,回到官方吧。

這裡寫圖片描述

事實上前面我們已經使用到了注解@GET,這裡就是告訴我們這種注解其實對於HTTP其它常用的請求方式(GET,POST,PUT,DELETE等等)都封裝了。
而對於@GET來說,我們知道HTTP-GET是可以將一些簡單的參數信息直接通過URL進行上傳的,所以URL又可以像下面那樣使用:

@GET("api/retrofitTesting?param=value")

replacement blocks與@path

不知道大家注意到官方示例和我們剛才自己測試寫的demo中有一個細小的差別沒,就是下面這樣的東西:

//官方的
@GET("users/{user}/repos")
//我們的
@GET("api/retrofitTesting")

我們注意到官方的API的URL中有一個”{user}“這樣的東西?它的作用是什麼呢?我們也能在官方文檔上找到答案:

這裡寫圖片描述

從上述介紹中,我們注意到一個叫做replacement blocks的東西。可以最簡單的將其理解為路徑替換塊,用”{}”表示,與注解@path配合使用。
當然我們自己實際去使用一下能夠對其有一個更加深刻的理解,所以我們將我們之前的demo修改一下,變成下面這樣:

public interface DemoService {
    @GET("api/{name}")
    Call testHttpGet(@Path("name") String apiAction);
}

使用的方式也會有所不同,我們在調用的使用需要將對應的值傳給responseInfo方法。

Call call = service.testHttpGet("retrofitTesting");

這樣修改過後的實際效果實際上與我們之前的demo是一樣的,那麼這樣做的好處是什麼?顯然是為了解耦。以官方的例子來說:
https://api.github.com/users/{user}/repos”中的{user}就是為了針對不同的github用戶解耦。因為這裡假設代入我的github,URL就將變成:
https://api.github.com/users/unconventional1programmer/repos”。而github的用戶千千萬萬,如果使用我們之前的方式代碼就會如下:

@GET("users/unconventional1programmer/repos")

這二者的優劣一目了然,我們肯定不會想要為了獲取不同的user的repos去寫N多個套路完全相同的API - interface吧。

@Query

前面我們講到:對於@GET來說,參數信息是可以直接放在url中上傳的。那麼你馬上就反應過來了,這一樣也存在嚴重的耦合!於是,就有了@query。

這裡寫圖片描述

同樣,為了便於理解,我們仍然自己來實際的使用一下。就用我們之前的舉到的例子好了,這裡我們用@query來替換我們之前說到的如下代碼:

@GET("api/retrofitTesting?param=value")

替換為:

public interface DemoService {
    @GET("api/retrofitTesting")
    Call testHttpGet(@Query("param") String param);
}

調用的時候改為:

Call call = service.testHttpGet("value");

然後就可以在服務器查看是否成功接收到參數了:

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println(request.getParameter("param"));
        }

@QueryMap

聰明的你現在肯定還不滿足,因為可能有這樣的疑問:假設我要在參數中上傳10個參數呢?這意味著我要在方法中聲明10個@Query參數?當然不是!

這裡寫圖片描述

我們看到了,Retrofit也考慮到了這點,所以針對於復雜的參數上傳,為我們准備了@QueryMap。現在來修改我們自己的demo:

public interface DemoService {
    @GET("api/retrofitTesting")
    Call testHttpGet(@QueryMap Map params);
}

調用的時候自然也發生了變化:

        Map params =new HashMap<>();
        params.put("param1","value1");
        params.put("param2","value2");
        Call call = service.testHttpGet(params);

@POST

有了之前的基礎,現在我們免不了要搗鼓一下POST。相信有了@GET的使用經驗,如果只是想發發簡單的POST請求是沒有多大的難度,我們關注下POST的BODY,即請求體的使用技巧。

這裡寫圖片描述

通過官方文檔,我們發現出現了一個新的東西叫做“@Body”,顧名思義它就是用來封裝請求體的。而同時通過後面的“User”參數類型,我們不難推斷出:使用@Body時,是通過實體對象的形式來進行封裝的。那麼閒話少說,我們當然仍舊是自己動手來試一下:
首先,我們編寫一下servlet的doPost方法,假設我們這裡提供一個新建User的API。完成後為該POST-request配置一個新的API URL,我們這裡配置為:“/api/users”。然後,在Android端編寫一個新的interface,大致如下:

public interface DemoService {
    @POST("api/users")
    Call uploadNewUser(@Body User user);
}

class User{
    private String name;
    private String gender;
    private int age;

    public User(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
}

class ResponseInfo{
    String describe;
}

這樣其實就搞定了,是不是很簡單。然後我們通過如下代碼去調用測試就行了:

Call call = service.uploadNewUser(new User("tsr","male",24));

這裡唯一需要說明的就是,通過@BODY這種方式封裝請求體,Retrofit是通過JSON的形式來封裝數據的。我們可以在服務器讀取流中的數據打印:

{"name":"tsr","gender":"male","age":24}

我單獨額外說明一下這個的初衷是因為:這種情況以servlet來說,通過如下的形式是讀取不到的對應的參數值的(返回null),需要自己解析。

System.out.println(request.getParameter("username"));//輸出為null

@FormUrlEncoded

這時有的朋友就說了,那我要是就想要通過request.getParameter的方式直接讀取參數信息呢?沒關系,也是可以的,使用@FormUrlEncoded搞定。

這裡寫圖片描述

其實通過這個注解的命名,我們就很容易聯系到HTTP Content-Type中的application/x-www-form-urlencoded:而其實在服務器打印Content-Type的話,會發現的確如此。也就是說,其實使用該注解過後,正是通過表單形式來上傳參數的。

public interface DemoService {
    @FormUrlEncoded
    @POST("api/users")
    Call uploadNewUser(@Field("username") String username,@Field("gender") String male,@Field("age") int age);
}

然後是依舊是調用,這時服務器就可以通過request.getParameter直接讀取參數了:

Call call = service.uploadNewUser("tsr","male",24);

當然,這個時候難免又會想起那個老梗:要寫這麼多的@Field參數?當然不是,也有@FieldMap供我們使用,使用方法參照@QueryMap

@Headers與@Header

使用了@FormUrlEncoded之後,不知道你有沒有好奇一下,假設我們的參數中含有中文信息,會不會出現亂碼?讓我們來驗證一下:

Call call = service.uploadNewUser("張德帥","male",24);

這裡上傳的username信息是中文,而在服務器讀取後進行打印,其輸出的是“?????·???”。沒錯,確實出現亂碼了。
這個時候我們應該如何去解決呢?當然可以通過URLEncode對數據進行指定編碼,然後服務器再進行對應的解碼讀取:

        String name =  URLEncoder.encode("張德帥","UTF-8");
        Call call = service.uploadNewUser(name,"male",24);

但如果了解一點HTTP協議的使用,我們知道還有另一種解決方式:在Request-Header中設置charset信息。於是,這個時候就涉及到添加請求頭了:

這裡寫圖片描述

關於@Headers的使用看上去非常簡單。那麼,接下來我們就來修改一下我們之前的代碼:

public interface DemoService {
    @Headers("Content-type:application/x-www-form-urlencoded;charset=UTF-8")
    @FormUrlEncoded
    @POST("api/users")
    Call uploadNewUser(@Field("username") String username,@Field("gender") String male,@Field("age") int age);
}

通過@Headers我們在Content-type中同時指明了編碼信息,再次運行程序測試,就會發現服務器正確讀取到了中文的信息。

除了@Headers之外,還有另一個注解叫做@Header。它的不同在於是動態的來添加請求頭信息,也就是說更加靈活一點。我們也可以使用一下:

public interface DemoService {
    // @Headers("Content-type:application/x-www-form-urlencoded;charset=UTF-8")
    @FormUrlEncoded
    @POST("api/users")
    Call uploadNewUser(@Header("Content-Type") String contentType,@Field("username") String username,@Field("gender") String male,@Field("age") int age);
}
// 調用
Call call = service.uploadNewUser("application/x-www-form-urlencoded;charset=UTF-8","張德帥","male",24);

讀取response header

通過上面的總結我們知道通過@Header可以在請求中添加header,那麼我們如何去讀取響應中的header呢?我們會發現官方文檔並沒有相關介紹。
那麼顯然我們就只能自己看看了,一找發現對於Retrofit2來說Response類有一個方法叫做headers(),通過它就獲取了本次請求所有的響應頭。
那麼,我們記得在OkHttp裡面,除了headers(),還有用於獲取單個指定頭的header()方法。我們能不能在Retrofit裡使用這種方式呢?答案是可以。
我們發現Retrofit的Response還有一個方法叫做raw(),調用該方法就可以把Retrofit的Response轉換為原生的OkHttp當中的Response。而現在我們就很容器實現header的讀取了吧。

okhttp3.Response okResponse = response.raw();
response.raw().header("Cache-Control");
}

進階技巧

@Multipart 與文件上傳

在官方文檔中,實際上還有一個重要的注解,那就是@Multipart。這個使用起來相對要復雜一點,所以放在這裡。下面我們就來看一下這個東西的使用。

這裡寫圖片描述

可以看到官方文檔上對該注解使用的說明非常簡單,但這個注解使用起來卻不是那麼簡單,這就很煩人了。不過也沒關系,我們由淺入深的來看一下。

public interface DemoService {
    @Multipart()
    @POST("api/multipartTesting")
    Call testMultipart(@Part("part1") String part1,@Part("part2") String part2);
}

這裡對官方示例做了簡化,因為我們在@Part參數中只使用了String類型,而沒有聲明RequestBody類型。接著就是調用它來進行測試:

Call call = service.testMultipart("this is the part1","this is the part2");

為了一探究竟,我們現在在服務器打印一下Content-Type與請求體(request-body)分別是怎樣的:

這裡寫圖片描述

現在我們來分析一下從打印的請求體中,我們能夠得到哪些信息:

首先,本次請求的Content-Type是multipart/form-data; 也就是代表通過2進制形式上傳多部分的數據。 boundary故名思議就是分割線,它是用來對上傳的不同部分的數據來進行分隔的,就像上圖中體現的一樣。 通過@Part傳入的參數信息都像上圖中一樣被組裝,首先是相關的內容信息,然後間隔一個空行,之後是實際數據內容,最後接以boundary。 當使用@Part上傳String參數信息時,我們可以看到其默認的參數類型為application/json。
(這代表如果你的服務器接不支持解析此類型的Content-Type,就需要自己修改為對應的類型)

現在我們注意到一個問題,那就是multipart/form-data將會以2進制形式來上傳數據信息。那麼,什麼時候我們才需要2進制呢?顯然就是文件的上傳。
OK,那麼我們繼續修改代碼,這次我們嘗試通過@Part來上傳一個文件會是什麼情況?

public interface DemoService {
    @Multipart()
    @POST("api/files")
    Call uploadFile(@Part("file") RequestBody photo);
}
//調用部分
File path = Environment.getExternalStorageDirectory();
File file = new File(path,"test.jpg");
RequestBody image = RequestBody.create(MediaType.parse("image/png"),file);
Call call = service.uploadFile(image);

這裡可以發現,當要上傳的數據是文件時,就要使用到RequestBody了。再次運行程序,然後截取服務器打印的請求體的部分進行查看:

這裡寫圖片描述

我們其實可以很直觀的看到,大體的格式與之前都是一樣的,而不同之處在於:

Content-Type不再是application/json,而是image/png了。 空行之後的數據內容不再是之前直觀的文本,而是二進制的圖片內容。

這個時候就會出現一個問題,假設我們為了方便,打算通過一些公有的API去測試一下上傳文件(圖片)什麼的,會發現上傳不成功。這是為什麼呢?
實際上如果我們完全自己動手來寫服務器,完全根據之前的請求體打印信息的格式來寫解析的方法,肯定是也能完成文件上傳的。但問題在於:
我們說到multipart/form-data是支持多部分數據同時上傳的,於是就出現了一個聽上去很有逼格的梗,叫做“圖文混傳”。那麼:
為了區別於同一個request-body中的文本或文件信息,所以通常實際開發來說,上傳文件的時候,Content-Disposition是這樣的:

Content-Disposition: form-data; name="file";filename="test.jpg"

而不是我們這裡看到的:

Content-Disposition: form-data; name="file"

於是別人的api裡讀取不到這個關鍵的“filename”,自然也就無法完成上傳了。所以如果想通過Retrofit實現文件上傳,可以通過如下方式來改造一下:

public interface DemoService {
    @Multipart()
    @POST("api/files")
    Call uploadFile(@Part("file\";filename=\"test.jpg") RequestBody photo);
}

然後我們在servlet服務器中進行解析,完成文件的上傳。這裡當然就不自己去寫解析body的代碼了,可以使用現有的庫commons-fileupload。

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();

            ServletFileUpload upload = new ServletFileUpload(factory);

            List items = upload.parseRequest(request);// 得到所有的文件
            Iterator i = items.iterator();
            while (i.hasNext()) {
                FileItem fi = (FileItem) i.next();
                String fileName = fi.getName();
                if (fileName != null) {
                    File fullFile = new File(fi.getName());
                    // 寫入到D盤
                    File savedFile = new File("D://", fullFile.getName());
                    fi.write(savedFile);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

再次運行程序,從代碼中可以看到,我們希望將客戶端上傳的圖片保存電腦的D盤根目錄下,打開D盤看一下吧,已經成功上傳了!
這裡寫圖片描述

不同類型的Converter

相信我們還記得之前我們讀取服務器返回的json數據時,依賴於GsonConverterFactory就成功完成了解析。那麼問題來了:
假設有的時候我們服務器返回的就是一段簡單的文本信息呢?這叫老夫如何是好,其實這在之前就已經有了答案了:

這裡寫圖片描述

我們看到除了基於GSON的converter之外,官方還提供了很多其它的Converter。眼尖的馬上就看到了Scalars後面有一個東西叫做String。
於是接下來的工作就簡單了,依然是配置依賴;然後將Call的泛型指定為String;最後記得將Converter改掉,像下面這樣:

//...
.addConverterFactory(ScalarsConverterFactory.create())

再次運行程序,查看日志打印信息,發現成功搞定:

這裡寫圖片描述

有了這個例子,對於同類型的需求我們就很容易舉一反三了。但馬上又想到:假設我們的數據類型官方沒有提供現成的Converter呢?很簡單,自己造!

這裡寫圖片描述

從官方描述中,我們發現自定義Converter其實很簡單。即創建一個繼承自Converter.Factory的類,然後提供自己的Converter實例就行了。
那麼假設,我們現在服務返回的數據格式是下面這樣的。而我們的需求是希望在客戶端對其解析後,將其放進一個Map裡面。

username=張三&male=男性

根據這樣的需求,我們可以像下面這樣很容易的實現我們自己的Converter:

public class CustomConverterFactory extends Converter.Factory{

    public static CustomConverterFactory create() {
        return new CustomConverterFactory();
    }

    @Override
    public Converter<responsebody,> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        return new CustomConverter();
    }

    class CustomConverter implements Converter<responsebody,map<string,string>>{

        @Override
        public Map<string,string> convert(ResponseBody body) throws IOException {
            Map<string,string> map = new HashMap<>();
            String content = body.string();
            String [] keyValues = content.split("&");

            for (int i = 0;i<keyvalues.length;i++){ int="" key="keyValue.substring(0,postion);" keyvalue="keyValues[i];" postion="keyValue.indexOf("=");" pre="" return="" string="" value="keyValue.substring(postion+1,keyValue.length());"><p>現在,我們就可以進行調用測試了:</p><pre class="brush:java;">            public void onResponse(Call<map<string,string>> call, Response<map<string,string>> response) {
                Map<string,string> map = response.body();
                Set<string> keySet = map.keySet();

                for (String key : keySet){
                    Log.d(TAG,key+":"+map.get(key));
                }

            }</string></string,string></map<string,string></map<string,string></pre>

查看日志打印: 這裡寫圖片描述

使用Interceptor

之前在學習@Headers的用法的時候,其實官方文檔中還有一個東西,我們沒有總結。官方的描述是這樣的:

Headers that need to be added to every request can be specified using an OkHttp interceptor. 簡單易懂,也就是說:如果你項目裡的每一個請求需要加入某個header,那麼就可以使用interceptor(interceptor本身是OkHttp裡的東西)

interceptor顧名思義就是攔截器,其具體使用方法可以參照https://github.com/square/okhttp/wiki/Interceptors。這裡我們看下官方的第一個例子:

這裡寫圖片描述

這裡面的其它東西相信都很熟悉,一個新的關鍵叫做Interceptor.chain。很明顯,從命名我們可以猜想出它就是完成攔截的關鍵。 事實上,我們可以對以上的示例進行簡化,最後其實我們需要學習的就兩行代碼:

Request request = chain.request();
Response response = chain.proceed(request);

也就是說:

通過chain的request()方法,可以返回Request對象。 通過chain的proceed()方法,可以返回此次請求的響應對象。

那麼,以我們之前的需求來說:對所有的請求都添加某個請求頭。實際就很容易實現了。

            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                // 重寫request
                Request requestOverwrite = request.newBuilder().header("User-Agent","Android").build();

                return chain.proceed(requestOverwrite);
            }

同理,如果我們想要在每個Response中都添加某個header值呢?做法是一樣的:

            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                okhttp3.Response originalResponse = chain.proceed(request);

                return originalResponse.newBuilder().header("Cache-Control","max-age=100").build();
            }

但是,到現在為止,我們說到的都是interceptor在OkHttp中的使用。那麼對應在Retrofit呢,其實很簡單,因為我們說了Retrofit就是基於OkHttp的。 那麼,前面一切的步驟都可以按照在OkHttp裡的使用方法按部就班。唯一額外要做的工作就是,構建好OkHttpClient對象後,記得把它設置給Retrofit。

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.2.100:8080/TestServer/")
                .client(mOkHttpClinet) // 看這裡,看這裡
                .build();

總結

到了這裡,對Android中常用的網絡請求庫say hello系列的三篇博客總算是總結完畢了。而針對Retrofit的此文是寫的最認真的一篇,一個是因為現在它的確是流行;其次是因為個人也比較感興趣。如果您觀看後有任何想法,望多多指正,給出寶貴意見。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved