Retrofit
来源:互联网 发布:linux安装oracle 编辑:程序博客网 时间:2024/06/10 17:32
Retrofit将Http的请求转换成了Java接口形式。
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user);}
前言
本文中的Retrofit均指代Retrofit2.0。
本文涉及到的代码以及测试使用的接口可在Github上找到。
测试接口服务器在 server 项目下,直接运行 RESTServer.main() 即可启动测试服务器,所面代码示例均使用该接口(接口地址 http://localhost:4567/ ).
当然你也可以自己借助 json-server 或 最新开源的Parse 搭建一个REST API,不过都需要安装Node.js,有兴趣的可以去试试。
接口列表:
注:以上的接口的{id}和{page}均代表一个纯数字,/blog/{id} 可以用 /blog?id=XXX 代替,page同理。
了解了注解、反射、泛型、HTTP的内容只需要看一篇Retrofit的代码示例就可以轻松玩转Retrofit
1.1、创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .build();
创建Retrofit实例时需要通过Retrofit.Builder
,并调用baseUrl
方法设置URL。
注: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException
,所以如果你看到别的教程没有以 / 结束,那么多半是直接从Retrofit 1.X 照搬过来的。
1.2、接口定义
以获取指定id的Blog为例:
public interface BlogService { @GET("blog/{id}") Call<ResponseBody> getBlog(@Path("id") int id);}
注意,这里是interface
不是class
,所以我们是无法直接调用该方法,我们需要用Retrofit创建一个BlogService
的代理对象。
BlogService service = retrofit.create(BlogService.class);
拿到代理对象之后,就可以调用该方法啦。
1.3、接口调用
Call<ResponseBody> call = service.getBlog(2);// 用法和OkHttp的call如出一辙,// 不同的是如果是Android系统回调方法执行在主线程call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { try { System.out.println(response.body().string()); } catch (IOException e) { e.printStackTrace(); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { t.printStackTrace(); }});
打印结果:
"code":200,"msg":"OK","data":{"id":2,"date":"2016-04-15 03:17:50","author":"怪盗kidou","title":"Retrofit2 测试2","content":"这里是 Retrofit2 Demo 测试服务器2"},"count":0,"page":0}
2、Retrofit注解详解
Retrofit 共22个注解,这节就专门介绍这22个注解,为帮助大家更好理解我将这22个注解分为三类,并用表格的形式展现出来,表格上说得并不完整,具体的见源码上的例子注释。
第一类:HTTP请求方法
以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:method
、path
,hasBody
,下面是用HTTP注解实现上面 Example01.java 的例子。
public interface BlogService { /** * method 表示请求的方法,区分大小写 * path表示路径 * hasBody表示是否有请求体 */ @HTTP(method = "GET", path = "blog/{id}", hasBody = false) Call<ResponseBody> getBlog(@Path("id") int id);}
注:method 的值 retrofit 不会做处理,所以要自行保证其准确性。
第二类:标记类
@FormUrlEncoded@POST("user/edit")Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
@Multipart@PUT("user/photo")Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
第三类:参数类
注1:{占位符}和PATH
尽量只用在URL的path部分,url中的参数使用Query
和QueryMap
代替,保证接口定义的简洁
注2:Query
、Field
和Part
这三者都支持数组和实现了Iterable
接口的类型,如List
,Set
等,方便向后台传递数组。
@GET("group/{id}/users")Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);
Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);//结果:ids[]=0&ids[]=1&ids[]=2
静态的Header
@Headers("Cache-Control: max-age=640000")@GET("widget/list")Call<List<Widget>> widgetList();
@Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App"})@GET("users/{username}")Call<User> getUser(@Path("username") String username);
动态修改Header值
@GET("user")Call<User> getUser(@Header("Authorization") String authorization)
3、Gson与Converter
在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody
,这也是什么我在前面的例子接口的返回值都是 Call<ResponseBody>
,但如果响应体只是支持转换为ResponseBody
的话何必要引用泛型呢,返回值直接用一个Call
就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,而Converter
就是Retrofit为我们提供用于将ResponseBody
转换为我们想要的类型,有了Converter
之后我们就可以写把我们的第一个例子的接口写成这个样子了:
public interface BlogService { @GET("blog/{id}") Call<Result<Blog>> getBlog(@Path("id") int id);}
当然只改变泛型的类型是不行的,我们在创建Retrofit时需要明确告知用于将ResponseBody
转换我们泛型中的类型时需要使用的Converter
引入Gson支持:
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
通过GsonConverterFactory为Retrofit添加Gson支持:
Gson gson = new GsonBuilder() //配置你的Gson .setDateFormat("yyyy-MM-dd hh:mm:ss") .create();Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") //可以接收自定义的Gson,当然也可以不传 .addConverterFactory(GsonConverterFactory.create(gson)) .build();
这样Retrofit就会使用Gson将ResponseBody
转换我们想要的类型。
这是时候我们终于可以演示如使创建一个Blog了!
@POST("blog")Call<Result<Blog>> createBlog(@Body Blog blog);
被@Body注解的的Blog将会被Gson转换成RequestBody发送到服务器。
BlogService service = retrofit.create(BlogService.class);Blog blog = new Blog();blog.content = "新建的Blog";blog.title = "测试";blog.author = "怪盗kidou";Call<Result<Blog>> call = service.createBlog(blog);
结果:
Result{code=200, msg='OK', data=Blog{id=20, date='2016-04-21 05:29:58', author='怪盗kidou', title='测试', content='新建的Blog'}, count=0, page=0}
4、RxJava与CallAdapter
说到Retrofit就不得说到另一个火到不行的库RxJava,网上已经不少文章讲如何与Retrofit结合,但这里还是会有一个RxJava的例子,不过这里主要目的是介绍使用CallAdapter所带来的效果。
第3节介绍的Converter
是对于Call<T>
中T
的转换,而CallAdapter
则可以对Call
转换,这样的话Call<T>
中的Call
也是可以被替换的,而返回值的类型就决定你后续的处理程序逻辑,同样Retrofit提供了多个CallAdapter
,这里以RxJava
的为例,用Observable
代替Call
:
引入RxJava支持:
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'// 针对rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 针对rxjava2.x .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();
接口设计:
public interface BlogService { @POST("/blog") Observable<Result<List<Blog>>> getBlogs();}
使用:
BlogService service = retrofit.create(BlogService.class);service.getBlogs(1) .subscribeOn(Schedulers.io()) .subscribe(new Subscriber<Result<List<Blog>>>() { @Override public void onCompleted() { System.out.println("onCompleted"); } @Override public void onError(Throwable e) { System.err.println("onError"); } @Override public void onNext(Result<List<Blog>> blogsResult) { System.out.println(blogsResult); } })
结果:
Result{code=200, msg='OK', data=[Blog{id=1, date='2016-04-15 03:17:50', author='怪盗kidou', title='Retrofit2 测试1', content='这里是 Retrofit2 Demo 测试服务器1'},.....], count=20, page=1}
像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:
1、用Observable<Response<T>>
替代Observable<T>
,这里的Response
指retrofit2.Response
2、用Observable<Result<T>>
代替Observable<T>
,这里的Result
是指retrofit2.adapter.rxjava.Result
,这个Result
中包含了Response
的实例
5、自定义Converter
本节的内容是教大家实现在一简易的Converter
,这里以返回格式为Call<String>
为例。
在此之前先了解一下Converter接口及其作用:
public interface Converter<F, T> { // 实现从 F(rom) 到 T(o)的转换 T convert(F value) throws IOException; // 用于向Retrofit提供相应Converter的工厂 abstract class Factory { // 这里创建从ResponseBody其它类型的Converter,如果不能处理返回null // 主要用于对响应体的处理 public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } // 在这里创建 从自定类型到ResponseBody 的Converter,不能处理就返回null, // 主要用于对Part、PartMap、Body注解的处理 public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } // 这里用于对Field、FieldMap、Header、Path、Query、QueryMap注解的处理 // Retrfofit对于上面的几个注解默认使用的是调用toString方法 public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } }}
我们要想从Call<ResponseBody>
转换为 Call<String>
那么对应的F
和T
则分别对应ResponseBody
和String
,我们定义一个StringConverter
并实现Converter
接口。
public static class StringConverter implements Converter<ResponseBody, String> { public static final StringConverter INSTANCE = new StringConverter(); @Override public String convert(ResponseBody value) throws IOException { return value.string(); }}
我们需要一个Fractory
来向Retrofit
注册StringConverter
public static class StringConverterFactory extends Converter.Factory { public static final StringConverterFactory INSTANCE = new StringConverterFactory(); public static StringConverterFactory create() { return INSTANCE; } // 我们只关实现从ResponseBody 到 String 的转换,所以其它方法可不覆盖 @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { if (type == String.class) { return StringConverter.INSTANCE; } //其它类型我们不处理,返回null就行 return null; }}
使用Retrofit.Builder.addConverterFactory
向Retrofit
注册我们StringConverterFactory
:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") // 如是有Gson这类的Converter 一定要放在其它前面 .addConverterFactory(StringConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();
注:addConverterFactory
是有先后顺序的,如果有多个ConverterFactory
都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory
是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配。
只要返回值类型的泛型参数就会由我们的StringConverter
处理,不管是Call<String>
还是Observable<String>
6、自定义CallAdapter
本节将介绍如何自定一个CallAdapter
,并验证是否所有的String都会使用我们第5节中自定义的Converter
。
先看一下CallAdapter接口定义及各方法的作用:
public interface CallAdapter<T> { // 直正数据的类型 如Call<T> 中的 T // 这个 T 会作为Converter.Factory.responseBodyConverter 的第一个参数 // 可以参照上面的自定义Converter Type responseType(); <R> T adapt(Call<R> call); // 用于向Retrofit提供CallAdapter的工厂类 abstract class Factory { // 在这个方法中判断是否是我们支持的类型,returnType 即Call<Requestbody>和`Observable<Requestbody>` // RxJavaCallAdapterFactory 就是判断returnType是不是Observable<?> 类型 // 不支持时返回null public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit); // 用于获取泛型的参数 如 Call<Requestbody> 中 Requestbody protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } // 用于获取泛型的原始类型 如 Call<Requestbody> 中的 Call // 上面的get方法需要使用该方法。 protected static Class<?> getRawType(Type type) { return Utils.getRawType(type); } }
了解了CallAdapter
的结构和其作用之后,我们就可以开始自定义我们的CallAdapter
了,本节以CustomCall<String>
为例。
在此我们需要定义一个CustomCall
,不过这里的CustomCall
作为演示只是对Call
的一个包装,并没有实际的用途。
public static class CustomCall<R> { public final Call<R> call; public CustomCall(Call<R> call) { this.call = call; } public R get() throws IOException { return call.execute().body(); }}
有了CustomCall
,我们还需要一个CustomCallAdapter
来实现 Call<T>
到 CustomCall<T>
的转换,这里需要注意的是最后的泛型,是我们要返回的类型。
public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> { private final Type responseType; // 下面的 responseType 方法需要数据的类型 CustomCallAdapter(Type responseType) { this.responseType = responseType; } @Override public Type responseType() { return responseType; } @Override public <R> CustomCall<R> adapt(Call<R> call) { // 由 CustomCall 决定如何使用 return new CustomCall<>(call); }}
提供一个CustomCallAdapterFactory
用于向Retrofit
提供CustomCallAdapter
:
public static class CustomCallAdapterFactory extends CallAdapter.Factory { public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory(); @Override public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { // 获取原始类型 Class<?> rawType = getRawType(returnType); // 返回值必须是CustomCall并且带有泛型 if (rawType == CustomCall.class && returnType instanceof ParameterizedType) { Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType); return new CustomCallAdapter(callReturnType); } return null; }}
使用addCallAdapterFactory
向Retrofit
注册CustomCallAdapterFactory
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:4567/") .addConverterFactory(Example09.StringConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE) .build();
注: addCallAdapterFactory
与addConverterFactory
同理,也有先后顺序。
7、其它说明
7.1 Retrofit.Builder
前面用到了 Retrofit.Builder
中的baseUrl
、addCallAdapterFactory
、addConverterFactory
、build
方法,还有callbackExecutor
、callFactory
、client
、validateEagerly
这四个方法没有用到,这里简单的介绍一下。
7.2 Retrofit的Url组合规则
- 如果你在注解中提供的url是完整的url,则url将作为请求的url。
- 如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值
- 如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值
7.3 Retrofit提供的Converter
7.4 Retrofit提供的CallAdapter:
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- Retrofit
- retrofit
- Retrofit
- retrofit
- C++模板机制
- Azure internal Load Balancer是怎么工作的
- 保证分布式系统数据一致性的6种方案
- 算法谜题137 跳跃成对2
- Swift中的数组使用
- Retrofit
- HTML5 2 拖放
- linux下编程实现GPS数据获取与解析
- 深入学习Hibernate4_07使用二级缓存
- caffe:网络结构可视化工具
- mysql插入操作中旧数据处理(ignore和on duplicate key update)
- php的9种魔术方法
- leetcode
- SheetJS/js-xlsx的小demo