1.前言
本文核心是通过对比OkHttp和Retrofit的使用方式不同。聊聊Retrofit存在的意义,已经有okhttp的情况下,为什么又在此基础上发展出了Retrofit.
将一次网络请求划分为三步
- 请求前
- 确定接口地址,收集业务参数,请求头等,即创建request
- 请求中
- 客户端与服务端建立http连接,发送request
- okhttp替开发人员完成了绝大部分工作,无须关注客户端与服务器之间是如何建立连接的
- 请求后
- 接收response,处理数据
请求前,请求后是开发人员可以自主处理的。本文将按照上述思路去分析网络请求的使用
后续的源码分析,没有使用罗列大量代码的方式去写文,想通过文章去看懂源码是不现实的,关键点引导阅读的方式可能会更有效一点,输出一些自己的理解,帮助大家阅读源码。最终还是要自己去看的。 源码分析基于retrofit:2.9.0
2.okhttp的使用
如下代码展示使用okhttp完成一次同步的网络请求,向服务器传递json参数。大致流程
- 使用线程池切换到子线程
- json序列化参数
- 收集业务参数,创建request
- 使用OkHttp发起请求
- 获取response,Json反序列化
- 处理成功和错误逻辑,切换主线程,传递数据
- UI逻辑处理
- 注:使用 异步方法可以省去线程管理
call.enqueue(callback)
可以看出流程还是很繁琐的,每一个流程点都需要开发人员手动编写逻辑。
而且如下代码是很原始,朴素的,不可能用在商业项目中。可以预见的是,如果在项目中只使用OkHttp,需要优秀的封装才可以满足日常开发需求。
然而一个优秀网络模块封装对开发人员要求是很高的,所以出现了retrofit
private val fixedThreadPool = Executors.newFixedThreadPool(2)
/**
* 发起网络请求,向服务器传递json参数
*/
private fun okhttpJson() {
fixedThreadPool.execute(Runnable() {
//请求前
val gson = Gson()
val user1 = UserEntity(1, "小明")
val jsonParams = gson.toJson(user1)
val requestBody =
RequestBody.create(MediaType.parse("application/json;charset=utf-8"), jsonParams)
val request = Request.Builder()
.url("xxx")
.post(requestBody)
.build()
//请求中
val httpClient = OkHttpClient()
val call = httpClient.newCall(request)
val response = call.execute()
//请求后
if (response.isSuccessful) {
val responseBody = response.body()
val jsonData = responseBody?.string()
val httpEntity = gson.fromJson(jsonData, HttpEntity::class.java)
if (httpEntity.code == 200) {
val data = httpEntity.data
// 请求正常
uiCallback(200,"请求成功",data)
} else {
val code = httpEntity.code
val message = httpEntity.msg
// 业务错误
uiCallback(code,message,null)
}
} else {
val code = response.code()
val message = response.message()
// 网络错误
uiCallback(code,message,null)
}
})
}
/**
* 回调函数 切换主线程
*/
private fun uiCallback(code:Int,msg:String,data:Any?){
// 仍然在子线程,可以进行其他逻辑操作
runOnUiThread(Runnable(){
// UI操作
})
}
3.Retrofit的使用
Retrofit实现一次网络请求流程如下
- 创建接口ApiService ,声明方法,通过注解标记接口地址,参数等信息
- 创建Retrofit对象
- 通过Retrofit生成ApiService 的实现类
- 调用方法,发起网络请求
- 获取response,处理成功和错误逻辑
- 切换主线程,传递数据
- UI逻辑处理
- 注:依然使用同步调用发起网络请求
interface ApiService {
@GET("article/list/{page}/json")
fun queryArticleList(
@Path("page") page: Int,
@Query("page_size") size: Int
): Call<HttpEntity<PageEntity<ArticleEntity>>>
@POST("xxx")
fun register(@Body userEntity: UserEntity) : Call<HttpEntity<Any>>
}
private val fixedThreadPool = Executors.newFixedThreadPool(2)
private fun createRetrofit() {
fixedThreadPool.execute(Runnable() {
val retrofit = Retrofit.Builder()
.baseUrl("https://www.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(ApiService::class.java)
val call = apiService.queryArticleList(page = 0, size = 10)
val response = call.execute()
if (response.isSuccessful) {
val httpEntity: HttpEntity<PageEntity<ArticleEntity>>? = response.body()
httpEntity?.let {
val code = it.code
val msg = it.msg
val page = it.data
uiCallback(code, msg, page)
}
} else {
val code = response.code()
val msg = response.message()
uiCallback(code, msg, null)
}
})
}
/**
* 回调函数 切换主线程
*/
private fun uiCallback(code: Int, msg: String, data: PageEntity<ArticleEntity>?) {
// 仍然在子线程,可以进行其他逻辑操作
runOnUiThread(Runnable() {
// UI操作
})
}
4.对比
在分别实现了OkHttp与Retrofit发起一次网络请求后,通过对比代码
- 请求前
- Retrofit把创建Request这一过程,单独拆分放到接口类。利用注解把调用接口需要的数据和抽象方法的声明结合起来,Retrofit帮助开发人员创建Request,结构清晰明了。OKHttp需要编写相对复杂的代码,创建Request,RequestBody对象
- 由于的
Converter的存在,Retrofit会自动对数据进行序列化操作,无需开发人员处理。OkHttp需要自行引入Gson等Json序列化组件,自行实现数据序列化操作
- 请求后
- 返回结果自动序列化完成,无需开发人员处理
- 目前 后续数据处理与OkHttp基本一致,(可实现
CallAdapter,结合rxjava使用,对请求后数据处理流程进行优化)
4.小结
- 个人感觉Retrofit最大的意义是建立规范
- 利用接口和注解,规范,约束开发人员如何创建request,发起一次请求
- 利用
Converter规范数据序列化方式 - 利用
CallAdapter规范请求后返回类型,如何处理数据
- 举个例子,如果使用OkHttp,仅仅创建request这一个步骤。不同项目,不同开发人员,对于同一种情况可能写出百花齐放的代码(永远不要小看人类的想象力呀! 🐶)
- 使用Retrofit之后,无论谁写代码,都被规范为一种统一格式:接口与注解。
- 分分钟上手的好嘛,只要用过Retrofit的开发人员就不需要额外讲解这东西怎么用,降低开发成本
Converter和CallAdapter也是同理,并且更加是天才构思!客户端需要与多个服务器进行交互也是常见的需求。服务器A返回数据是JSON格式,服务器B返回数据是XML格式。当出现这种情况,使用Retrofit只需要,创建两个Retrofit对象,一个添加JsonConverter 另一个添加XmlConverter即可。 如果使用OKHttp自行封装,可要了命了
5.分析原理前的思考
在看retrofit源码的时候 或者 任何一个框架的源码时 都要牢记一点,
作者设计这个框架的根本目的是什么!!!
甭管是谁,写代码肯定是为了决解问题或者达到一个目的。把握或者揣测框架作者的初心很重要,写代码肯定会遵循一定的逻辑,作者的初心就是逻辑的起点。 有起点有目的可能会走一些弯路,但不会太偏,脑子没一点思路 直接就翻源码的感觉 大家应该都经历过 🐶Doge
retrofit是基于okhttp的二次封装,虽然经过封装后api的使用已经大变样,但是核心不会变,最终还是通过okhttp的api发起网络请求。
也就是说retrofit的源码中,依然存在如okhttp的使用 一节中的代码原始代码。
很容易就会想到一些问题
- retrofit怎么变成okhttp的
- retrofit的注解怎么转化为okhttp的request
- 接口是怎么实例化成为一个可调用对象的
Converter和CallAdapter是如何工作的
6.源码分析
很头疼的一节,通过描述阅读思路的方式 提供说明引导。 源码还是得自己读!
val retrofit = Retrofit.Builder()
.baseUrl("https://www.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(ApiService::class.java)
val call = apiService.queryArticleList(page = 0, size = 10)
val response = call.execute()
val response = call.execute()从执行网络请求入手,想看execute方法内部是如何实现的- 点击进入源码发现是
retrofit2.Call接口,不能直接看现实,那么就查找 call对象是如何生成的,找到实现类 自然就可以看实现了 apiService.queryArticleList(page = 0, size = 10)方法返回call对象,但是apiService也是接口,咱开发人员自己定义,也是看不到具体实现。 那么就需要看ApiService对象是如何创建的retrofit.create(ApiService::class.*java*)create()中包含两处逻辑validateServiceInterface(service);- 非核心流程,对class参数进行校验,判断class类型是否接口,不允许是泛型类,通过循环遍历class继承的所有接口 是否为泛型类
Proxy.*newProxyInstance*- 核心流程,java动态代理,在运行时根据接口class 动态创建 接口的实现类,返回咱们要找的ApiService
- 我认为retrofit使用动态代理的主要目的是为了动态创建对象,并不是常规意义上AOP的概念,对某一个功能进行增强。源码中也不存在 代理模式中真实对象的概念
- 通过动态代理,创建接口的代理对象,每调用一个方法 最终都会调用到
InvocationHandler.invoke(Object proxy, Method method, @Nullable Object[] args)invoke方法有三个参数 分别是:当前代理对象,反射对象Method,方法参数值。 - 在这种情境下的方法 方法的作用更像一个javaBean,携带完成一次网络请求需要的所有信息
- 利用Method对象,可以拿到关于这个方法的一切信息,比如:返回值类型,方法名,方法注解,参数类型,参数注解等等。所以可以看出,invoke方法接收的是 当前调用方法的信息。开发者利用这些信息,实现自己的业务
- 在看看在接口中定义的对网络请求方法的定义。 可以想象到 Retrofit分析方法注解,参数注解,组装成OkHttp发起请求需要的request。 分析方法返回值类型,在接收网络请求数据时进行解析封装
@GET("article/list/{page}/json") fun queryArticleList(@Path("page") page: Int,@Query("page_size") size: Int ): Call<HttpEntity<PageEntity<ArticleEntity>>> @POST("xxx/aaa") fun register(@Body userEntity: UserEntity) : Observable<HttpEntity<Any>>
- 上面讨论了动态代理 和 InvocationHandler.invoke() 的作用和意义。视线拉回来,查看继续走源码流程,查看
retrofit.create()方法源码的原因是 希望找到 ApiService对象是如何创建的,retrofit2.Call接口的实现类,call.execute()方法是如何实现的- 核心方法
return ..省略.. loadServiceMethod(method).invoke(args); - 已经清楚ApiService对象 是通过动态代理创建,所有的接口方法调用最终会走到
InvocationHandler.invoke()。那么invoke()方法的返回值 就是方法声明的返回值。 - 上述示例中的
Call<HttpEntity<PageEntity<ArticleEntity>>>或Observable<HttpEntity<Any>>
- 核心方法
- 进入invoke方法,
ServiceMethod.invoke()抽象类,抽象方法 没有实现 有个静态方法ServiceMethod.parseAnnotations()看方法名的作用是注解解析 - 因为无法直接查看
ServiceMethod.invoke()所以要去查看上一步,找到ServiceMethod的实现类。即查看loadServiceMethod() loadServiceMethod()实现了map缓存, 如果缓存中没有,则解析对method对象继续宁解析,逻辑在静态方法ServiceMethod.parseAnnotations();在步骤6提过ServiceMethod.parseAnnotations();主要做了三件事- 创建
RequestFactory对象,看名字也可以推断出类的作用,用来创建request对象- 其中需要关注
create(Object[] args)方法返回值类型是okhttp3.Request RequestFactory.Builder中 包含 一系列***parseXXX()***方法,解析注解获取数据,最终组装成okhttp3.Request与之前的分析猜想几乎一致
- 其中需要关注
- 校验返回值类型
- 调用
return HttpServiceMethod.*parseAnnotations()* 因为直接return 所以这个方法一定返回ServiceMethod抽象类的子类对象。 所以其中一定包含invoke方法的实现 。
- 创建
- 进入
HttpServiceMethod发现它还是一个抽象类,表明它还会有子类实现。现在有两种思路- 直接找
invoke方法 - 先解读
HttpServiceMethod.*parseAnnotations()* - 我打算先看
invoke方法
- 直接找
invoke方法做了两件事- 创建
OkHttpCall对象。OkHttpCall实现了retrofit2.Call接口。- 这里需要注意!!! 回到最初的步骤1,2。想看
val response = call.execute()是如何实现的,进入源码发现是类型为retrofit2.Call的接口 - 经过不断的跟踪主流程,终于发现了实现类,喜大普奔
- 这里需要注意!!! 回到最初的步骤1,2。想看
- 调用抽象方法
adapt() - 所以现在又有两种选择,
- 直接看
OkHttpCall的实现 - 回过头去
HttpServiceMethod.*parseAnnotations()方法*。找HttpServiceMethod的子类是如何实现adapt()的 - 我选择去看
HttpServiceMethod.*parseAnnotations()直接去看*OkHttpCall的实现,不能算作是跟着源码流程走,如果想看实现通过AndroidStudio也能看,还是跟着流程走比较好
- 直接看
- 创建
- 进入
HttpServiceMethod.*parseAnnotations()方法 关键点如下*- 变量
boolean isKotlinSuspendFunction是否为kotlin的挂起函数,走不同的逻辑 createCallAdapter()发现一个熟悉的身影,获取callAdapter的位置被咱们找到了,让retrofit支持RxJava就是通过CallAdapter实现的。- retrofit内部维护CallAdapter实现类的List容器,初始化Retrofit对象时通过
Retrofit.Builder().addCallAdapterFactory()添加自定义的CallAdapter,就是添加到内部的List容器中 - 当程序运行到*
createCallAdapter()* 时,会比对返回值类型,遍历List,找到匹配的类型,这也是为什么Retrofit能够支持多种返回类型的原因 - retrofit提供了默认的CallAdapter实现,在
Retrofit.Builder().build()方法中调用platform.defaultCallAdapterFactories()获取默认实现。类名是DefaultCallAdapterFactory - 所以*
createCallAdapter()* 方法返回对象的真是类型就是DefaultCallAdapterFactory
- retrofit内部维护CallAdapter实现类的List容器,初始化Retrofit对象时通过
createResponseConverter()又一个熟悉的身影 用来获取Converter,逻辑于*createCallAdapter()* 一致不过多解释- 最末return语句,通过if-else语句区分是否为kotlin携程的情况返回三种类型的
HttpServiceMethod子类实现CallAdaptedSuspendForResponseSuspendForBody- !!!!!上述三个类都继承自
HttpServiceMethod实现adapt()抽象方法
- 变量
HttpServiceMethod.adapt(Call<ResponseT> call, Object[] args)有两个参数Call<ResponseT> call步骤11提到的OkHttpCall也是步骤1最初咱们要查看的val response = call.execute()的实现类- 进入
OkHttpCall.execute()内部调用了okhttp3.Call.execute()
- 进入
Object[] args追述到遥远的步骤4 动态代理部分,传入的方法参数