Retrofit 2.0 使用和原理
使用教程:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1016/3588.html
retrofit2 与okhttp关系
http://blog.csdn.net/lmj623565791/article/details/51304204
DEMO,用api调用天气数据
http://blog.csdn.net/a553181867/article/details/52093695
Retrofit2 源码解读
构造方法
Retrofit构造方法采用构建者模式构造,源码如下。
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();//key为接口中定义方法,value为转换过后的方法 public static final class Builder { private Platform platform;//平台:安卓、java等 private okhttp3.Call.Factory callFactory; //okhttp的Call工厂类,自定义newCall将Request转为Call private HttpUrl baseUrl;//okhttp中的类,保存解析过的url private List<Converter.Factory> converterFactories = new ArrayList<>();//类型转换工厂列表。 private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();//CallAdapter工厂列表。 private Executor callbackExecutor;//回调线程池 private boolean validateEagerly;//急需验证?作用在于直接将所有方法加入前面的map缓存中。 Builder(Platform platform) { this.platform = platform; converterFactories.add(new BuiltInConverters());//添加默认的转换器 } public Builder() { this(Platform.get());//通过Platform.get()获取关于当前平台的实现 } public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient();//默认使用OkHttpClient } Executor callbackExecutor = this.callbackExecutor; if (callbackExecutor == null) {//默认使用平台默认回调线程池 callbackExecutor = platform.defaultCallbackExecutor(); } //将平台默认CallAdapter.Factory加入列表中 List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories); adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); //将默认Converter.Factory加入列表中 List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories); return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, callbackExecutor, validateEagerly); }
可以看出,首选通过Platform.get()
来获取平台实现然后添加了默认转换工厂。由于我们是Android平台,毋容置疑,接下来就去看看Andoird类的源码。
static class Android extends Platform { //回调线程池 @Override public Executor defaultCallbackExecutor() { return new MainThreadExecutor();//主线程 } @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) { //ExecutorCallAdapterFactory继承CallAdapter.Factory,内部代理了原来的Call<T>,用于将Callback回调到指定线程中。 return new ExecutorCallAdapterFactory(callbackExecutor); } static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r);//采用Hanlder#post回调到主线程 } } }
Andoird类的源码主要实现了两个方法,一个是实现了默认的回调线程池,用于在主线程中执行任务,另一个是实现了CallAdapter工厂,通过代理的方式,将执行结果回调到callbackExecutor中去执行。所以,我们只需将callbackExecutor赋值为MainThreadExecutor即可实现主线程间的回调。
BuiltInConverters继承于Converter.Factory,Converter.Factory中有三个方法:
public Converter<ResponseBody, ?> responseBodyConverter(xx)
用于将ResponseBody转换为指定类型,通常用于对响应结果的类型转换。public Converter<?, RequestBody> requestBodyConverter(xx)
用于将指定类型转为RequestBody。一般用于将@Body,@Part,@PartMap
转为RequestBodypublic Converter<?, String> stringConverter
用于将指定类型转为String,用于将@Field,@FieldMap,@Path,@Query,@Header
等注解的参数类型转为String。
Retrofit提供的用于自定义的方法如下:
client(OkHttpClient)
用于自定义客户端callFactory(okhttp3.Call.Factory factory)
用于自定义Call工厂,重写newCall将Request转为Call。OkHttpClient就是实现了这个接口。addConverterFactory
添加类型转换工厂(Gson转换等)addCallAdapterFactory
添加CallAdapter代理工厂,用来代理原始的Call(RxJavaCallAdapter等)。callbackExecutor
自定义回调线程池,默认为主线程validateEagerly
是否继续验证,是就提前将所有方法转为ServiceMethod放入缓存中,而不是调用一个缓存一个baseUrl
用于定义基本链接,必须以”/”结尾
假如基本地址为http://example.com/api/
,关于baseUrl与注解中路径的拼接问题如下:
注解中的路径 | 最终Url (baseUrl为http://example.com/api/) |
---|---|
foo/bar/ | http://example.com/api/foo/bar/ |
/foo/bar/ | http://example.com/foo/bar/ |
https://github.com/square/retrofit/ | https://github.com/square/retrofit/ |
//github.com/square/retrofit/ | http://github.com/square/retrofit/ |
接下来看使用:
Retrofit retrofit = new Retrofit.Builder() .baseUrl(httpurl) .addConverterFactory(GsonConverterFactory.create()) .build(); IWeather iWeather = retrofit.create(IWeather.class); Call<City> call = iWeather.getCity(API_KEY, "shenzhen"); call.enqueue(new Callback<City>() { @Override public void onResponse(Call<City> call, retrofit2.Response<City> response) {} @Override public void onFailure(Call<City> call, Throwable t) {} });
以这段代码为例
我给Retrofit对象传了一个IWeather接口的Class对象,怎么又返回一个IWeather
对象呢?进入create
方法一看,没几行代码,但是我觉得这几行代码就是Retrofit的精妙的地方:
/** Create an implementation of the API defined by the {@code service} interface. */ public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } 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); } });
create
方法就是返回了一个Proxy.newProxyInstance
动态代理对象。那么问题来了...
动态代理是个什么东西?
看Retrofit代码之前我知道Java动态代理是一个很重要的东西,比如在Spring框架里大量的用到,但是它有什么用呢?
Java动态代理就是给了程序员一种可能:当你要调用某个Class的方法前或后,插入你想要执行的代码
比如你要执行某个操作前,你必须要判断这个用户是否登录,或者你在付款前,你需要判断这个人的账户中存在这么多钱。这么简单的一句话,我相信可以把一个不懂技术的人也讲明白Java动态代理是什么东西了。
为什么要使用动态代理
你看上面代码,获取数据的代码就是这句:
Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
上面api
对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi
接口的implements
产生的对象,当api
对象调用getAuthor
方法时会被动态代理拦截,然后调用Proxy.newProxyInstance
方法中的InvocationHandler
对象,它的invoke
方法会传入3个参数:
- Object proxy: 代理对象,不关心这个
- Method method:调用的方法,就是
getAuthor
方法 - Object... args:方法的参数,就是
"qinchao"
而Retrofit关心的就是method
和它的参数args
,接下去Retrofit就会用Java反射获取到getAuthor
方法的注解信息,配合args
参数,创建一个ServiceMethod
对象
ServiceMethod
就像是一个中央处理器,传入Retrofit
对象和Method
对象,调用各个接口和解析器,最终生成一个Request
,包含api 的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。最后返回一个Call
对象,Retrofit2中Call接口的默认实现是OkHttpCall
,它默认使用OkHttp3作为底层http请求client
使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送
3 Retrofit的源码分析
想要弄清楚Retrofit的细节,先来看一下Retrofit源码的组成:
- 一个
retrofit2.http
包,里面全部是定义HTTP请求的Java注解,比如GET
、POST
、PUT
、DELETE
、Headers
、Path
、Query
等等 - 余下的
retrofit2
包中几个类和接口就是全部retrofit的代码了,代码真的很少,很简单,因为retrofit把网络请求这部分功能全部交给了OkHttp了
Retrofit接口
Retrofit的设计非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:
Callback<T>
这个接口就是retrofit请求数据返回的接口,只有两个方法
void onResponse(Response<T> response);
void onFailure(Throwable t);
Converter<F, T>
这个接口主要的作用就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson、protobuf等等,你可以在创建Retrofit
对象时添加你需要使用的Converter
实现(看上面创建Retrofit对象的代码)
Call<T>
这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>
,你可以根据实际情况实现你自己的Call类,这个设计和Volley的HttpStack
接口设计的思想非常相似,子类可以实现基于HttpClient
或HttpUrlConnetction
的HTTP请求工具,这种设计非常的插件化,而且灵活
CallAdapter<T>
上面说到过,CallAdapter
中属性只有responseType
一个,还有一个<R> T adapt(Call<R> call)
方法,这个接口的实现类也只有一个,DefaultCallAdapter
。这个方法的主要作用就是将Call
对象转换成另一个对象,可能是为了支持RxJava才设计这个类的吧
Retrofit的运行过程
上面讲到ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);
代码返回了一个动态代理对象,而执行Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
代码时返回了一个OkHttpCall
对象,拿到这个Call
对象才能执行HTTP请求
上面api
对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi
接口的implements
产生的对象,当api
对象调用getAuthor
方法时会被动态代理拦截,然后调用Proxy.newProxyInstance
方法中的InvocationHandler
对象, 创建一个ServiceMethod
对象
ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall);
loadServiceMethod
的方法很简陋,直接传入了Method然后build()
,那么这个build()
又做了什么?
ServiceMethod loadServiceMethod(Method method) { ServiceMethod result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method);//首先从map中取看看是否已经缓存过 if (result == null) {//否则构造ServiceMethod。 result = new ServiceMethod.Builder(this, method).build(); serviceMethodCache.put(method, result);//缓存map中 } } return result; } public Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations();//赋值方法注解数组 this.parameterTypes = method.getGenericParameterTypes();//赋值参数类型数组 this.parameterAnnotationsArray = method.getParameterAnnotations();//赋值参数注解数组 } public ServiceMethod build() { callAdapter = createCallAdapter();//创建CallAdapter,用来代理Call responseType = callAdapter.responseType();//获取返回类型 //... responseConverter = createResponseConverter();//创建ResponseConverter,用来转换ResponseBody为指定类型 for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation);//遍历解析方法注解 } //... int parameterCount = parameterAnnotationsArray.length;//parameterAnnotationsArray为参数注解数组 parameterHandlers = new ParameterHandler<?>[parameterCount];//初始化ParameterHandler,用来处理参数相关 for (int p = 0; p < parameterCount; p++) {//遍历参数注解数组 Type parameterType = parameterTypes[p];//获取参数类型 Annotation[] parameterAnnotations = parameterAnnotationsArray[p];//获取参数注解数组 //... //通过注解和参数类型,解析并赋值到parameterHandlers中 parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); } //... return new ServiceMethod<>(this); }
创建ServiceMethod
刚才说到,ServiceMethod
就像是一个中央处理器,具体来看一下创建这个ServiceMethod
的过程是怎么样的
第一步,获取到上面说到的3个接口对象:
callAdapter = createCallAdapter(); responseType = callAdapter.responseType(); responseConverter = createResponseConverter();
第二步,解析Method的注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了@Multipart
,但是Http请求方法是GET,同样也会报错。因此,在注解Java方法是需要严谨
for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } if (httpMethod == null) { throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.)."); }
parseMethodAnnotation
用于遍历解析方法上的注解,比如请求方法,请求头之类的。
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); if (!Void.class.equals(responseType)) { throw methodError("HEAD method must use Void as response type."); } } 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 = (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("@Headers annotation is empty."); } headers = parseHeaders(headersToParse);//解析Header } else if (annotation instanceof Multipart) { //Multipart if (isFormEncoded) { throw methodError("Only one encoding annotation is allowed."); } isMultipart = true; } else if (annotation instanceof FormUrlEncoded) { //FormUrlEncoded if (isMultipart) { throw methodError("Only one encoding annotation is allowed."); } isFormEncoded = true; } }
从上面源码可以看出,使用parseHttpMethodAndPath这个方法用于解析请求方法注解和路径参数保存到Set中
private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) { if (this.httpMethod != null) { throw methodError("Only one HTTP method is allowed. Found: %s and %s.", this.httpMethod, httpMethod); } this.httpMethod = httpMethod; this.hasBody = hasBody; if (value.isEmpty()) { return; } int question = value.indexOf('?');//查询参数开始的符号 if (question != -1 && question < value.length() - 1) { //如果在查询参数中使用了{},则抛出异常。 String queryParams = value.substring(question + 1); Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams); if (queryParamMatcher.find()) { throw methodError("URL query string \"%s\" must not have replace block. " + "For dynamic query parameters use @Query.", queryParams); } } //赋值相对链接 this.relativeUrl = value; //解析{}路径参数保存到Set中 this.relativeUrlParamNames = parsePathParameters(value); }
从源码可以发现,不允许在查询参数中使用{}
进行占位,否则就会抛出异常,然后将请求方法中的注解值赋值给relativeUrl,通过parsePathParameters
将{}路径参数保存到Set中。
第三步,比如上面api中带有一个参数{user}
,这是一个占位符,而真实的参数值在Java方法中传入,那么Retrofit会使用一个ParameterHandler
来进行替换:
int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<?>[parameterCount];
最后,ServiceMethod
会做其他的检查,比如用了@FormUrlEncoded
注解,那么方法参数中必须至少有一个@Field
或@FieldMap
执行Http请求
之前讲到,OkHttpCall
是实现了Call
接口的,并且是真正调用OkHttp3
发送Http请求的类。OkHttp3
发送一个Http请求需要一个Request
对象,而这个Request
对象就是从ServiceMethod
的toRequest
返回的
总的来说,OkHttpCall
就是调用ServiceMethod
获得一个可以执行的Request
对象,然后等到Http请求返回后,再将response body传入ServiceMethod
中,ServiceMethod
就可以调用Converter
接口将response body转成一个Java对象
结合上面说的就可以看出,ServiceMethod
中几乎保存了一个api请求所有需要的数据,OkHttpCall
需要从ServiceMethod
中获得一个Request
对象,然后得到response后,还需要传入ServiceMethod
用Converter
转换成Java对象
你可能会觉得我只要发送一个HTTP请求,你要做这么多事情不会很“慢”吗?不会很浪费性能吗?
我觉得,首先现在手机处理器主频非常高了,解析这个接口可能就花1ms可能更少的时间(我没有测试过),面对一个HTTP本来就需要几百ms,甚至几千ms来说不值得一提;而且Retrofit会对解析过的请求进行缓存,就在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
这个对象中
调用服务接口
以上介绍了retrofit.create(XX.class);
用于创建服务方法接口的过程。现在该进行相关调用了。我们知道,创建服务方法会返回一个Call<XX>
对象,通过Call<XX>
可以进行相关异步同步调用。
@Override public void enqueue(final Callback<T> callback) { if (callback == null) throw new NullPointerException("callback == null"); okhttp3.Call call;//okhttp中的call对象 Throwable failure; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; call = rawCall; failure = creationFailure; if (call == null && failure == null) { try { call = rawCall = createRawCall();//创建原始Call,即okhttp中的Call对象 } catch (Throwable t) { failure = creationFailure = t; } } } if (failure != null) { callback.onFailure(this, failure); return; } if (canceled) { call.cancel(); } //通过enqueue进行异步调用 call.enqueue(new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) throws IOException { Response<T> response; try { response = parseResponse(rawResponse);//解析响应内容 } catch (Throwable e) { callFailure(e); return; } callSuccess(response); } }
从源码不难看出,首先通过createRawCall()
来创建OkHttp中的Call对象,然后通过Call进行异步/同步调用,获取结果后通过parseResponse
解析OkHttp中的Response,然后进行相应回调。 createRawCall()
用于创建OkHttp中的Call对象,源码如下:
private okhttp3.Call createRawCall() throws IOException { Request request = serviceMethod.toRequest(args);//通过 serviceMethod.toRequest进行转换成Request okhttp3.Call call = serviceMethod.callFactory.newCall(request);//调用newCall返回Call对象,默认callFactory为OkHttpClient,OkHttpClient也实现了okhttp3.Call.Factory接口。 if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call; }
4 最后
Retrofit非常巧妙的用注解来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求
Retrofit的功能非常多的依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)
Retrofit中接口设计的恰到好处,在你创建Retrofit
对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的Converter
、使用不同的CallAdapter
,这也就提供了你使用RxJava来调用Retrofit的可能