一、Retrofit 基本使用
这里只实现最基本的使用,适配器和转换器等并未实现。
基本使用方法分两步,先定义 Api 接口:
public interface Api {
@GET("/v3/weather/weatherInfo")
Call getWeather(@Query("city") String city, @Query("key") String key);
@POST("/v3/weather/weatherInfo")
Call postWeather(@Field("city") String city, @Field("key") String key);
}
分别使用 POST 和 GET 请求去获取天气数据,这里使用的是高德地图的 API。
接下来创建 Retrofit 对象,获取 Api 实例,用异步方式执行 Api 中的请求:
private void request() {
// 1.生成 SimpleRetrofit 的实例
SimpleRetrofit retrofit = new SimpleRetrofit.Builder().baseUrl("https://restapi.amap.com").build();
// 2.使用 SimpleRetrofit 为 Api 接口生成一个动态代理
Api api = retrofit.create(Api.class);
// 3.调用 Api 接口中的方法,拿到一个 OkHttp 中的任务对象 call
Call call = api.getWeather("北京", "13cb58f5884f9749287abbead9c658f2");
// 4.异步方式执行 call,即将其入队
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
}
二、手写代码
写代码之前至少要清楚 Retrofit 其实是因为封装了 OkHttp 才拥有网络访问能力的,实际执行网络请求的是 OkHttp。Retrofit 要做的是为网络请求接口生成动态代理对象,并在请求方法被调用时,在动态代理的 InvocationHandler 中解析注解,把要使用的网络请求方法和参数解析出来生成 OkHttp 的 Request 对象,最后由 OkHttp 发送请求。
2.1 注解定义
我们要实现一个极简版的 Retrofit(只是为了更好的理解 Retrofit 框架),请求方法只实现了 GET、POST,参数注解只支持 @Query、@Field:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GET {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface POST {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Query {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Field {
String value();
}
2.2 SimpleRetrofit 的初始化
Retrofit 使用 Builder 创建其对象:
public class SimpleRetrofit {
HttpUrl baseUrl;
Call.Factory callFactory;
public SimpleRetrofit(Builder builder) {
this.baseUrl = builder.baseUrl;
this.callFactory = builder.callFactory;
}
static class Builder {
HttpUrl baseUrl;
Call.Factory callFactory;
Builder baseUrl(String baseUrl) {
this.baseUrl = HttpUrl.parse(baseUrl);
return this;
}
public SimpleRetrofit build() {
// 先做参数校验
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
if (callFactory == null) {
callFactory = new OkHttpClient();
}
return new SimpleRetrofit(this);
}
}
}
Call.Factory 是生成 Call 对象的工厂,其唯一实现类为 OkHttpClient,调用其 newCall(Request) 方法可以生成 Call 对象,最后要通过这个方法把我们解析出来的数据封装在 Request 中以生成 Call 对象。
2.3 创建动态代理对象
SimpleRetrofit 对象生成后,在 create() 内生成动态代理对象:
private final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},
new InvocationHandler() {
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
ServiceMethod serviceMethod = loadServiceMethod(method);
return serviceMethod.invoke(args);
}
});
}
/**
* DLC 方式获取 ServiceMethod,如果没有解析过就解析该方法
* @param method 动态代理执行的接口方法
* @return 解析后的方法对象
*/
private ServiceMethod loadServiceMethod(Method method) {
// 先不加锁,避免性能损失
ServiceMethod serviceMethod = serviceMethodCache.get(method);
if (serviceMethod != null) return serviceMethod;
// 避免多线程下重复解析
synchronized (serviceMethodCache) {
serviceMethod = serviceMethodCache.get(method);
if (serviceMethod == null) {
// 在 build() 中解析方法和参数
serviceMethod = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, serviceMethod);
}
}
return serviceMethod;
}
2.4 解析方法和参数
在 ServiceMethod 的 Builder 中解析方法和参数注解:
public class ServiceMethod {
private final String httpMethod;
private final Call.Factory callFactory;
private final HttpUrl baseUrl;
private final String relativeUrl;
private final ParameterHandler[] parameterHandlers;
private FormBody.Builder formBuilder;
private FormBody formBody;
private HttpUrl.Builder urlBuilder;
public ServiceMethod(Builder builder) {
baseUrl = builder.retrofit.baseUrl;
callFactory = builder.retrofit.callFactory;
httpMethod = builder.httpMethod;
relativeUrl = builder.relativeUrl;
parameterHandlers = builder.parameterHandlers;
boolean hasBody = builder.hasBody;
// 如果有请求体,创建一个 OkHttp 请求体对象
if (hasBody) {
formBuilder = new FormBody.Builder();
}
}
static class Builder {
private final SimpleRetrofit retrofit;
private final Method method;
private final Annotation[] annotations;
// 方法上有 n 个参数,每个参数又有 m 个注解,用一个 nxm 的数组保存
private final Annotation[][] parameterAnnotations;
String httpMethod;
boolean hasBody;
String relativeUrl;
ParameterHandler[] parameterHandlers;
public Builder(SimpleRetrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
annotations = method.getAnnotations();
parameterAnnotations = method.getParameterAnnotations();
}
public ServiceMethod build() {
// 解析方法上的注解
for (Annotation annotation : annotations) {
if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
}
}
// 解析方法参数上的所有注解,把注解值存入 ParameterHandler[] 中
int length = parameterAnnotations.length; // 方法上的参数个数
parameterHandlers = new ParameterHandler[length];
for (int i = 0; i < length; i++) {
// 一个参数上的所有注解
Annotation[] annotations = parameterAnnotations[i];
parameterHandlers[i] = parseParameter(annotations);
}
return new ServiceMethod(this);
}
private ParameterHandler parseParameter(Annotation[] annotations) {
// 根据注解类型创建对应的 ParameterHandler
ParameterHandler result = null;
for (Annotation annotation : annotations) {
ParameterHandler annotationAction = parseParameterAction(annotation, annotations);
// 如果当前检查的注解并不是我们能处理的,就继续遍历下一个
if (annotationAction == null) {
continue;
}
// 如果 result 不为 null 说明之前遍历时已经找到了 SimpleRetrofit 能处理的注解
// 不允许一个参数上被多个 SimpleRetrofit 的参数注解标注,抛异常
if (result != null) {
throw new IllegalArgumentException("Multiple Retrofit annotations found, only one allowed.");
}
result = annotationAction;
}
// 遍历完都没找到说明这个参数没有被 SimpleRetrofit 注解标注,不应该被检查
if (result == null) {
throw new IllegalArgumentException("No Retrofit annotation found.");
}
return result;
}
private ParameterHandler parseParameterAction(Annotation annotation, Annotation[] annotations) {
if (annotation instanceof Query) {
String key = ((Query) annotation).value();
return new ParameterHandler.QueryParameterHandler(key);
} else if (annotation instanceof Field) {
String key = ((Field) annotation).value();
return new ParameterHandler.FieldParameterHandler(key);
}
return null;
}
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
// 规定一个方法上只能有一个 httpMethod 注解,否则抛出异常
if (this.httpMethod != null) {
String message = String.format("Only one HTTP method is allowed. Found: %s and %s.",
this.httpMethod, httpMethod);
throw new IllegalArgumentException(message);
}
this.httpMethod = httpMethod;
this.hasBody = hasBody;
if (value == null) {
return;
}
this.relativeUrl = value;
}
}
}
解析方法注解主要是为了获取使用哪种 HTTP 方法(GET、POST)、是否有请求体以及相对地址;解析方法参数注解是为了把被 @Field 或 @Query 注解的参数的值,即网络请求的 key 保存在 ParameterHandler[] 中。比如说对于 getWeather():
@GET("/v3/weather/weatherInfo")
Call getWeather(@Query("city") String city, @Query("key") String key);
@Query 注解的值分别为 city、key,那么就把 city 和 key 分别传入 ParameterHandler[] 中保存,而这两个 key 对应的 value 会在调用 ServiceMethod 的 invoke() 方法时传入。
ParameterHandler 的作用是保存网络请求的 key,并把 key-value 回调给 ServiceMethod:
public abstract class ParameterHandler {
abstract void apply(ServiceMethod serviceMethod, String value);
static class QueryParameterHandler extends ParameterHandler {
String key;
public QueryParameterHandler(String key) {
this.key = key;
}
@Override
void apply(ServiceMethod serviceMethod, String value) {
serviceMethod.addQueryParameter(key, value);
}
}
static class FieldParameterHandler extends ParameterHandler {
String key;
public FieldParameterHandler(String key) {
this.key = key;
}
@Override
void apply(ServiceMethod serviceMethod, String value) {
serviceMethod.addFieldParameter(key, value);
}
}
}
回调方法一个是处理 GET 请求的,一个是处理 POST 请求的:
// get 请求,把 key-value 拼接到 url 中
public void addQueryParameter(String key, String value) {
if (urlBuilder == null) {
urlBuilder = baseUrl.newBuilder(relativeUrl);
}
urlBuilder.addQueryParameter(key, value);
}
// post 请求,把 key-value 放到请求体中
public void addFieldParameter(String key, String value) {
formBuilder.add(key, value);
}
2.5 将解析结果拼接成网络请求并发送
最后在 invoke() 中生成 OkHttp 的 Request 对象并调用 CallFactory 的 newCall(Request) 生成 Call:
public Object invoke(Object[] args) {
for (int i = 0; i < parameterHandlers.length; i++) {
ParameterHandler parameterHandler = parameterHandlers[i];
parameterHandler.apply(this, args[i].toString());
}
if (urlBuilder == null) {
urlBuilder = baseUrl.newBuilder(relativeUrl);
}
HttpUrl httpUrl = urlBuilder.build();
if (formBuilder != null) {
formBody = formBuilder.build();
}
Request request = new Request.Builder().url(httpUrl).method(httpMethod, formBody).build();
return callFactory.newCall(request);
}
使用者拿到 Call 之后就可以发送该请求并获取结果了。