篇章目标要点
Retrofit是目前最为流程的网络访问框架,其内部集成了OkHttp框架。Retrofit用法非常简单,本文目的是探究一下其工作原理,通过源码了解其是解析注解参数的过程,以及处理网络返回信息的过程。计划在下一篇文章阐述其如何整合OkHttp进行网络请求的过程,以及内部的拦截器的工作原理。
Retrofit源码
可以在Gitee上获取Retrofit源码,便于在本地阅读
git clone https://gitee.com/mirrors/retrofit.git
Retrofit基本用法
1.添加依赖
//retrofit网络框架依赖
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
2.定义请求任务接口
根据自己的任务类型制定相应的请求任务接口,在这里需要先弄清楚Base Url基础网络链接,Path路径,Query请求参数这几个概念的关系,我随便找了一个百度图片的url链接来加深对这几个概念的认识。
访问百度图片的示例
https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=false&word=%E6%9D%A8%E5%B9%82&step_word=&hs=0&pn=2&spn=0&di=9280&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&istype=2&ie=utf-8&oe=utf-8&in=&cl=2&lm=-1&st=-1&cs=3359357441%2C1831176160&os=2057570920%2C3873201257&simid=4259213350%2C801558043&adpicid=0&lpn=0&ln=1872&fr=&fmq=1631953164671_R&fm=index&ic=0&s=undefined&hd=undefined&latest=undefined©right=undefined&se=&sme=&tab=0&width=&height=&face=undefined&ist=&jit=&cg=star&bdtype=11&oriquery=&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fwx4.sinaimg.cn%2Fmw690%2F007lbnyvly1gudwovi41tj60jd0o4jsl02.jpg%26refer%3Dhttp%3A%2F%2Fwx4.sinaimg.cn%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Djpeg%3Fsec%3D1634545171%26t%3Db00bb646d0f38c13d770fa18d056c682&fromurl=ippr_z2C%24qAzdH3FAzdH3Fojtk5_z%26e3Bv54AzdH3Fm0dcl9canlAzdH3FKxPH7BccI&gsm=3&rpstart=0&rpnum=0&islist=&querylist=&nojc=undefined
关于Base Url基础网络链接,Path路径,Query请求参数这3个概念的关系如下
术语 | 内容 | 特征 |
---|---|---|
Base Url | https://image.baidu.com/search/ | 请求链接中头部的除Path以外的链接 |
Path | detail | 请求链接中“?”前的参数 |
Query | z=0 表示Query的key为”z”,value为”0” | 1.“?”后的参数,每一组参数都是key,value同时出现;2.两组参数之间使用“&”连接 |
有了上述的概念之后,我们开始定义请求接口,示例如下 |
/**
* 请求百度图片api
* */
public interface RetrofitService {
@GET("index")
Call<Object> getPicture(@Query("word") String word);//请求图片
}
可以根据需要定义拦截器,一般考虑在拦截器修正请求链接,用以增加全部网络请求场景的共同参数,常见的用法有添加时间戳,添加令牌token,添加序列号等,参照以下示例
public class PictureInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//添加新的请求参数
HttpUrl.Builder builder = request.url().newBuilder();
String query = request.url().query();
builder.removeAllQueryParameters("word");
builder.addQueryParameter("tn","baiduimage");
builder.addQueryParameter("ipn","r");
builder.addQueryParameter("ct","201326592");
builder.addQueryParameter("cl","2");
builder.addQueryParameter("lm","-1");
builder.addQueryParameter("st","-1");
builder.addQueryParameter("sf","1");
builder.addQueryParameter("fmq","");
builder.addQueryParameter("pv","");
builder.addQueryParameter("ic","0");
builder.addQueryParameter("nc","1");
builder.addQueryParameter("z","");
builder.addQueryParameter("se","1");
builder.addQueryParameter("showtab","0");
builder.addQueryParameter("fb","0");
builder.addQueryParameter("width","");
builder.addQueryParameter("height","");
builder.addQueryParameter("face","0");
builder.addQueryParameter("istype","2");
builder.addQueryParameter("ie","utf-8");
builder.addQueryParameter("fm","index");
builder.addQueryParameter("pos","history");
builder.addQueryParameter(query.substring(0,query.indexOf("=")),query.substring(query.indexOf("=") , query.length()-1));
//重构请求数据
HttpUrl newUrl = builder.build();
Request newQuest = request.newBuilder()
.url(newUrl)
.method(request.method() , request.body())
.build();
return chain.proceed(newQuest);
}
}
接下来就是构建请求实体方法
private final static String baseUrl = "https://image.baidu.com/search/";
//构建请求实体
private Retrofit getEntity(){
PictureInterceptor interceptor = new PictureInterceptor();
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
定义了一个基于关键词请求图片的方法
//基于关键词搜索图片
public void getPicture(String keyWords){
Retrofit retrofit = getEntity();
RetrofitService retrofitService = retrofit.create(RetrofitService.class);
Call<Object> call = retrofitService.getPicture(keyWords);
call.enqueue(new Callback<Object>() {
@Override
public void onResponse(Call<Object> call, Response<Object> response) {
Log.d(TAG,"response body = "+response.body());
}
@Override
public void onFailure(Call<Object> call, Throwable t) {
Log.d(TAG,"error");
}
});
}
以上即是Retrofit基本用法的简要说明示例
网络请求任务流程
RetrofitService retrofitService = retrofit.create(RetrofitService.class)
在网络请求任务流程中,如上述示例,在创建了RetrofitService对象之后,在Retrofit类中create方法会生成RetrofitService动态代理对象。随后会通过loadServiceMethod方法解析RetrofitService方法内容,调用ServiceMethod类的方法parseAnnotations来解析注解,最终实现注解解析的是HttpServiceMethod类中的parseAnnotations方法。
//使用动态代理,由service的ClassLoader对生成的代理对象加载
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override public @Nullable Object invoke(Object proxy, Method method,
@Nullable 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);
}
//1.基于invoke执行网络请求
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
HttpServiceMethod类中的createCallAdapter方法用于获取网络请求任务
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
try {
//noinspection unchecked
//获取Call请求任务
return (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw methodError(method, e, "Unable to create call adapter for %s", returnType);
}
}
随后该请求会走到Retrofit中,来获取下一个网络请求任务
//获取下一个网络请求任务
public CallAdapter<?, ?> nextCallAdapter(
@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
Objects.requireNonNull(returnType, "returnType == null");
Objects.requireNonNull(annotations, "annotations == null");
//获取下一个任务
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
……
throw new IllegalArgumentException(builder.toString());
}
最终是在DefaultCallAdapterFactory类中get()方法创建网络执行器
//创建网络请求执行器
@Override
public @Nullable CallAdapter<?, ?> get(
Type returnType, Annotation[] annotations, Retrofit retrofit) {
……
final Executor executor =
Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
}
};
}
通过ExecutorCallbackCall类实现网络请求的执行方法
@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
// Emulate OkHttp's behavior of throwing/delivering an IOException on
// cancellation.
//如果任务被取消,走失败接口通知
callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
} else {
callback.onResponse(ExecutorCallbackCall.this, response);
}
});
}
@Override
public void onFailure(Call<T> call, final Throwable t) {
callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
}
});
}
网络请求任务流程用流程图概括如下
处理网络响应流程
这里的行为是处理网络请求得到的Response.基本流程是Retrofit创建了动态代理对象后,会调用HttpServiceMethod中的invoke方法基于反射原理调用相应方法。然后在OkHttpCall对象中execute()方法,最后是在parseResponse方法中解析Response,当中会对Response Code进行判断,比如Code值小于200或者大于等于300时认为是错误响应,会走error通知;Code值为204或205时认为是成功响应,会走success通知。
OkHttpCall中parseResponse方法源码如下
//4.解析网络响应,预处理不同code的情况
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse =
rawResponse
.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
//该范围code认为响应错误
if (code < 200 || code >= 300) {
try {
// Buffer the entire body to avoid future I/O.
ResponseBody bufferedBody = Utils.buffer(rawBody);
return Response.error(bufferedBody, rawResponse);
} finally {
rawBody.close();
}
}
//该范围code认为响应成功
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
T body = responseConverter.convert(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}
处理网络响应的函数调用的基本流程如下
Retrofit解析注解参数原理
解析网络请求参数流程的发起是在Retrofit中创建了动态代理对象后,通过调用loadServiceMethod(Method method)方法请求解析RretrofitService方法内容。然后在ServiceMethod类中调用RequestFactory对象的parseAnnotations(Retrofit retrofit, Method method)方法,最终将RequestFactory对象解析的方法和参数信息传入HttpServiceMethod类中parseAnnotations方法中用于检查检查RetrofitService接口方法的注解信息,并将注解和方法信息转为网络请求任务
//解析RetrofitService方法内容
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
//解析方法注解信息
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
RequestFactory基于注解参数类别解析代码如下
//5.基于注解参数类别解析
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError(method, "@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
}
……
// Get the relative URL path and existing query string, if present.
//URL路径以?结尾,查找结尾
int question = value.indexOf('?');
if (question != -1 && question < value.length() - 1) {
// Ensure the query string does not have any named parameters.
String queryParams = value.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
if (queryParamMatcher.find()) {
throw methodError(
method,
"URL query string \"%s\" must not have replace block. "
+ "For dynamic query parameters use @Query.",
queryParams);
}
}
this.relativeUrl = value;
this.relativeUrlParamNames = parsePathParameters(value);
}
解析请求参数,并转为网络请求这部分的流程梳理总结如下
学习心得
Retrofit框架内部调用逻辑复杂,文章重在记录个人学习过程的理解,由于个人能力水平有限,解答过程存在不足,后续会不断完善,也欢迎大家指正交流。