话前:
1 如果有时间可以看一看retrofit2的jack本尊演讲视频,可能有人连retrofit 1都还没用过,但实际上基本不影响,其主要的框架设计并没有发生变化,等熟悉了1我们再看2会更加事半功倍。
2 对于retrofit的背景我稍稍提一下,它其实在2010年就开始着手开发了,而当时的大当家是鼎鼎大名的crazy bob,即guice的作者,后来由square接管。dagger的命运也是如此的相似。
3 关于retrofit与okhttp,两者都是square出品。okhttp实际上充当的是通信员的角色,而retrofit是支持替换okhttp这个通信员的,urlconnection,okhttp,protobuf都是候选通信员,这种插件式的设计的确很良心啊。用一张图可以描述开发者,retrofit以及okhttp之间的关系:
可以看出,retrofit在整个流程当中的作用,我们可以这样理解,myservice是我们自己的系统,主要与我们的app交互,converter是一个系统,主要负责数据的转换,okhttp是一个系统,主要负责socket连接,而这中间负责协调工作的就是retrofit这个系统,它可以与任意一个系统交互,在我看来这是典型的门面模式,这里面除了retrofit不能替换,其余的系统都是可以替换的,像插件一样。
4 示例:
public interface CharactersApi {
@GET("/characters") Observable> getCharacters(
@Query("offset") int offset);
}
这个接口是自己定义的请求接口,大概了解其请求方式,参数,方法名,返回值,接下来,我们转入restadapter类。
1 create方法
public T create(Class service) {
Utils.validateServiceClass(service);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },
new RestHandler(getMethodInfoCache(service)));
}
这个方法是我们最常用的方法,因为其使用方法就是api = adapter.create(CharactersApi.class),然后通过对这个api进行注册便可以得到自己想要的observable。那么当我们把它传进去之后,它会首先对我们传入的类进行校验,只能是接口且不能是子接口。接下来,它使用了代理模式生成我们接口的代理类返回供开发者使用,从这点可以看出,作者希望在执行请求的前后做一些其他事情,然而它并没有实现我们的接口,而是将其大卸八块,拼装成了它自己的一个类RestMethodInfo为执行网络请求提供所需的必要信息。那么我不仅发出疑问既然如此,为什么非要是接口呢?从设计者的角度来讲,我写的是个网络框架,你调用我的主要目的就是网络请求并得到结果,那么我只需要了解你想要怎样的请求就可以了,然后我帮你调用,拿到结果返回给你便是。以其他形式将请求路径,参数,请求方式等等信息告知框架,然后框架内部执行请求,返回一个结果,我想应该也是可以的,因为很多框架都是这样做的,比如android-async-http就是如此。那么这便是retrofit的最大特色之一,开发者利用接口配合注解对需求作出定义,将这份定义传入框架内部识别,识别成功执行然后返回结果,所以相对于其他框架而言它对于这份定义有自己的规范,这套规范就是上面的CharactersApi接口的样子。
在最开始使用retrofit的时候,我很不适应这样的规范,适应了之后,反而爱不释手,在实际开发过程中,修改代码是一件繁琐但很常见的事情,但修改网络请求却从未感到如此的轻松,因为通过定义的接口,可以很直观的看到这个接口的几乎所有信息,参数也好返回值也好都很容易看到并修改到。另外它也很类似于过去得自己动手写的网络调用provider,只不过我们现在不用关心其具体实现了。如果从逻辑上来说,开发者给框架一大堆参数,倒不如双方定义一套规则能够互相都能读懂来得方便,更何况,使用接口来作为规则的定义岂不好呢。
2 缓存
private final Map, Map> serviceMethodInfoCache = new LinkedHashMap, Map>();
...
Map getMethodInfoCache(Class> service) {
synchronized (serviceMethodInfoCache) {
Map methodInfoCache = serviceMethodInfoCache.get(service);
if (methodInfoCache == null) {
methodInfoCache = new LinkedHashMap();
serviceMethodInfoCache.put(service, methodInfoCache);
}
return methodInfoCache;
}
}
static RestMethodInfo getMethodInfo(Map cache, Method method) {
synchronized (cache) {
RestMethodInfo methodInfo = cache.get(method);
if (methodInfo == null) {
methodInfo = new RestMethodInfo(method);
cache.put(method, methodInfo);
}
return methodInfo;
}
}
3 invoke
@SuppressWarnings("unchecked") //
@Override public Object invoke(Object proxy, Method method, final 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);
}
// Load or create the details cache for the current method.
final RestMethodInfo methodInfo = getMethodInfo(methodDetailsCache, method);
if (methodInfo.isSynchronous) {
try {
return invokeRequest(requestInterceptor, methodInfo, args);
} catch (RetrofitError error) {
Throwable newError = errorHandler.handleError(error);
if (newError == null) {
throw new IllegalStateException("Error handler returned null for wrapped exception.",
error);
}
throw newError;
}
}
if (httpExecutor == null || callbackExecutor == null) {
throw new IllegalStateException("Asynchronous invocation requires calling setExecutors.");
}
if (methodInfo.isObservable) {
if (rxSupport == null) {
if (Platform.HAS_RX_JAVA) {
rxSupport = new RxSupport(httpExecutor, errorHandler, requestInterceptor);
} else {
throw new IllegalStateException("Observable method found but no RxJava on classpath.");
}
}
return rxSupport.createRequestObservable(new RxSupport.Invoker() {
@Override public ResponseWrapper invoke(RequestInterceptor requestInterceptor) {
return (ResponseWrapper) invokeRequest(requestInterceptor, methodInfo, args);
}
});
}
// Apply the interceptor synchronously, recording the interception so we can replay it later.
// This way we still defer argument serialization to the background thread.
final RequestInterceptorTape interceptorTape = new RequestInterceptorTape();
requestInterceptor.intercept(interceptorTape);
Callback> callback = (Callback>) args[args.length - 1];
httpExecutor.execute(new CallbackRunnable(callback, callbackExecutor, errorHandler) {
@Override public ResponseWrapper obtainResponse() {
return (ResponseWrapper) invokeRequest(interceptorTape, methodInfo, args);
}
});
return null; // Asynchronous methods should have return type of void.
}
1 同步异步
上一篇提到过我们定义的接口返回值会对后面有很大影响,可以说retrofit是根据接口是否存在返回值来区分该请求是同步还是异步的。如果返回值不是void则为同步,如果为observable则会走rxjava的流程,否则为void就是异步请求,若为异步请求,则把参数的最后一个对象拿来作为请求返回值的回调。以这种方式来定义规范,着实让人惊叹。
2 RequestInterceptor 拦截器
上面的代码里,我们可以找到requestInterceptor.intercept(interceptorTape),这意味着retrofit将拦截器暴露出来给开发者使用,实际开发中,一般用它来对header和参数作统一处理,比如添加设备信息或者token等等,因为这里只针对header和parameter暴露了几个方法。尽管2.0里这里有很大变化,但对于拦截器的相关类还是有必要进行说明。
下篇继续。