開源框架地址: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;
}
}
}