淺談Android網絡封裝框架Retrofit

開源框架地址:https://github.com/square/retrofit

英文文檔官網:http://square.github.io/retrofit/

RxJava框架:https://github.com/ReactiveX/RxJava

okhttp框架:https://github.com/square/okhttp

在對Android 開發中,我們都是從原生的HttpUrlConnection到經典的 Apache公司的HttpClient,再到對前面這些網絡基礎框架的封裝(比如Volley、AsyncHttpClient等)。Http請求相關開源框架還是很多的,今天我們講解 Square 公司開源的Retrofit。Square 公司的框架總是一如既往的簡潔優雅!Retrofit更是以其簡易的接口配置、強大的擴展支持、優雅的代碼結構受到大家的追捧。

Retrofit是一個 RESTful 的 HTTP 網絡請求框架的封裝。注意這里并沒有說它是網絡請求框架,主要原因在于網絡請求的工作并不是Retrofit來完成的。Retrofit2.0 開始內置OkHttp,前者專注于接口的封裝,后者專注于網絡請求的高效,二者分工協作!

我們的應用程序通過Retrofit請求網絡,實際上是使用Retrofit接口層封裝請求參數、Header、Url 等信息,之后由OkHttp完成后續的請求操作,在服務端返回數據之后,OkHttp將原始的結果交給Retrofit,后者根據用戶的需求對結果進行解析的過程。

Retrofit的使用就像它的編碼風格一樣,非常簡單,首先你需要在你的 build.gradle 中添加依賴:

[java]view plaincopy

compile'com.squareup.retrofit2:retrofit:2.0.2'

加入我們想要訪問 GitHub 的 api 對,那么我們就定義一個接口:

接口當中的listRepos方法,就是我們想要訪問的api了,在發起請求時,{user}會被替換為方法的第一個參數user。

Retrofit支持的協議包括GET/POST/PUT/DELETE/HEAD/PATCH,當然你也可以直接用HTTP來自定義請求。這些協議均以注解的形式進行配置例如下面GET:

[java]view plaincopy

publicinterfaceGitHubService?{

@GET("users/{user}/repos")

Call>?listRepos(@Path("user")?String?user);

}

這些注解都有一個參數 value,用來配置其路徑,比如示例中的 users/{user}/repos,我們還注意到在構造Retrofit之時我們還傳入了一個baseUrl("https://api.github.com/"),請求的完整 Url 就是通過baseUrl與注解的value(下面稱 “path“ ) 整合起來的,具體整合的規則如下:

path是絕對路徑的形式:

path = "/apath",baseUrl = "http://host:port/a/b"

Url = "http://host:port/apath"

path是相對路徑,baseUrl是目錄形式:

path = "apath",baseUrl = "http://host:port/a/b/"

Url = "http://host:port/a/b/apath"

path是相對路徑,baseUrl是文件形式:

path = "apath",baseUrl = "http://host:port/a/b"

Url = "http://host:port/a/apath"

path是完整的 Url:

path = "http://host:port/aa/apath",baseUrl = "http://host:port/a/b"

Url = "http://host:port/aa/apath"

建議采用第二種方式來配置,并盡量使用同一種路徑形式。

構造 Retrofit:

[java]view plaincopy

Retrofit?retrofit?=newRetrofit.Builder.baseUrl("https://api.github.com/").build;

GitHubService?service?=?retrofit.create(GitHubService.class);

//service發起請求

Call>?repos?=?service.listRepos("octocat");

返回的repos其實并不是真正的數據結果,它更像一條指令,你可以在合適的時機去執行它:

[java]view plaincopy

Lisg?data?=?repos.execute();//同步調用

repos.enqueue(newCallback>(){

@Override

publicvoidonResponse(Call>?call,?Response>?response){

List?data?=?response.body();

}

@Override

publicvoidonFailure(Call>?call,Throwable?t){

t.printStackTrace();

}

});

是不是很簡單,其中的接口定義方式就是我們前面有篇博客講的(淺談Java回調機制)http://blog.csdn.net/caihongdao123/article/details/51657840

認識Query & QueryMap

[java]view plaincopy

