读retrofit源码
虽然项目中经常用到retrofit,但我一直也没有深入了解过其内部实现。直到有一次,项目中需要取到http url中的path对应的值,结合其他数据,生成接口验签的签名,一开始我无从下手,还是跟后端java的同事一起调试,看到retrofit使用的是动态代理,然后通过retrofit 2.5版本新增的Invocation类,最终完成了path数据获取。
经过这件事,我必须抽时间学习一下retrofit的源码,因为retrofit是支持java和android平台,其使用的动态代理技术,我作为android开发,也没有接触,这也让我觉得,拓展技术面可以让你了解到很多全新的事物,也许你的整个技术架构都会有新的认识。
下面还是说正事,我是使用android studio打开retrofit的源码,版本是2.9.0。
那么,我们先从Retrofit类开始,我们使用的时候,也是需要先创建Retrofit的实例,创建方法是通过builder模式进行的,那我们看一下其内部Builder类。
Builder类公开构造器方法是无参,另外你也可以用Retrofit的newBuilder方法,这会调用另一个内部构造器,接收一个retrofit实例作为参数;构造方法里,首先关注platform字段的实例化,Platform是对运行平台的抽象,判断运行在android还是jvm上,并且由于java8引入了interface default method,而retrofit动态代理的目标也是interface,因此,Platform中封装了对default method的处理。另外在看builder有参构造方法,或者build方法时,我们可以看到,其中调用了platform的defaultConverterFactories和defaultCallAdapterFactories,这两个方法中,如果平台支持java8,那么就会包含OptionalConverterFactory和CompletableFutureCallAdapterFactory,这两个都是根据java8的新特性,用于支持Optional和CompletableFuture。同时,retrofit内置的java8的adapter和converter库也就过期了。
另外我们需要注意,如果是Android平台,则会实现一个MainThreadExecutor作为defaultCallbackExecutor,其实现了execute方法,使用handler执行runnable,实现切换到主线程的功能。而这个callbackExecutor,会作为参数传入DefaultCallAdapterFactory,而此DefaultCallAdapterFactory会加入callAdapterFactory的集合中,最后这个集合会作为参数在构造retrofit实例时使用,具体怎么用我们后面说。
构造完Retrofit类实例之后,我们重点看一下其create方法,其方法签名是: <T> T create(final Class<T> service),这也就是动态代理的核心部分,根据定义的interface构造API endpoint的implementation,这里我都沿用了create代码中注释部分的词汇,翻译不对容易引起误会。create方法中,首先执行validateServiceInterface方法,此方法首先检查传入的class对象,没有类型参数,然后判断validateEagerly为true的话,就遍历Service类中定义的方法,针对不是default method也不是static method的方法,执行loadServiceMethod。
validateServiceInterface方法调用完成后,就调用Proxy.newProxyInstance创建对应接口的代理类的实例,其重点是第三个入参是InvocationHandler,代码中匿名内部类实现了这个接口,实现其invoke方法,这里判断是default method 则调用platform的invokeDefaultMethod方法,否则执行loadServiceMethod获取ServiceMethod对象后调用其invoke方法。
接下来我们重点看一下loadServiceMethod方法,在此方法中,我们可以看到Retrofit中定义的serviceMethodCache字段,这是一个ConcurrentHashMap,里面存储了根据method解析出来的ServiceMethod,而ServiceMethod实例是通过其静态方法parseAnnotations生成的,在这个方法中,先是通过RequestFactory.parseAnnotations得到RequestFactory的实例,调用这个方法,会遍历解析method的annotation,得到是否包含body等信息,然后解析参数注解的数组,得到ParameterHandler<?>的实例,这一步是调用parseParameter方法实现的,其内部建立参数注解,然后调用parseParameterAnnotation得到对应的ParameterHandler,ParameterHandler是个抽象类,需要实现抽象方法apply(RequestBuilder builder, @Nullable T value),知道这点,我们继续回到parseParameterAnnotation,这个方法非常长,其逻辑就是条件判断参数annotation注解是什么:
if(annation instance Url){
ParameterHandler.RelativeUrl
}else if(annotation instanceof Path){
ParameterHandler.Path
}else if(annotation instanceof Query){
ParameterHandler.Query
}else if(annotation instanceof QueryName){
ParameterHandler.QueryName
}else if(annotation instanceof QueryMap){
ParameterHandler.QueryMap
}else if(annotation instanceof Header){
ParameterHandler.Header
}else if(annotation instanceof HeaderMap){
ParameterHandler.HeaderMap
}else if(annotation instanceof Field){
ParameterHandler.Field
}else if(annotation instanceof FieldMap){
ParameterHandler.FieldMap
}else if(annotation instanceof Part){
if(part.value.isEmpty()){
//原始数据即文件
ParameterHandler.RawPart.INSTANCE
}else{
ParameterHandler.Part
}
}else if(annotation instanceof PartMap){
ParameterHandler.PartMap
}else if(annotation instanceof Body){
ParameterHandler.Body
}else if(annotation instanceof Tag){
ParameterHandler.Tag
}
上面的是伪代码,如果参数是数组或集合对象,则会返回用上面的返回值生成的ParameterHandler.array或ParameterHandler.iterable。我们可以看到,每个参数注解,都有对应的ParameterHandler实现类,而其apply方法实现的就是将数据填入RequestBuilder类实例中。
注意Part注解,这里会判断value是否是空的,如果是空的,则注解的类必须是MultipartBody.Part,或者List<MultipartBody.Part>,MultipartBody.Part数组,以支持上次单个或多个文件;
另外,对于ParameterHandler.Part,ParameterHandler.PartMap和ParameterHandler.Body的构造方法,需要调用requestBodyConverter传入Converter<?, RequestBody>,而其他ParameterHandler实现类,如果需要converter,则都是调用stringConverter得到Converter<?, String>,这两个方法,都是在Converter接口中需要实现的,不同的是,即使不实现Converter<?, String>,retrofit也会调用(Converter<T, String>) BuiltInConverters.ToStringConverter.INSTANCE得到默认的Converter<?,String>,其实现就是简单的调用入参value的toString方法,这里我们就可以知道Converter的requestBodyConverter是怎么被使用的。
另外实际代码中,有很多判断逻辑,检查数据正确性,比如Field注解中先会判断isFormEncoded字段是否为true,而这个内部字段,在之前解析方法注解的FormUrlEncoded注解时会设置为true。
然后将retrofit ,method,requestBody作为参数,调用HttpServiceMethod.parseAnnotations,得到HttpServiceMethod实例,这个也就是要保存的ServiceMethod的具体实现类,通过注释我们知道HttpServiceMethod.parseAnnotations方法中使用到了反射,为了不每次都执行此方法,就需要通过serviceMethodCache缓存,以提升性能。
下面我们看一下HttpServiceMethod.parseAnnotations。方法中首先判断isKotlinSuspendFunction是否为true,此属性来自requestionFactory,我们在解析方法的参数时,如果是协程方法,虽然在kotlin代码中看不到,但从实际java字节码中可以看到,方法最后一个参数的类型是Continuation,由此便能判断是否是协程方法,如果是协程方法并且返回Response<T>,那代码中会解析出实际的responseType,使用其构造adapterType;而如果不是协程方法,则调用method.getGenericReturnType()得到adpaterType。这个adapterType实际就是返回参数类型,通过此参数,我们可以构造CallAdapter,并继续构造responseConverter,然后将这两个参数,以及requestFactory,callFactory(从retrofit实例中得到),如果不是协程方法,返回CallAdapted,如果是并且方法返回参数是Response,则返回SuspendForResponse,否则返回SuspendForBody,这里的三个类,都是HttpServiceMethod的子类,HttpServiceMethod实现的invoke方法,就是使用入参(CallAdapter,responseConverter,requestFactory,callFactory),构造OkHttpCall实例,然后调用adapt抽象方法,而我们刚看到的三个子类,就是实现adapt方法。CallAdapted类中,adapt方法实现很简单,直接callAdapter.adapt(call)得到我们需要的api方法的返回值,而其他两个方法,也是先调用callAdapter.adapt(call),然后使用协程,这块牵涉到kotlin协程知识,笔者对协程了解甚少,有兴趣的读者可以自己去了解。同时我们也了解到CallAdapter是如何是被使用的。
在构造CallAdapter时,我们提到了DefaultCallAdapterFactory,这里就会执行DefaultCallAdapterFactory的get方法,构造一个CallAdapter的匿名内部类实现,最终使得在调用Call.enqueue方法时,会调用callbackExecutor的execute,在其内部runnable中调用callback.onResponse或者callback.onFailure,这样enqueue方法在android端,callback的回调方法就会在主线程中执行。
我们查看一下rxjava2的CallAdapter,最后发现会执行call.execute得到实际的response,那我们看一下就看一下retrofit中Call的实现类OkHttpCall,既然提到了execute方法,那我们就查看一下。
execute方法先是调用getRawCall,得到okhttp3.Call,之前我们说的Call类,都是retrofit源码中的Call,这两者都是接口,并且内部的方法签名也基本一致,唯一的区别是retrofit的Call类声明中包含泛型参数,同时Response<T> execute()和enqueue(Callback<T> callback)也都是泛型方法。
说完Call,我们接着看getRawCall方法,其先检查OkhttpCall缓存的rawCall是否存在,存在则直接返回此实例,否则调用createRawCall方法,此方法通过requestFactory.create得到request实例,而构造此实例的方法是先使用之前解析并存储在requestFactory中有关method的各种属性构造RequestBuilder,然后调用ParameterHandler的apply,将之前解析参数注解时得到的属性设置到requestBuilder中,这里我们就可以看到ParameterHandler是怎么被使用的,以及你设置的注解是怎么生效的,完成后调用requestBuilder的build方法,得到request,然后调用callFactory.newCall,传入request,就得到了Okhttp3.Call,至于这个callFactory,那就是构建retrofit实例时设置的。
得到Okhttp3.Call实例,调用其execute方法后,得到response后,会调用parseResponse方法解析Okhttp.Response的retrofit源码中的Response<T>(这两者的区别,与之前提到的Call是类似的)。parseResponse方法中先取出body,然后根据http code进行不同的处理:
if(code < 200 || code >= 300){
返回Response.error
}
if(code == 204 || code == 205){
success,返回不带body的response,
}
调用responseConverter.convert得到ResponseBody转换类型的实例,然后返回带这个body的response
上面我们就可以看到responseBodyConverter是怎么被使用的。结合之前关于requestBodyConverter以及CallAdapter的介绍,我们可以了解到我们提供的CallAdapter是如何把默认返回的Call转换成我们自定义的类型,以及Converter是如何转换RequestBody和ResponseBody为我们自定义的类型。
之前我提到通过Invocation类获取注解path数据,那现在我们来看一下retrofit是什么时候添加Invocation的。我们回到RequestFactory的create方法创建request时,查看方法最后一行:
return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();
我们可以看到,在构造request时,通过tag方法添加了Invocation的实例,并且传入了method对象,以及参数列表,这样我们就可以在Interceptor中获取需要的数据。