本文针对的是Retrofit 2.1.0
一、Retrofit简介
如今开发一个Android应用,几乎都需要和网络打交道,最常见的就是通过http协议来请求服务器数据或者是给服务器发送数据,虽然Android本身也提供了HttpUrlConnection和Volley来发起http请求,功能上其实也能满足,但鉴于Retrofit+OkHttp的组合如此流行,所以几个项目下来,都是用的这一套。
Retrofit是一个针对Android和java的类型安全的、开源的http客户端,由大名鼎鼎的Square公司出品(另外还有:picasso、dagger等),通过注解的方式,让开发者定义一个接口方法来完成http请求(真正的请求其实是由OkHttp来完成的),至于这个过程是怎么实现的,后面会详细说。
相关链接:
二、使用
简单介绍下Retrofit的使用
- 定义一个接口:ApiService
public interface ApiService {
/**
* 获取协议内容
*
* @return 此时返回的JsonResponse里面的body字段的类型为 AgreementResponseBody
*/
@GET("system/getAgreement")
Call<JsonResponse> getAgreement();
/**
* 获取点赞信息
* @param getApprovalInfoBean
* @return
*/
@POST("approval/getApproval")
Call<JsonResponse> getApprovalInfo(@Body GetApprovalInfoBean getApprovalInfoBean);
}
上面就定义了两个最常用的http的方法,GET请求和POST请求,可以看到,需要使用GET或者是POST只需要在方法上加上注解就可以(当然还有其它方法,像:DELETE等)。POST方法的话因为有请求体,所以会用@Body来注解,这里放的是我自定义的一个类(因为结合了fastjson使用)。两个方法的返回都是Call<T>的形式。
- Retrofit的相关配置及初始化
Retrofit使用了Builder模式,主要是做了一些配置,可以看到,实例化了一个OkHttpClient,因为要用它来进行实际的http请求。
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.readTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(9, TimeUnit.SECONDS);
//添加拦截器,保留一些调试时的日志
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
}
OkHttpClient okHttpClient = builder.build();
/**
* addConverterFactory,是为了对象的序列化和反序列化,一般就是使用json相关的工具。
* addCallAdapterFactory,是为了返回Call类型之外的其它类型
*/
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(FastJsonConverterFactory.create())
.build();
api = retrofit.create(ApiService.class);
- 发起请求
一般用的比较多的是异步请求,当然也可以使用同步请求。在Callback回调里面可以进行成功及失败,回调都是在主线程里面(在Android上使用,底层是通过Handler post到主线程)。
Call<JsonResponse> call = apiService.getApprovalInfo(getApprovalInfoBean);
requestList.add(call);
call.enqueue(new Callback<JsonResponse>() {
@Override
public void onResponse(Call<JsonResponse> call, Response<JsonResponse> response) {
}
@Override
public void onFailure(Call<JsonResponse> call, Throwable t) {
}
});
至此基本的用法就大概说完了,当然还有很多其它的用法,具体可以参考下官网。基本上Http的方法都支持。
三、源码分析
-
源码的包结构
http包:
http包里面放的都是一些自定义的注解类,平常开发者会用到的是:Call、CallAdapter、Callback、Converter、Retrofit
- 主要的几个类之间的关系
- Retrofit是如何把一个接口方法转变为http请求的吗
Retrofit里面用到了大量的反射、泛型、注解,还用到了动态代理(这是将接口方法转换为http请求的关键,毕竟我们没有看到 Call<JsonResponse> getAgreement();方法的实现)。
可能自己之前写的代码比较简单,考虑的方面也比较单一,反射、注解之类的东西用的也比较少。抛开性能不说(反射会降低性能),这些功能(或者说是语法吧)能够实现一些“比较特殊”的功能,还有注解、泛型这些可以给让代码变得更加优雅,增加代码的可扩展性和动态性。感谢开源,让我们能够读到大神的源码。
关键点是这里,使用了java的动态代理,其实上面我们定义的接口方法getAgreement()真正的执行地方是下面invoke()方法里面,通过反射调用了。
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);
}
});
真正的方法调用:
return method.invoke(this, args);
return platform.invokeDefaultMethod(method, service, proxy, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
这里可以看到有三个return的地方,第一种情况是:
这里又有一个关键的类:ServiceMethod,关于ServiceMethod,请看下文的“4”。
- ServiceMethod分析(关键衔接点)
ServiceMethod就是具体来做相关的方法参数的解析(解析方法上面的注解)及解析方法的返回值。解析注解里面的值(http的参数)其实是通过正则表达式匹配来完成的。
static final String PARAM = "[a-zA-Z][a-zA-Z0-9_-]*";
static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");
static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);
ServiceMethod仍然采用了Builder模式(Retrofit源码里面可以看到大量使用Builder,对于有许多需要配置的参数的情况,这样写起来更优雅一点)。
最终的请求是由toRequest方法来完成:
/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
contentType, hasBody, isFormEncoded, isMultipart);
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
int argumentCount = args != null ? args.length : 0;
if (argumentCount != handlers.length) {
throw new IllegalArgumentException("Argument count (" + argumentCount
+ ") doesn't match expected count (" + handlers.length + ")");
}
for (int p = 0; p < argumentCount; p++) {
handlers[p].apply(requestBuilder, args[p]);
}
return requestBuilder.build();
}
返回的requestBuilder.build()产生了Request的实例,而Request是 OkHttp里面的类。由此可见,Retrofit只是封装了http的请求,最终的请求还是通过OkHttp来完成的。
- Call接口
可用于异步也可用于同步请求,异步的话就放到队列里,调用enqueue()方法;同步的话调用execute()方法,直接返回一个Response<T>对象。
- Callback接口
Call的实例调用enqueue()方法的参数,用于接收回调,包含两个回调方法:
- http层面上的正常响应,即网络正常
/**
* Invoked for a received HTTP response.
* <p>
* Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
* Call {@link Response#isSuccessful()} to determine if the response indicates success.
*/
void onResponse(Call<T> call, Response<T> response);
- http层面上的异常响应
/**
* Invoked when a network exception occurred talking to the server or when an unexpected
* exception occurred creating the request or processing the response.
*/
void onFailure(Call<T> call, Throwable t);
一般在正常响应里面需要判断下应用层面的情况,如:200、404等;判断完应用层面后需要再判断下自定义的情况,一般:刷新成功、刷新失败等。所以可以写一个抽象类实现Callback接口,在里面做一些自已的处理,避免写太多重复代码。
- Converter<F, T>接口
将F类型转换为T类型,主要用在转成json对象,如:addConverterFactory(FastJsonConverterFactory.create())。
包含了一个方法和一个抽象工厂类:
T convert(F value) throws IOException;
abstract class Factory
抽象工厂类主要有三种转换器方法:
public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
Annotation[] annotations, Retrofit retrofit) {
return null;
}
public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
public @Nullable Converter<?, String> stringConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
return null;
}
分别是将ResponseBody转成自己想要的类型,将自定义类型转为RequestBody,将http的参数转为字符串类型
跟这个相关的有一个自带的类:
final class BuiltInConverters extends Converter.Factory
- 重写了responseBodyConverter和requestBodyConverter这两个方法
- CallAdapter<R, T> 接口
这个接口常见的好像主要是为了配合Rxjava使用,因为还没有用过Rxjava,所以等后面用了再作更深入的分析。
主要用于将响应的类型R转换为自己想要的类型,用的比较多的是将响应转换为rxjava支持,会用到RxJava2CallAdapterFactory.create()。
在Retrofit.Builder#addCallAdapterFactory(Factory)里调用 ,一般在初始化里面。
包含了两个方法和一个内部的抽象类
Type responseType();
T adapt(Call<R> call);
abstract class Factory
抽象工厂方法里面包含了一个get()方法,返回一个CallAdapter类型;包含两个返回类型的方法:
/**
* Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
* example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}.
*/
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
/**
* Extract the raw class type from {@code type}. For example, the type representing
* {@code List<? extends Runnable>} returns {@code List.class}.
*/
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
- 关于http包里的自定义注解类
这里定义注解是为了后面可以解析注解得到http请求对应的方法及各种参数
- 方法注解:GET、POST、PUT、DELETE、PATCH、HTTP、HEAD、FormUrlEncoded、Headers、Multipart、OPTIONS、Streaming
- 参数注解:Body、Field、FieldMap、HeaderMap、Part、PartMap、Path、Query、QueryMap、Url
- Platform类(判断是属于哪个平台)
Retrofit的介绍说的就是可以在普通的java中使用,也可以在Android中使用,所以里面有一段代码用来判断当前代码是运行在哪个平台上。
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}
其实就是通过查找有没有平台相关的类来判断具体是哪个平台,像在Android上就找“android.os.Build”;普通java的话,就通过找“java.util.Optional”来判断;但是iOS查找“"org.robovm.apple.foundation.NSObject"”我就不是很明白了,iOS上也可以运行吗?
这个小技巧感觉可以学习一下。
四、总结
之前使用Retrofit的时候也只是参照官网的例子简单使用了一下,并没有去深究它内部的实现,最近在看自己写的代码的时候,发现每一次http请求都需要在回调里面做好多判断,如:在onResponse()里面要判断http的状态码,进一步又要判断自定义的状态,感觉好臃肿,于是就想到用抽象类来解决。最近刚好有点时间,就阅读了一下源码,发现收获还是挺多的。
- 通过注解的方式来完成http请求,对于我来说还算是比较新的。
- 给方法、参数加上自定义注解,来增加一些编译期和运行时的行为。
- 目前做项目的时候抛出异常的情况还比较少,而如果是设计一个库供他人使用,可能就得像Retrofit一样抛出一些异常,做一些参数检查,来增加代码的健壮性。
- 使用工厂模式隐藏创建过程的复杂度,
- 使用Builder模式来避免构造方法里面有一堆的参数。
- 使用泛型来增加代码的可扩展性,避免写一堆的样板代码。
第一次写博客,以前都是在有道云笔记里面记东西,虽然也记了挺多,但是都比较随意。这一次会想写有两个原因:第一、应工作室同学的邀请;第二、学Android也有一段时间了,也用了不少开源库,看过一部分源码,感觉需要好好总结一下,提升一下自己的技术水平。以前总是想着说自己的水平还不够高,还没到写博客的时候,但是现在想想,其实也没那么绝对,写出来是对自己的一个阶段性总结,对学过的东西的一个巩固,一开始可能写的不好,但写多了,慢慢总会有进步。之前看到过一句话“没看过优秀的源码,是不太容易写好优秀的代码的。”我对此还是比较认同的,开源的世界带来了如此丰富的资源,需要我们好好利用。
写了一些代码,也看过一些代码,感觉看源码就像在看书一样,也有结构、语法、先后顺序,都是要表达些什么。只不过文字和源代码是从不同方面来理解世界,从不同角度来构筑这个世界。我越发地相信,这个世界有很多东西是相通的,是有联系的。