@GET("/list")?Call?list(@Query("page")intpage);

Query其實就是 Url 中 ‘?’ 后面的 key-value,比如:這里的 cate=android就是一個Query,而我們在配置它的時候只需要在接口方法中增加一個參數,即可:

[java]view plaincopy

interfacePrintlnServer{

@GET("/")

Call?cate(@Query("cate")?String?cate);

}

當面臨參數很多的情況,我們就采用QueryMap!

認識Field & FieldMap

[java]view plaincopy

@FormUrlEncoded

@POST("/")

Call?example(@Field("name")?String?name,@Field("occupation")?String?occupation);

我們用 Field聲明了表單的項,這樣提交表單就跟普通的函數調用一樣簡單直接了,表單項不確定個數就試用FieldMap。

認識Part & PartMap

[java]view plaincopy

publicinterfaceFileUploadService?{

@Multipart@POST("upload")

Call?upload(@Part("description")?RequestBody?description,@PartMultipartBody.Part?file);

}

如果你需要上傳文件,和我們前面的做法類似,定義一個接口方法,需要注意的是,這個方法不再有 @FormUrlEncoded這個注解,而換成了@Multipart,后面只需要在參數中增加Part就可以了。也許你會問,這里的Part和Field究竟有什么區別,其實從功能上講,無非就是客戶端向服務端發起請求攜帶參數的方式不同,并且前者可以攜帶的參數類型更加豐富,包括數據流。也正是因為這一點,我們可以通過這種方式來上傳文件,下面我們就給出這個接口的使用方法:

[java]view plaincopy

//先創建Service

FileUploadService?service?=?retrofit.create(FileUploadService.class);

//構建要上傳的文件

File?file?=newFile(filename);

RequestBody?requestFile?=?RequestBody.crate(MediaType.parse("application/otcet-stream"),file);

MultipartBody.Part?body?=?MultipartBody.Part.crateFormData("aFile",file.getNmae(),requestFile);

String?description?=?RequestBody.create(MediaType.parse("multipart/form-data"),descriptionString);

Call?call?=?service.upload(description,body);

call.enqueue(newCallback(){

@Override

publicvoidonResponse(Call?response){

Log.d("success","success");

}

@Override

publicvoidonFailure(Call?call,Throwable?t){

t.printStackTrace();

}

});

如果你需要上傳多個文件,就聲明多個Part參數,或者試試PartMap!

上面提供的上傳文件的方式前后構造了三個對象:File-->RquestBody-->MultipartBody.part看起來其實是非常復雜的。

實際上Retrofit允許我們自己定義入參和返回的類型,不過,如果這些類型比較特別,我們還需要準備相應的 Converter,也正是因為 Converter 的存在,Retrofit在入參和返回類型上表現得非常靈活。該如何做呢,請看下面:

[java]view plaincopy

publicinterfaceFileUploadService?{

@Multipart@POST("upload")

Call?upload(@Part("description")?RequestBody?description,//注意這里的參數?"aFile"?之前是在創建?MultipartBody.Part?的時候傳入的?@Part("aFile")

File?file);

}

staticclassFileRequestBodyConverterFactoryextendsConverter.Factory?{

@Override

publicConverter?requestBodyConverter(Type?type,?Annotation[]?parameterAnnotations,?Annotation[]?methodAnnotations,?Retrofit?retrofit)?{

returnnewFileRequestBodyConverter;

}

}

staticclassFileRequestBodyConverterimplementsConverter?{

@Override

publicRequestBody?convert(File?file)throwsIOException?{

returnRequestBody.create(MediaType.parse("application/otcet-stream"),?file);

}

}

在創建 Retrofit的時候記得配置上它,這樣,我們的文件內容就能上傳了:

[java]view plaincopy

addConverterFactory(newFileRequestBodyConverterFactory)

注意:Retrofit在選擇合適的 Converter 時,主要依賴于需要轉換的對象類型,在添加 Converter 時,注意 Converter 支持的類型的包含關系以及其順序。

總結上面的技術知識,我們來看完整的請求:

