珍惜作者劳动成果,如需转载,请注明出处。
http://blog.csdn.net/zhengzechuan91/article/details/50349934
Retrofit(Github地址)是square公司一套开源的http框架,简单易用,并且支持okhttp和RxJava,如果你不想为配置繁杂的http请求而写一套自己的网络请求框架,那么这套优雅的框架你不妨试试。
上篇博客我们介绍了retrofit在项目中的使用,今天我们就来说说这个框架实现的原理。
我们先来说说Retrofit运行原理:
生成RestAdapter实例,然后RestAdapter的内部类RestHandler动态代理我们实际的操作接口RealService;
当我们调用RealService的方法时,就会由RestHandler.invoke()代理;
生成RequestBuilder实例,并通过setArguments(Object[])将参数解析到RequestBuilder实例中,如果参数为POJO类型,通过Converter的toBody(Object)将POJO转换为TypedOutput;
利用我们设置的RequestInterceptor,在发送http请求前拦截RequestBuilder并向其中添加参数,最后转换为Request;
执行Client.execute(Request),返回Response;
如果返回Response的状态码为2**,继续处理,否则抛出各种RetrofitError异常;
需要的返回类型为Response,同步直接返回,异步时包装为ResponseWrapper;
需要的返回类型如果不为Response,通过设定的Converter的fromBody(TypedInput, Type)将TypedInput转化为POJO,同步直接返回,异步包装为ResponseWrapper。
通过上面我们看到,这个框架将不同的平台抽象为Platform,将不同的请求方式抽象为Request,将不同的返回结果抽象为Response,而Converter提供了Request与POJO的转换和Response与POJO的转换,思路还是非常清晰。
既然我们对框架有了整体的理解,我们看看一些细节,以加深我们对框架的理解。
我们根据上面的思路从平台、请求、拦截、返回四个角度对框架做深入的分析
在平台的选择上是通过抽象类Platform来切换的。
Platform对平台相关的变量都通过方法进行了抽象:
/* Platform.java */
//默认Converter
abstract Converter defaultConverter();
//默认Client
abstract Client.Provider defaultClient();
//默认同步http线程池
abstract Executor defaultHttpExecutor();
//默认异步http线程池
abstract Executor defaultCallbackExecutor();
//默认log
abstract RestAdapter.Log defaultLog();
而三个内部类Android、AppEngine和Base都实现了以上的抽象方法,分别代表三个不同的平台。而进行选择时,我们也会从前往后以此选取,当前面两个平台都不满足时,则会使用默认的Base。
/* Platform$Android.java */
private static class Android extends Platform {
@Override Converter defaultConverter() {
//默认为Gson解析
return new GsonConverter(new Gson());
}
@Override Client.Provider defaultClient() {
final Client client;
if (hasOkHttpOnClasspath()) {
//使用了okhttp框架,使用OkClient
client = OkClientInstantiator.instantiate();
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
//android2.3以下,使用ApacheClient
client = new AndroidApacheClient();
} else {
//android2.3及以上,使用HttpURLConnection
client = new UrlConnectionClient();
}
return new Client.Provider() {
@Override public Client get() {
return client;
}
};
}
@Override Executor defaultHttpExecutor() {
//同步任务添加到线程池
return Executors.newCachedThreadPool(new ThreadFactory() {
@Override public Thread newThread(final Runnable r) {
return new Thread(new Runnable() {
@Override public void run() {
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
r.run();
}
}, RestAdapter.IDLE_THREAD_NAME);
}
});
}
@Override Executor defaultCallbackExecutor() {
//异步任务添加到主线程消息队列
return new MainThreadExecutor();
}
@Override RestAdapter.Log defaultLog() {
return new AndroidLog("Retrofit");
}
}
ApacheClient与UrlConnectionClient都直接实现了Client接口,而OkClient继承了UrlConnectionClient。
ApacheClient是对apache的HttpClient的封装,UrlConnectionClient是对java的UrlConnectionClient的封装,而OkClient则是对okhttp框架的OkHttpClient的封装。
我们再看看ApacheClient和UrlConnectionClient请求的代码:
/* ApacheClient.java */
@Override
public Response execute(Request request) throws IOException {
//向HttpUriRequest中添加Request的uri和head,如果是
//Request的body不为空,则向HttpUriRequest的Entry中设置
//Request的body
HttpUriRequest apacheRequest = createRequest(request);
//执行http请求
HttpResponse apacheResponse = execute(client, apacheRequest);
//解析HttpResponse的head和entry,包装成Response实例
//如果entry不为空,则Response的body为TypedByteArray
return parseResponse(request.getUrl(), apacheResponse);
}
/* UrlConnectionClient.java */
@Override
public Response execute(Request request) throws IOException {
//获取Client实例
HttpURLConnection connection = openConnection(request);
//向connection中添加Request的head,如果Request的
//body不为空,则将body写入connection中
//如果body的长度不为-1,则使用固定长度流输出
//如果长度为-1,则等固定大小缓存满后输出一次
//而默认是在数组中全部缓存输出流,一次输出,可能会oom
prepareRequest(connection, request);
//通过connection返回的值构建Response,而Response的
//body为TypedInputStream
return readResponse(connection);
}
看完了上面平台相关的代码,我们一定会有一个疑问:Request的body值什么时候不为空呢?
让我们先来看看RequestBuilder这个类,这个类是对要调用的方法的解析,里面有三个地方对TypedOutput类型的body进行了赋值,分别是方法注解@FormUrlEncoded、@Multipart和参数注解@Body。
RestAdapter adapter = new RestAdapter.Builder()
.setClient()
.setEndpoint()
.setRequestInterceptor()
.setConverter()
.setLogLevel(LogLevel.FULL)
.build();
RealService real = adapter.create(RealService.class);
当我们在RestAdapter.Builder().build()时,系统会帮我们配置我们没实现的配置,而这些参数都是根据Platform生成的默认值。
而RestAdapter#create(),首先会判断需要代理的必须是接口,并且不能和其他类或接口有继承或实现的关系,否则会抛出IllegalArgumentException异常。然后通过RestAdapter的内部类RestHandler动态代理接口RealService。
接下来的所有操作都是在RestHandler#invoke()中完成。
serviceMethodInfoCache中缓存了每个RealService接口对应的RestMethodInfo队列,每一个Method对应一个RestMethodInfo。
RestAdapter#create()时,默认会创建一个RealService接口对应的队列加入到serviceMethodInfoCache中。
RealService调用getList()方法获取数据时,就会执行到RestHandler#invoke()
@Override
public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable {
// 不是这个接口定义的方法不代理
if (method.getDeclaringClass()
== Object.class) {
return method.invoke(this, args);
}
// 这个时候RealService对应的队列
// methodDetailsCache里面是空的,
//通过Method新建一个对应的RestMethodInfo
//里面会有一些Method相关的赋值
final RestMethodInfo methodInfo = getMethodInfo(methodDetailsCache, method);
//如果方法返回的是object类型
if (methodInfo.isSynchronous) {
try {
return invokeRequest(requestInterceptor, methodInfo, args);
} catch (RetrofitError error) {
throw newError;
}
}
// 新建RequestInterceptorTape实例
final RequestInterceptorTape interceptorTape = new RequestInterceptorTape();
//方法最后一个参数为Callback,异步
Callback<?> callback = (Callback<?>) args[args.length - 1];
//线程池执行CallbackRunnable
// 1.后台线程通过obtainResponse()获取
// ResponseWrapper
// 2.主线程Callback回调返回Response
httpExecutor.execute(new
CallbackRunnable(callback,
callbackExecutor) {
@Override
public ResponseWrapper obtainResponse() {
return (ResponseWrapper)
invokeRequest(interceptorTape,
methodInfo, args);
}
});
return null;
}
我们看到,最后都是调用到了RestAdapter#invokeRequest()方法,这个方法封装了拦截、http请求细节,返回了Response。
那我们就重点看看这个方法里的逻辑。
//新建RestMethodInfo实例时只是通过构造方法设置了几个参数,
//而这里则是解析这个方法的方法注解和参数参数注解
methodInfo.init();
来看看RestMethodInfo#init()方法的代码:
synchronized void init() {
if(!this.loaded) {
this.parseMethodAnnotations();
this.parseParameters();
this.loaded = true;
}
}
而RestMethodInfo#parseMethodAnnotations()主要解析了方法里的注解,其中对@POST、@GET注解相对路径的处理我们看看:
private void parsePath(String path) {
//如果这个相对路径不是以/开头的,会抛出异常
if (path == null || path.length() == 0 || path.charAt(0) != '/') {
throw methodError("URL path \"%s\" must start with '/'.", path);
}
//url为相对路径
String url = path;
String query = null;
int question = path.indexOf('?');
//如果相对路径中带有参数,则检查参数中是否有占位符
if (question != -1 && question < path.length() - 1) {
url = path.substring(0, question);
query = path.substring(question + 1);
Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(query);
//如果相对路径带的参数中有占位符,抛出异常
if (queryParamMatcher.find()) {
throw methodError("URL query string \"%s\" must not have replace block.", query);
}
}
//记录相对路径中的占位符列表
Set<String> urlParams = parsePathParameters(path);
//requestUrl保存不带参数的相对路径
requestUrl = url;
//requestUrlParamNames保存相对路径中的占位符列表
requestUrlParamNames = urlParams;
//requestQuery保存相对路径的参数字符串:
//' id=1&name="android" '
requestQuery = query;
}
而负责解析参数注解的方法RestMethodInfo#parseParameters()则是将该方法的每个参数的名称放在requestParamNames数组中,将每个参数注解的类型放在requestParamUsage数组中。
这个时候这个方法的注解已经解析完毕,然后根据这个RestMethodInfo创建一个RequestBuilder:
//向RequestBuilder拷贝RestMethodInfo的值
//queryParams:' ?id=1&name="android" '
RequestBuilder requestBuilder = new
RequestBuilder(serverUrl, methodInfo, converter);
//向head、url等注入args参数,args参数中不能有null值,会抛异常
requestBuilder.setArguments(args);
//更新相对路径url
methodInfo.setRelativeUrl(requestBuilder.getRelativeUrl());
methodInfo.setArgs(args);
这个时候,我们已经在RequestBuilder中准备好了request时需要的参数。
我们可能想在http请求前,再往head中添加一些公共参数,框架给我们提供了RequestInterceptor接口,通过RequestInterceptor#intercept(),我们就可以对参数进行修改。
public interface RequestInterceptor {
void intercept(RequestInterceptor.RequestFacade var1);
public interface RequestFacade {
void addHeader(String var1, String var2);
void addPathParam(String var1, String var2);
void addEncodedPathParam(String var1,
String var2);
void addQueryParam(String var1, String var2);
void addEncodedQueryParam(String var1,
String var2);
RestMethodInfo getMethodInfo();
}
}
我们再来看看实际代码中是怎么拦截的:
//异步
if (requestInterceptor instanceof
RequestInterceptorTape) {
RequestInterceptorTape interceptorTape =
(RequestInterceptorTape) requestInterceptor;
interceptorTape.setMethodInfo(methodInfo);
//从自定义拦截器向interceptorTape中添加参数
//实际调用RequestInterceptorTape#add*()向tape中添加
//不同Command实例
RestAdapter.this.requestInterceptor
.intercept(interceptorTape);
//从interceptorTape向requestBuilder中添加参数
//实际调用RequestInterceptorTape#intercept()
//遍历tape根据不同Command挨个向requestBuilder添加参数
interceptorTape.intercept(requestBuilder);
} else { //同步
//从自定义拦截器向requestBuilder中添加参数
requestInterceptor.intercept(requestBuilder);
}
添加完了参数,那我们拦截的任务也就完成了。
拦截完了后,通过RequestBuilder#build()就将url的根路径和相对路径拼装在了一起,并将RequestBuilder转化成了Request。
Request request = requestBuilder.build();
根据现在的平台去执行请求,然后返回Response。
Response response = clientProvider.get().execute(request);
类型为方法的返回类型,这个是API返回数据将TypedInput转换为POJO时Converter#fromBody(TypedInput, Type)的第二个参数,表示我们方法返回的类型。
Type type = methodInfo.responseObjectType;
如果返回值的状态码不为2XX,则会抛出RetrofitError异常。
如果返回的状态码为2XX,则说明请求API成功。
如果返回的是Response,则直接返回
if (type.equals(Response.class)) {
response = Utils.readBodyToBytesIfNecessary(response);
if (methodInfo.isSynchronous) {
return response;
}
return new ResponseWrapper(response, response);
}
而如果返回的类型不是Response,则需要我们去转换
TypedInput body = response.getBody();
if (body == null) {
return new ResponseWrapper(response, null);
}
ExceptionCatchingTypedInput wrapped = new ExceptionCatchingTypedInput(body);
try {
//通过转换器将输入流wrapped转换成返回类型type
Object convert = converter.fromBody(wrapped, type);
if (methodInfo.isSynchronous) {
return convert;
}
return new ResponseWrapper(response, convert);
} catch (ConversionException e) {
}
返回Response后,同步的话,会返回异步线程,通知UI线程数据更新;而如果是异步,则会通知到Callback,直接在UI线程中更新数据即可。
我们再总结下使用时需要注意的地方:
1. 异步最后一个参数为Callback,返回类型为void;同步参数中没有Callback,返回类型为Object;
2. 只有被方法注解@FormUrlEncoded、@Multipart和参数注解@Body注解时,参数中才能有POJO参数;
3. request的POJO是通过Converter#toBody(Object)转换为TypedOutput的;
4. response是通过Converter#fromBody(TypedInput, Type)将TypedInput转化为POJO的;
5. @GET或@POST的相对路径必须是以/开头的,否则会抛异常;
6. 我们定义的请求API的接口的参数不能有null值,否则会抛异常。
好了,Retrofit框架相关的分析到此结束。