編輯:關於Android編程
之前對Android裡常用的網絡請求庫OkHttp與Volley做了簡單的學習歸納與總結,這裡看這個系列中的最後一篇,來認識一下Retrofit。
Retrofit可以認為是OkHttp的“升級版”。之所以這麼說,是因為其內部默認正是基於OkHttp來進行封裝的。這點從Retrofit這個命名就可以看出端倪。
回顧一下OkHttp,我們會發現雖然是封裝過後的庫,但OkHttp的封裝是比較“碎片化”的。所以如果不自己再進行封裝,使用時代碼就比較容易耦合。
而Retrofit作為其升級版,有一個最吸引人的特色就是:將所有的請求封裝為interface,並通過“注解”來聲明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最基本和核心的設計理念。
當我們要去學習一樣新的技術,還有什麼是比官方的資料更好的了呢?所以,第一步我們打開網址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更簡單的用例,再逐步深入。
現在我們已經閱讀完了官方的用例介紹,乍看之下沒什麼,但實際用起來說不定就得碰坑。為方便測試,仍然通過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") CalltestHttpGet(); } // 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 Callcall = 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")
不知道大家注意到官方示例和我們剛才自己測試寫的demo中有一個細小的差別沒,就是下面這樣的東西:
//官方的 @GET("users/{user}/repos") //我們的 @GET("api/retrofitTesting")
我們注意到官方的API的URL中有一個”{user}“這樣的東西?它的作用是什麼呢?我們也能在官方文檔上找到答案:
從上述介紹中,我們注意到一個叫做replacement blocks的東西。可以最簡單的將其理解為路徑替換塊,用”{}”表示,與注解@path配合使用。
當然我們自己實際去使用一下能夠對其有一個更加深刻的理解,所以我們將我們之前的demo修改一下,變成下面這樣:
public interface DemoService { @GET("api/{name}") CalltestHttpGet(@Path("name") String apiAction); }
使用的方式也會有所不同,我們在調用的使用需要將對應的值傳給responseInfo方法。
Callcall = 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吧。
前面我們講到:對於@GET來說,參數信息是可以直接放在url中上傳的。那麼你馬上就反應過來了,這一樣也存在嚴重的耦合!於是,就有了@query。
同樣,為了便於理解,我們仍然自己來實際的使用一下。就用我們之前的舉到的例子好了,這裡我們用@query來替換我們之前說到的如下代碼:
@GET("api/retrofitTesting?param=value")
替換為:
public interface DemoService { @GET("api/retrofitTesting") CalltestHttpGet(@Query("param") String param); }
調用的時候改為:
Callcall = service.testHttpGet("value");
然後就可以在服務器查看是否成功接收到參數了:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println(request.getParameter("param")); }
聰明的你現在肯定還不滿足,因為可能有這樣的疑問:假設我要在參數中上傳10個參數呢?這意味著我要在方法中聲明10個@Query參數?當然不是!
我們看到了,Retrofit也考慮到了這點,所以針對於復雜的參數上傳,為我們准備了@QueryMap。現在來修改我們自己的demo:
public interface DemoService { @GET("api/retrofitTesting") CalltestHttpGet(@QueryMap Map params); }
調用的時候自然也發生了變化:
Mapparams =new HashMap<>(); params.put("param1","value1"); params.put("param2","value2"); Call call = service.testHttpGet(params);
有了之前的基礎,現在我們免不了要搗鼓一下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") CalluploadNewUser(@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; }
這樣其實就搞定了,是不是很簡單。然後我們通過如下代碼去調用測試就行了:
Callcall = 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
這時有的朋友就說了,那我要是就想要通過request.getParameter的方式直接讀取參數信息呢?沒關系,也是可以的,使用@FormUrlEncoded搞定。
其實通過這個注解的命名,我們就很容易聯系到HTTP Content-Type中的application/x-www-form-urlencoded:而其實在服務器打印Content-Type的話,會發現的確如此。也就是說,其實使用該注解過後,正是通過表單形式來上傳參數的。
public interface DemoService { @FormUrlEncoded @POST("api/users") CalluploadNewUser(@Field("username") String username,@Field("gender") String male,@Field("age") int age); }
然後是依舊是調用,這時服務器就可以通過request.getParameter直接讀取參數了:
Callcall = service.uploadNewUser("tsr","male",24);
當然,這個時候難免又會想起那個老梗:要寫這麼多的@Field參數?當然不是,也有@FieldMap供我們使用,使用方法參照@QueryMap
使用了@FormUrlEncoded之後,不知道你有沒有好奇一下,假設我們的參數中含有中文信息,會不會出現亂碼?讓我們來驗證一下:
Callcall = service.uploadNewUser("張德帥","male",24);
這裡上傳的username信息是中文,而在服務器讀取後進行打印,其輸出的是“?????·???”。沒錯,確實出現亂碼了。
這個時候我們應該如何去解決呢?當然可以通過URLEncode對數據進行指定編碼,然後服務器再進行對應的解碼讀取:
String name = URLEncoder.encode("張德帥","UTF-8"); Callcall = 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") CalluploadNewUser(@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") CalluploadNewUser(@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);
通過上面的總結我們知道通過@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。這個使用起來相對要復雜一點,所以放在這裡。下面我們就來看一下這個東西的使用。
可以看到官方文檔上對該注解使用的說明非常簡單,但這個注解使用起來卻不是那麼簡單,這就很煩人了。不過也沒關系,我們由淺入深的來看一下。
public interface DemoService { @Multipart() @POST("api/multipartTesting") CalltestMultipart(@Part("part1") String part1,@Part("part2") String part2); }
這裡對官方示例做了簡化,因為我們在@Part參數中只使用了String類型,而沒有聲明RequestBody類型。接著就是調用它來進行測試:
Callcall = 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。現在我們注意到一個問題,那就是multipart/form-data將會以2進制形式來上傳數據信息。那麼,什麼時候我們才需要2進制呢?顯然就是文件的上傳。
OK,那麼我們繼續修改代碼,這次我們嘗試通過@Part來上傳一個文件會是什麼情況?
public interface DemoService { @Multipart() @POST("api/files") CalluploadFile(@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") CalluploadFile(@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); Listitems = 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盤看一下吧,已經成功上傳了!
相信我們還記得之前我們讀取服務器返回的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>
查看日志打印:
之前在學習@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的此文是寫的最認真的一篇,一個是因為現在它的確是流行;其次是因為個人也比較感興趣。如果您觀看後有任何想法,望多多指正,給出寶貴意見。
Android的消息機制主要是指Handler的運行機制,Handler的運行需要底層的MessageQueue和Looper的支撐。MessageQueue 消息隊列,
簡介點擊事件的事件分發,其實就是對MotionEvent事件的分發過程,即當一個MotionEvent產生之後,系統需要這個事件傳遞給一個具體的View,而這個傳遞過程就
說明:RecyclerView是support-v7包中的新組件,你可以使用該組件替代ListView和CridView,從命名可以看出RecyclerView會自動回
上一篇文章對MVC框架模式做了簡要概述並且在文章的最後給出了MVC3個組件之間相互工作的邏輯圖,在本文我們將進一步對模型(model)-視圖(view)-控制器(cont