浅谈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阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容