前言
Retrofit 是一个赞誉无数的 Android 网络框架,它的优点和强大无需我们多说。今天我们主要通过实现一个简单的 Retrofit 框架(只能够进行 Get 请求)来了解它内部核心运作机制。
注解抽取
Retrofit 中有多达 25 个注解,由于我们只是实现 Retrofit 的 Get 功能,因此我们只用抽取其中的 Get 、Field 二个注解接口即可。
**Get.java **
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
**Field.java **
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Field {
String value();
}
实现 Retrofit 类
Retrofit 类是 Retrofit 框架最核心的工作类,它通过 Builder 模式来构建各种请求参数。它的作用非常简单,通过动态代理来获取到我们自定义的方法接口实例,然后在解析该方法接口的注解参数以后,再调用Okhttp框架去执行网络请求。
Retrofit.java
public final class Retrofit {
/**
* 该map的作用用于缓存Retrofit解析以后的方法,因为注解解析是相对耗时的。
*/
private final Map serviceMethodCache = new ConcurrentHashMap<>();
private String baseUrl;
public Retrofit(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getBaseUrl() {
return baseUrl;
}
/**
* 框架核心方法,用于构建接口实例
*
* @param service
* @param
* @return
*/
public T create(final Class service) {
// 动态代理
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[]{service},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1 验证是否是接口
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
//2 进行构建接口实例,同时进行方法注解、方法参数注解解析,这些参数解析完成以后,我们就可以把注解解析后的url进行拼接
ServiceMethod serviceMethod =
loadServiceMethod(method, args);
//3 调用 http 框架执行网络请求
OkHttpCall okHttpCall = new OkHttpCall(serviceMethod);
return okHttpCall;
}
}
);
}
/**
* 进行构建接口实例
*
* @param method 调用的接口方法
* @param args 方法参数内容
* @return
*/
private ServiceMethod loadServiceMethod(Method method, Object[] args) {
ServiceMethod result = serviceMethodCache.get(method);
if (result != null)
return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method, args).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
/**
* Retrofit 的构建类,除了 baseUrl 以外,其他的配置参数都是可选的,在这里我们只添加 baseUrl。
*/
public static final class Builder {
private String baseUrl;
public Builder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
return new Retrofit(baseUrl);
}
}
}
方法、方法参数注解解析
在 Retrofit 类中的 create 方法中,我们提到Retrofit 的工作原理分为三步:
- 一 校验接口
- 二 构建接口实例,解析注解内容
- 三 拼接 Url,执行网络请求
接下来,我们来进行编写第二步。
ServiceMethod.java
public class ServiceMethod {
private Builder mBuilder;
public ServiceMethod(Builder builder) {
this.mBuilder = builder;
}
/**
* 获取具体网络请求类型
*
* @return methodName
*/
public String getMethodName() {
return mBuilder.methodName;
}
/**
* @return 请求url,这里GET请求我们需要进行url参数拼接,在实际源码中我们其实是在Okhttpcall中调用requestbuilder进行统一拼接的。
*/
public String getBaseUrl() {
if (mBuilder.methodName.equals("GET")) {
StringBuffer sb = new StringBuffer();
sb.append(mBuilder.retrofit.getBaseUrl())
.append(mBuilder.relativeUrl);
Map parameterMap = getParameter();
if (parameterMap != null) {
Set keySet = parameterMap.keySet();
if (keySet.size() > 0) {
sb.append("?");
}
for (String key : keySet) {
sb.append(key).append("=").append(parameterMap.get(key)).append("&");
}
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
return mBuilder.retrofit.getBaseUrl();
}
public Map getParameter() {
return mBuilder.parameterMap;
}
/**
* 用于解析注解
*/
static final class Builder {
final Retrofit retrofit;
final Method method;
final Annotation[] methodAnnotations;
final Annotation[][] parameterAnnotationsArray;
private Map parameterMap = new HashMap<>();
private Object[] args;
private String methodName;
private String relativeUrl;
Builder(Retrofit retrofit, Method method, Object[] args) {
this.retrofit = retrofit;
this.method = method;
// 方法注解列表
this.methodAnnotations = method.getAnnotations();
// 方法参数注解列表
this.parameterAnnotationsArray = method.getParameterAnnotations();
// 方法参数内容列表
this.args = args;
}
public ServiceMethod build() {
// 遍历方法注解
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
// 遍历方法参数注解
int parameterCount = parameterAnnotationsArray.length;
for (int p = 0; p < parameterCount; p++) {
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
parseParameter(p, parameterAnnotations);
}
return new ServiceMethod(this);
}
/**
* 解析方法注解,获取方法注解中的值,用于后续拼接url地址
*
* @param annotation
*/
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value());
}
}
private void parseHttpMethodAndPath(String httpMethod, String value) {
if (httpMethod.equals("GET")) {
methodName = "GET";
this.relativeUrl = value;
}
}
/**
* 解析方法参数注解
*
* @param p 方法参数值的index
* @param parameterAnnotations 方法参数注解数组
*/
private void parseParameter(int p, Annotation[] parameterAnnotations) {
// 方法参数值
Object value = args[p];
// 遍历参数注解
for (Annotation annotation : parameterAnnotations) {
//首先需要判断参数注解类型
if (annotation instanceof Field) {
Field field = (Field) annotation;
// 参数名称(接口参数名称)
String key = field.value();
parameterMap.put(key, value);
}
}
}
}
}
执行网络请求
在解析了注解内容,拼接了 Url 地址以后,我们的简单框架就只剩下执行网络请求这一步了,这一步难度不大,所以直接上代码:
Call.java
/**
* 网络请求接口
*/
public interface Call extends Cloneable {
/**
* 同步请求
* @return
* @throws IOException
*/
String execute() throws IOException;
/**
* 异步请求
* @param callback
*/
void enqueue(Callback callback);
}
OkHttpCall.java
public class OkHttpCall implements Call {
private ServiceMethod mServiceMethod;
private static OkHttpClient client;
static {
client = new OkHttpClient();
}
public OkHttpCall(ServiceMethod serviceMethod) {
this.mServiceMethod = serviceMethod;
}
@Override
public String execute() throws IOException {
if (mServiceMethod.getMethodName().equals("GET")) {
Request request = new Request.Builder()
.url(mServiceMethod.getBaseUrl())
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
return null;
}
@Override
public void enqueue(final Callback callback) {
if (mServiceMethod.getMethodName().equals("GET")) {
Request request = new Request.Builder()
.url(mServiceMethod.getBaseUrl())
.build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
callback.onFailure(e);
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
callback.onResponse(response.body().string());
}
});
}
}
}
测试
我们的简单框架到了这里就已经编写完成了,我们开始进行测试,看看有无问题。
请求接口地址
AppService.java
public interface AppService {
//http://android.secoo.com/appservice/cartAndBrand.action?v=2.0
@GET("/appservice/cartAndBrand.action")
OkHttpCall getCartAndBrand(@Field("v") String v);
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://android.secoo.com").build();
AppService appService = retrofit.create(AppService.class);
final OkHttpCall okHttpCall = appService.getCartAndBrand("2.0");
okHttpCall.enqueue(new Callback() {
@Override
public void onResponse(String response) {
Log.e("response data",response);
}
@Override
public void onFailure(Throwable t) {
Log.e("error",t.getMessage());
}
});
}
}
按照代码执行一下,最后控制台日志成功输出 Json 字符串,代表我们的测试是成功的。