Retrofit详解:基本API + 自定义Converter
来源:互联网 发布:程序员远程工作招聘 编辑:程序博客网 时间:2024/06/06 03:40
本文是围绕Retrofit2.1版本来讲的,一些老版本API和新版本不一样了,就不讲了,与时俱进。
Retrofit是Square公司开发的,大神是JakeWharton。官网对其描述是:
a type-safe REST client for Android and Java.
你可以使用注解去描述一个HTTP请求、Url参数替换、查询参数。另外也支持文件上传等。
如何引用:
dependencies { // Retrofit & OkHttp compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0'}
第一个是Retrofit库,第二个是Gson库(用来将string转换成json的库),在retrofit中默认使用gson这个converter来转换response,后面会解释converter。
开始使用:
例如我现在要发出一个请求,url如下:
http://10.240.88.201:8080/repos/{owner}/contributors?loginname={name}
在后面路径中有{owner},这是一个替换位,这个值非固定,传不同的owner则返回不同的值。返回的是一个jsonArray,并且是一个get请求。在参数中,有一对键值对,{name}也是替换位,传入不同参数则返回不同值。
那看下retrofit怎么解决这个问题:
public class ServiceGenerator { public static final String API_BASE_URL = "http://10.240.88.201:8080/"; private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); private static Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(API_BASE_URL) .addConverterFactory(GsonConverterFactory.create()); public static <S> S createService(Class<S> serviceClass) { Retrofit retrofit = builder.client(httpClient.build()).build(); return retrofit.create(serviceClass); }}
public interface RequestClient { @GET("/repos/{owner}/contributors") Call<List<Contributor>> contributors( @Path("owner") String owner, @Query("loginname") String loginname ); class Contributor { String login; int contributions; }}
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //发起网络请求 RequestClient client = ServiceGenerator.createService(RequestClient.class); Call<List<Contributor>> call = client.contributors("company", "lxx"); call.enqueue(new Callback<List<Contributor>>() { @Override public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) { List<Contributor> contributors = response.body(); Toast.makeText(MainActivity.this, contributors.size() + "", Toast.LENGTH_LONG).show(); } @Override public void onFailure(Call<List<Contributor>> call, Throwable t) { Log.e("tag", "------------onFailure:"); t.printStackTrace(); } }); }}
如果服务器返回的值为:
[ { "login":"ssss", "contributions":1 }, { "login":"ssss", "contributions":2 }]
则在界面上会弹出toast,显示2。
现在来解释下,在ServiceGenerator中使用Builder创建一个新的REST client,并且基于API_BASE_URL。Retrofit其实是对OkHttp库的封装,简化里面参数的封装以及数据转换,所以在创建Retrofit的时候,还需要传入OkHttpClient。如对OkHTTP不熟悉的话,可以看OkHttp完全解析
和OkHttp不同的是,Retrofit需要定义一个借口,用来返回我们的Call对象,这个Call对象可不是OkHttp中的Call,而是定义在Retrofit中的Call。
这里我们定义的是RequestClient这个接口,在这个接口中定义了一个函数contributors。看下修饰该函数的注解,首先用了注解@GET来标识这是一个GET请求,当然你也可以使用@POST。在@GET括号中的是该请求的具体路径,如果在路径中出现{xxx}这样的格式,则说明该处为替换位,可以在函数参数中看到,有注解@Path(“owner”)来标识该处是用来替换的。
而@Query参数是用来标示参数键值对的。
在MainActivity中我们调用了该接口的contributor(“company”,”lxx”);,那么我们发出请求的url便是:
http://10.240.88.201:8080/repos/company/contributors?loginname=lxx
这样一看Retrofit是不是很方便,MainActivity在创建了Call之后,就会去请求,调用的方法和OkHttp的Call一样。其实我们仔细看Retrofit.Call和OkHttp.Call接口,其实是一样的,那为什么要搞两个呢?我猜想是和后面的切面编程有关系。
我们继续回到ServiceGenerator中的createService方法,在该方法中创建了一个retrofit后,调用了create(Class class)的方法,具体进去看看:
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
该方法前面是对该类进行基本检查,检查是否是接口,并且该接口不能继承自其它接口。然后就是调用了Proxy(动态代理,运用了切面编程的思想,不懂可以看如下两篇文章:Java的动态代理(dynamic proxy) 和 深入理解Java Proxy机制)
接下来再看下创建ServiceMethod,使用method初始化该ServiceMethod,method变量就是我们外面调用的contributors这个方法,而loadServiceMethod就是获取该method的注解、参数类型、参数的注解。
看代码:
ServiceMethod loadServiceMethod(Method method) { ServiceMethod result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = new ServiceMethod.Builder(this, method).build(); serviceMethodCache.put(method, result); } } return result; } public Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations(); }
所以我们外面定义的接口RequestClient只是用来获取这三样而已,里面不用方法则不用实现。而切面编程中,我们返回了一个实现Retrofit.Call接口的实例Retrofit.OkHttpCall。在Retrofit.OkHttpCall中封装了请求OkHttp的操作。所以这就是需要两个Call的原因,Retrofit.Call封装了OkHttp.Call的一些操作。
讲完这些对Retrofit应该有个大概的了解了。那么再讲讲Converter,其实就是对数据的转换。
最常见的就是json的转换,比如说我拿到一个json的字符串:
[ { "login":"ssss", "contributions":1 }, { "login":"ssss", "contributions":2 }]
我希望我只用定义
class Contributor { String login; int contributions; }
转换库可以在拿到服务器数据后自动帮我填充到类的变量上去。
这样类似的库有很多,Retrofit提供如下的库:
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
具体功能可以自己去查。那么我们可以自定义这样的converter么?当然可以!
看下Retrofit提供的接口:
public interface Converter<F, T> { T convert(F value) throws IOException; /** Creates {@link Converter} instances based on a type and target usage. */ abstract class Factory { /** * Returns a {@link Converter} for converting an HTTP response body to {@code type}, or null if * {@code type} cannot be handled by this factory. This is used to create converters for * response types such as {@code SimpleResponse} from a {@code Call<SimpleResponse>} * declaration. */ public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } /** * Returns a {@link Converter} for converting {@code type} to an HTTP request body, or null if * {@code type} cannot be handled by this factory. This is used to create converters for types * specified by {@link Body @Body}, {@link Part @Part}, and {@link PartMap @PartMap} * values. */ public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } /** * Returns a {@link Converter} for converting {@code type} to a {@link String}, or null if * {@code type} cannot be handled by this factory. This is used to create converters for types * specified by {@link Field @Field}, {@link FieldMap @FieldMap} values, * {@link Header @Header}, {@link HeaderMap @HeaderMap}, {@link Path @Path}, * {@link Query @Query}, and {@link QueryMap @QueryMap} values. */ public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } }}
responseBodyConverter就是对返回数据做转换,requestBodyConverter是对请求数据做转换。(看了这一对函数,其实觉得有点像OkHttp拦截器,也是分请求前和请求后的。)
举个具体的例子:如果服务器返回的数据都是使用Base64解密,那么对于客户端拿到数据之前应该先解密再使用,那么如何用converter来实现呢?
上代码:
public class Base64GsonConverterFactory extends Converter.Factory { public static Base64GsonConverterFactory create() { return create(new Gson()); } public static Base64GsonConverterFactory create(Gson gson) { return new Base64GsonConverterFactory(gson); } private final Gson gson; private Base64GsonConverterFactory(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); this.gson = gson; } @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new Base64GsonBodyConverter<>(adapter); } private static class Base64GsonBodyConverter<T> implements Converter<ResponseBody, T> { private final TypeAdapter<T> adapter; Base64GsonBodyConverter(TypeAdapter<T> adapter) { this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { String temp = value.string(); temp = new String(Base64.decode(temp, Base64.DEFAULT)); return adapter.fromJson(temp); } }}
该Factory继承自Converter.Factory,只重写了responseBodyConverter,说明只对返回的数据进行转换,请求前不转换。在处理responseBody的时候,使用Base64GsonBodyConverter来转换,进行base64解码。当然解码后还支持gson的转换。
从此转换数据是不是变的很方便?如果要使用这些converter的话,只用add进去就好了。
private static Retrofit.Builder builder = new Retrofit.Builder() .baseUrl(API_BASE_URL) .addConverterFactory(Base64GsonConverterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create());
那么问题来了!这么多的converter,我返回数据只有一个,不可能说每个converter都处理啊,这和RxJava可不一样,不是一个事件流处理,一个response只能被一个converter处理,那么那个来处理呢?
在Retrofit中维护一个converters的列表,记录着所有支持的converter。还记得刚刚的这个方法么?
@Overridepublic Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new Base64GsonBodyConverter<>(adapter);}
在该方法中的第一个参数会返回该请求需要返回的类型,也就是刚刚我们说的Retrofit.Call<T>
,也就是T类型,每个converter可以只处理自己想要处理的T的类型,举个例子,看下ScalarsConverterFactory这个converter处理的类型:
public final class ScalarsConverterFactory extends Converter.Factory { public static ScalarsConverterFactory create() { return new ScalarsConverterFactory(); } private ScalarsConverterFactory() { } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { if (type == String.class || type == boolean.class || type == Boolean.class || type == byte.class || type == Byte.class || type == char.class || type == Character.class || type == double.class || type == Double.class || type == float.class || type == Float.class || type == int.class || type == Integer.class || type == long.class || type == Long.class || type == short.class || type == Short.class) { return ScalarRequestBodyConverter.INSTANCE; } return null; } @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { if (type == String.class) { return StringResponseBodyConverter.INSTANCE; } if (type == Boolean.class || type == boolean.class) { return BooleanResponseBodyConverter.INSTANCE; } if (type == Byte.class || type == byte.class) { return ByteResponseBodyConverter.INSTANCE; } if (type == Character.class || type == char.class) { return CharacterResponseBodyConverter.INSTANCE; } if (type == Double.class || type == double.class) { return DoubleResponseBodyConverter.INSTANCE; } if (type == Float.class || type == float.class) { return FloatResponseBodyConverter.INSTANCE; } if (type == Integer.class || type == int.class) { return IntegerResponseBodyConverter.INSTANCE; } if (type == Long.class || type == long.class) { return LongResponseBodyConverter.INSTANCE; } if (type == Short.class || type == short.class) { return ShortResponseBodyConverter.INSTANCE; } return null; }}
这个ScalarsConverterFactory只处理String、boolean这种基本类型的数据,而其他类型的response都不会处理,直接返回null。
当一个response返回后,retrofit会遍历converters,并将response的type传给converter,询问该converter是否要处理,如果返回的不为空,则代表可以处理,不再遍历列表。如果遍历完都没有一个converter愿意处理的话,也会提示。所以添加converter的顺序很重要,像GsonConverterFactory这种是所有类型都会处理的,要放在后面添加,否则如果直接返回“string”的话,不为json,解析会出错。
所以小集合的converter放前面,大集合的converter放后面。
Retrofit官网:
- Retrofit 2 — Adding & Customizing the Gson Converter
- Retrofit — Getting Started and Create an Android Client
- https://github.com/square/retrofit/wiki/Converters
上面讲了基本的一些用法和概念,下面讲些其他的小点:
- Multiple Query Parameters of Same Name:如果我们要发出这样的url(https://api.example.com/tasks?id=123&id=124&id=125),参数中有多个名字相同的参数,该怎么做?
public interface TaskService { @GET("/tasks") Call<List<Task>> getTask(@Query("id") List<Long> taskIds);}
- Send Objects in Request Body:使用post请求发送请求时,需将参数放在body中传输。刚刚我们前面讲的都是get请求,按照HTTP协议规范,GET请求的参数应该放在url中,而不是body中,GET请求的body为空。所以我们前面@query是将参数放在url中,那现在如果post要传递参数则使用@Body注解来,例如:
public interface TaskService { @POST("/tasks") Call<Task> createTask(@Body Task task);}public class Task { private long id; private String text; public Task(long id, String text) { this.id = id; this.text = text; }}Task task = new Task(1, "my task title"); Call<Task> call = taskService.createTask(task); call.enqueue(new Callback<Task>() {});
- Retrofit详解:基本API + 自定义Converter
- Retrofit 2.0 自定义Converter
- Retrofit自定义Converter
- Retrofit自定义Converter步骤
- Android Retrofit 2.0自定义Converter(JSONObject Converter)
- Retrofit使用之自定义Converter
- Retrofit自定义Converter之StringConverterFactory
- 自定义Retrofit转化器Converter
- Retrofit 2.0 自定义Converter补充篇
- Retrofit自定义Converter数据异常处理攻略
- Retrofit自定义Converter,获取原始请求数据,实现自定义解析
- Retrofit 2 之自定义Converter实现加密解密
- Retrofit 2 之自定义Converter实现加密解密
- 自定义Retrofit的Converter使其去除json中非法字符
- android retrofit 实战自定义converter,解决相同接口返回不同数据的问题
- Android 使用Retrofit自定义Converter解析相同接口返回不同数据
- JSF 自定义Converter疑惑
- 自定义Struts1转换器Converter
- 链表的一系列操作(创建,插入,删除,从前到后,从后到前遍历整个链表)
- python字符串前'r'的用法
- 设计模式经典---六大设计原则
- 代码查看器
- linux配置sftp用户的chroot步骤(用户的sftp根目录)
- Retrofit详解:基本API + 自定义Converter
- 2016.11.28 jdbc学习
- 探讨Java中最常见的十道面试题(超经典)
- Bootstrap Table 插件 触发行点击事件
- 知乎live,linkedin数据总监李玥
- Fork/Join-Java并行计算框架
- iptables中用hashlimit来限速
- OpenStack-M版(Mitaka)搭建- – -镜像服务(Glance)篇
- 【TensorFlow】tf.nn.softmax_cross_entropy_with_logits的用法