网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Retrofit
中create
为什么使用动态代理?- 谈谈
Retrofit
运用的动态代理及反射? Retrofit
注解是怎么进行解析的?Retrofit
如何将注解封装成OKHttp
的Call
?Rretrofit
是怎么完成线程切换和数据适配的?
Retrofit
中create
为什么使用动态代理
我们首先可以看Retrofit
代理实例创建过程,通过一个例子来说明
val retrofit = Retrofit.Builder()
.baseUrl(“https://www.baidu.com”)
.build()
val myInterface = retrofit.create(MyInterface::class.java)
创建了一个MyInterface
接口类对象,create
函数内使用了动态代理来创建接口对象,这样的设计可以让所有的访问请求都给被代理,这里我简化了下它的create函数,简单来说它的作用就是创建了一个你传入类型的接口实例
/**
*
- @param loader 需要代理执行的接口类
- @return 动态代理,运行的时候生成一个loader对象类型的类,在调用它的时候走
*/
@SuppressWarnings(“unchecked”)
public T create(final Class loader) {
return (T) Proxy.newProxyInstance(loader.getClassLoader(),
new Class<?>[]{loader}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“具体方法调用前的准备工作”);
Object result = method.invoke(object, args);
System.out.println(“具体方法调用后的善后事情”);
return result;
}
});
}
那么这个函数为什么要使用动态代理呢,这样有什么好处?
我们进行Retrofit
请求的时候构建了许多接口,并且都要调用接口中的对象;接下来我们再调用这个对象中的getSharedList
方法
val sharedListCall:Call = myService.getSharedList(2,1)
在调用它的时候,在动态代理里面,在运行的时候会存在一个函数getSharedList
,这个函数里面会调用invoke
,这个invoke
函数就是Retrofit
里的invoke
函数;并且也形成了一个功能拦截,如下图所示:
所以,相当于动态代理可以代理所有的接口,让所有的接口都走invoke函数,这样就可以拦截调用函数的值,相当于获取到所有的注解信息,也就是Request
动态变化内容,至此不就可以动态构建带有具体的请求的URL
了么,从而就可以将网络接口的参数配置归一化
这样也就解决了之前OKHttp
存在的接口配置繁琐问题,既然都是要构建Request
,为了自主动态的来完成,所以Retrofit
使用了动态代理
谈谈Retrofit
运用的动态代理及反射
那么我们在读Retrofit
源码的时候,是否都有这样一个问题,为什么我写个接口以及一些接口Api
,我们就可以完成相应的http
请求呢?Retrofit
到底在这其中做了什么工作?简单来说,其核心就是通过反射+动态代理来解决的,那么动态代理和反射的原理是怎么样的?
代理模式梳理
首先我们要明白代理模式到底是怎么样的,这里我简单梳理下
- 代理类与委托类有着同样的接口
- 代理类主要为委托类预处理消息,过滤消息,然后把消息发送给委托类,以及事后处理消息等等
- 一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,为提供特定的服务
以上是普通代理模式(静态代理)它是有一个具体的代理类来实现的
动态代理+反射
那么Retrofit
用到的动态代理呢?答案不言而喻,所谓动态就是没有一个具体的代理类,我们看到Retrofit
的create
函数中,它是可以为委托类对象生成代理类, 代理类可以将所有的方法调用分派到委托对象上反射执行,大致如下
- 接口的
classLoader
- 只包含接口的
class
数组 - 自定义的
InvocationHandler()
对象, 该对象实现了invoke() 函数, 通常在该函数中实现对委托类函数的访问
这就是在create
函数中所使用的动态代理及反射
扩展:通过这些文章,了解更多动态代理与反射
反射,动态代理在Retrofit中的运用
Retrofit的代理模式解析
Retrofit
注解是怎么进行解析的?
在使用Retrofit
的时候,或者定义接口的时候,在接口方法中加入相应的注解(@GET
,@POST
,@Query
,@FormUrlEncoded
等),然后我们就可以调用这些方法进行网络请求了;那么就有了问题,为什么注解可以完整的覆盖网络请求?我们知道,注解大致分为三类,通过请求方法、请求体编码格式、请求参数,大致有22种注解,它们基本完整的覆盖了HTTP
请求方案 ;通过它们我们确定了网络请求request的具体方案;
此时,就抛出了开始的问题,Retrofit
注解是怎么被解析的呢?这里就要熟悉Retrofit
中的ServiceMethod
类了,总的来说,它首先选择Retrofit
里提供的工具(数据转换器converter,请求适配器adapter),相当于就是具体请求Request
的一个封装,封装完成之后将注解进行解析;
下面通过官方提供例子来说明下ServiceMethod
组成的部分
其中 @GET("users/{user}/repos"
是由parseMethodAnnotation
负责解析的;@Path
参数注解就由对应ParameterHandler
进行处理,剩下的Call<List<Repo>
毫无疑问就是使用CallAdapter
将这个Call
类型适配为用户定义的 service method
的返回类型。
那么ServiceMethod
是怎么对注解进行解析的呢,来简单梳理下它的源码
- 首先,在
loadService
方法中进行检测,禁止静态方法,这里Retrofit
笔者使用的是2.9.0版本,不再是直接调用ServiceMethod.Builder()
,而是使用缓存的方式调用ServiceMethod.parseAnnotations(this, method)
,将它转为RequestFactory
对象,其实本地大同小异,原理是差不多的
final class RequestFactory {
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
…
- 同样在
RequestFactory
中也是使用Builder模式,其实就是封装了一层,传入retrofit
-method
两个参数,在这里面我们调用了Method
类获取了它的注解数组methodAnnotations
,型参的类型数组parameterTypes
,型参的注解数组parameterAnnotationsArray
Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}
- 然后在
build
方法中,它会创建一个ReqeustFactory
对象,最终解析它通过HttpServiceMethod
又转换成ServiceMethod
实例,这个方法里主要就是针对注解的解析过程,由于源码非常长,感兴趣的同学可以去详细阅读下,这里大概概括下几个重要的解析方法
parseMethodAnnotation
该方法就是确定网络的传输方式,判断加了哪些注解,下面借用一张网络上的图表达会更直观点
parseHttpMethodAndPath
,parseHeaders
我们通过上图也可以看到,其实就是解析httpMethod
和headers
,它们都是在parseMethodAnnotation
方法中被调用的,从而进行细化。前者确定的是请求方法(get,post,delete等),后者顾名思义确定的是headers
头部;前者会检测httpMethod
,它不允许有多个方法注解,会使用正则表达式进行判断,url
中的参数必须用括号占位,最终提取出了真实的url
和url
中携带的参数名称;后者就是解析当前Http
请求的头部信息
- 经过以上方法注解处理以及验证,在
build
方法中还要对参数注解进行处理
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
parameterHandlers[p] =
parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
}
它循环进行参数的验证处理,通过parseParameter
方法最后一个参数判断是否继续,参考下网络上的图示
简单说下ParameterHandler
是怎么处理这个参数注解的?它会通过进行两项校验,分别是对不确定的类型合法校验和路径名称的校验,然后就是一堆参数注解的处理,分析源码后可以看到ParameterHandler
最终都组装成了一个RequestBuilder
,那么它是用来干神马的?答案是生成OKHttp
的Request
,网络请求还是交给OKHttp
来完成
以上简单分析了下Retrofit
注解的解析过程,需要深入了解的同学请自行探索。
如果同学对注解不太熟悉,想要了解
Java注解
的相关知识点可以阅读这篇文章—>(Retrofit注解)
Retrofit
如何将注解封装成OKHttp
的call
上个问题已经知道了Retrofit
中的ServiceMethod
对会注解进行解析封装,这时候各种网络请求适配器,请求头,参数,转换器等等都准备好了,最终它会将ServiceMethod
转为Retrofit
提供的OkHttpCall
,这个就是对okhttp3.Call
的封装,答案已经呼之欲出了。
@Override
final @Nullable ReturnT invoke(Object[] args) {
Call call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
]
[外链图片转存中…(img-M54cp94R-1715794263835)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新