前面我們已經看到 Retrofit為我們構造了一個OkHttpCall,實際上每一個OkHttpCall都對應于一個請求,它主要完成最基礎的網絡請求,而我們在接口的返回中看到的 Call 默認情況下就是OkHttpCall了,如果我們添加了自定義的callAdapter,那么它就會將OkHttp適配成我們需要的返回值,并返回給我們。

先看下call接口代碼:

[java]view plaincopy

publicinterfaceCallextendsCloneable?{

//同步發起請求

Response?executethrowsIOException;

//異步發起請求,結果通過回調返回

voidenqueue(Callback?callback);

booleanisExecuted;

voidcancel;

booleanisCanceled;

Call?clone;

//返回原始請求

Request?request;

}

接下來執行repos其實就是一個OkHttpCall實例,execute就是要發起網絡請求:

[java]view plaincopy

Call>?repos?=?service.listRepos("octocat");

List?data?=?repos.execute;

parseResponse主要完成了由okhttp3.Response向retrofit.Response的轉換,同時也處理了對原始返回的解析:

[java]view plaincopy

Response?parseResponse(okhttp3.Response?rawResponse)throwsIOException?{

ResponseBody?rawBody?=?rawResponse.body;//略掉一些代碼

try{

//在這里完成了原始?Response?的解析,T?就是我們想要的結果,比如?GitHubService.listRepos?的?List?T?body?=?serviceMethod.toResponse(catchingBody);

returnResponse.success(body,?rawResponse);

}catch(RuntimeException?e)?{

//?If?the?underlying?source?threw?an?exception,?propagate?that?rather?than?indicating?it?was?//?a?runtime?exception.?catchingBody.throwIfCaught;

throwe;

}

}

處理結果時我想要接入 RxJava,讓接口的返回結果改為 Observable:

[java]view plaincopy

publicinterfaceGitHub?{

@GET("/repos/{owner}/{repo}/contributors")

Observable>?contributors(@Path("owner")?String?owner,@Path("repo")?String?repo);

}

只需要提供一個 Adapter,將 OkHttpCall轉換為Observable即可。Retrofit提供了相應的 Adapter(RxJavaCallAdapterFactory)我們只需要在構造 Retrofit時,添加它:

[java]view plaincopy

addCallAdapterFactory(RxJavaCallAdapterFactory.create)

我們來看看RxJavaCallAdapterFactory是如何工作的:

我們只需要實現 CallAdapter類來提供具體的適配邏輯,并實現相應的Factory,用來將當前的CallAdapter注冊到Retrofit當中,并在Factory.get方法中根據類型來返回當前的CallAdapter即可。知道了這些,我們再來看RxJavaCallAdapterFactory:

[java]view plaincopy

/**

*??只給大家列出來比較重要的代碼段

*/

publicfinalclassRxJavaCallAdapterFactoryextendsCallAdapter.Factory{

@Override

publicCallAdapger?get(Type?returnType,Annotation[]?annotations,Retrofit?retrofit){

//判斷returnType是否為RxJava支持的類型

Class?rawType?=?getRawType(returnType);

String?CanonicalName?=?RawType.getCanonicalName();

booleanisSingle?="rx.Single".equals(CanonicalName);

booleanisCompletable?="rx.Completable".equals(CanonicalName);

if(rawType?!=?Observable.class&&?!isSingle?&&?!isCompletable){

returnnull;

}

returnAdapter;//"獲取你需要的Adapter?返回"

}

staticfinalclassSimpleCallAdapterimplementsCallAdapter>{

privatefinalType?responseType;

privatefinalScheduler?scheduler;

SimpleCallAdpter(Type?responseType?,Scheduler?scheduler){

this.responseType?=?responseType;

this.scheduler?=?scheduler;

}

@Override

publicType?responseType(){

returnresponseType;

}

@Override

public?Observable?adapt(Call?call){

//在這里創建需要作為返回值的Observable實例,并持有call實例,所以在Observable.subscribe觸發時,call.execute將會被調用

Observable?observable?=?Observable.create(newCallOnSubscribe<>(call)).lift(OperatorMapResponseToBodyOrError.instance());

if(scheduler?!=null){

returnobservable.subscribeOn(scheduler);

}

returnobservable;

}

}

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容