目录
一、Retrofit是什么?
Retrofit是一个基于OKHttp的RESTful网络请求框架,由Square公司开源,专为Android和Java提供类型安全的HTTP客户端。它可以理解为OKHttp的加强版,底层封装了OKHttp,主要负责网络请求接口的封装,使得网络请求工作更加简洁高效。
简单来说,Retrofit是OkHttp的封装,但他没有任何的网络请求功能,他只是对请求前,请求后的数据过程进行封装,网络请求功能还是在OkHttp里面。
二、为什么会出现Retrofit?它的作用是什么?
2.1 OkHttp使用上的缺陷
以前我们使用OKHttp发起请求,需要创建request,指定域名等等,每次发送请求,都需要些一大堆的内容。不够简单,不够复用。比如下面的代码,每次发送请求,都需要我们写一次Request。并且请求回来的内容,我们都需要对Response进行解析转换。
val client = OkHttpClient()
//1. 每次请求都需要创建request来指定url,参数等。
val request = Request.Builder()
.url(url)
.build()
var startTime = System.currentTimeMillis()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
val endTime = System.currentTimeMillis()
val duration = endTime - startTime
callback(null, duration)
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
val endTime = System.currentTimeMillis()
val duration = endTime - startTime
if (response.isSuccessful) {
// 2. 读取响应体,并且是Response类型的,还需要进行解析,我们希望能够得到指定类型的bean。
response.body?.string()?.let { responseBody ->
callback(responseBody, duration)
}
} else {
callback(null, duration)
println("HTTP请求失败: ${response.code}")
}
}
})
OKHttp设计非常好的,但是在使用方面,用户网络请求的接口配置繁琐,尤其是需要配置复杂请求body,请求头,参数的时候;以及数据解析过程需要用户手动拿到responsbody进行解析,不能复用;所以呢,如果使用OkHttp,我们需要在封装一次。不过,现在不用了,Retrofit正是来帮助我们解决这个问题。
我们在发起请求的时候,是操作Retrofit,所以它可以对我们的请求进行封装,并且OkHttp数据回来的时候,Retrofit又可以帮助我们解析,然后再将数据发送给我们。这就是Retrofit的作用,以及它使用再什么地方。
所以呢,网络请求的工作本质上是OkHttp完成,而Retrofit仅负责网络请求接口的封装。
App应用程序通过Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数、Header、Url等信息,之后由OkHttp完成后续的请求操作。 在服务端返回数据之后, OkHttp将原始的结果交给Retrofit, Retrofit根据用户的需求对结果进行解析。
三、那么它是如何去做这个优化的?
我们先从代码入手。如下是一个使用retrofit发起的get请求。
interface MyApi {
@GET("data") // 假设你的API端点是https://api.example.com/data
fun getData(
@Query("param1") param1: String,
@Query("param2") param2: String,
@Query("param3") param3: String? = null // param3是可选的,所以这里使用了Kotlin的可空类型
): Call<MyResponse>
}
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
// 假设你有一个全局的Retrofit实例,或者你可以在这个方法中创建它
// 注意:通常你会想要重用Retrofit实例,而不是每次请求都创建一个新的
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val myApi: MyApi = retrofit.create(MyApi::class.java)
// 发起网络请求
val call = myApi.getData("value1", "value2", "value3") // 或者对于可选参数,你可以传递null
call.enqueue(object : retrofit2.Callback<MyResponse> {
override fun onResponse(call: Call<MyResponse>, response: retrofit2.Response<MyResponse>) {
if (response.isSuccessful) {
val myResponse = response.body()
// 处理响应数据
myResponse?.let {
// 在这里使用myResponse
println("Status: ${it.status}")
it.data?.let { data ->
println("Data Field1: ${data.field1}")
println("Data Field2: ${data.field2}")
}
}
} else {
// 处理错误情况
println("Error: ${response.errorBody()?.string()}")
}
}
override fun onFailure(call: Call<MyResponse>, t: Throwable) {
// 处理请求失败的情况
println("Request failed: $t")
}
})
3.1 bulid
我们先这一部分开始讲起。
// 假设你有一个全局的Retrofit实例,或者你可以在这个方法中创建它
// 注意:通常你会想要重用Retrofit实例,而不是每次请求都创建一个新的
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
建立一个Retrofit对象的标准,配置好Retrofit类里的成员变量,像baseUrl,response的数据转换器等等。方便我们后面进行组装request或解析response的时候使用。
1)baseUrl("api.example.com/") :方法用于设置所有网络请求的基础URL。这意味着,当你定义接口中的方法时,你只需要指定相对于这个基础URL的路径部分。Retrofit 会自动将基础URL和路径部分拼接起来形成完整的请求URL。
2).addConverterFactory(GsonConverterFactory.create()) 方法用于添加Gson转换器工厂。Gson是一个Google提供的Java库,用于将Java对象序列化为JSON字符串,以及将JSON字符串反序列化为Java对象。通过添加Gson转换器工厂,Retrofit 能够自动地将请求体(通常是Java对象)转换为JSON字符串发送给服务器,并将服务器返回的JSON字符串自动转换为Java对象。
3)最后,通过调用 .build() 方法,我们根据前面的配置构建了一个 Retrofit 实例。这个实例随后可以用来创建服务接口(Service Interface)的实例。
3.2 create
val myApi: MyApi = retrofit.create(MyApi::class.java)
Retrofit会分析你传入的接口(MyApi),查找并解析所有使用Retrofit注解标记的方法。这些注解提供了关于如何执行HTTP请求的必要信息。
基于接口的分析结果,Retrofit会使用Java的动态代理机制生成一个实现了MyApi接口的代理对象。这个代理对象会在你调用接口中的方法时,自动地构建并执行相应的HTTP请求。
点击create方法进去源码
可以看到,create 方法通过newProxyInstance方法创建一个实现了MyApi接口的代理对象,因为接口不能直接调用方法。所以它创建了一个代理,可以看到使用的时候动态代理。动态,就是运行时的意思。在程序运行过程中创建了一个对象。
我们可以看到它返回一个对象,使用classLoader类加载器来生成一个对象,返回的对象就是实现了这个接口的对象。
(1)invoke方法
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// 如果是Object类的方法(如toString(), hashCode(), equals()等),那么不做特殊的处理。
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
args = args != null ? args : emptyArgs;
//isDefaultMethod是判断接口的方法是否有默认的实现,如果有默认的实现,那么就执行默认的实现,如果没有则走loadServiceMethod,这个方法下面我们特殊讲解一下。
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
为什么要使用invoke方法?也就是为什么要拦截?这样就可以拦截到接口的所有信息,注解、参数、返回值,就是用来构建request的动态变化内容。动态构建URL。
(2)loadServiceMethod
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
调用loadServiceMethod方法构建一个ServiceMethod,这里使用了缓存,为什么使用缓存?因为ServiceMethod里面使用了反射,我们都知道反射是比较消耗性能的,所以他们创建以后,就使用一个map集合来存储起来。
那么ServiceMethod究竟是做什么?
3.3 ServiceMethod
ServiceMethod是什么?它主要是:
- 注解解析:通过Java反射机制解析接口方法上的注解(如@GET、@POST等),根据这些注解生成OkHttp所需的Request参数。
- 创建请求:基于解析得到的注解信息和方法的参数,ServiceMethod负责构造完整的HTTP请求。这包括构建请求的URL(可能包含路径变量和查询参数)、设置请求头、以及准备请求体(对于POST、PUT等请求方法)。
这几点,我们可以在ServiceMethod里面看到。
现在 ServiceMethod 创建好了,我们要用他来构造一个 OkHttpCall。
OkHttpCall是什么?我们知道,如果要发起请求,那么就一定要调用OkHttp的call,然后调用call的enqueue方法发请求。所以OkHttpCall是Retrofit中对OkHttp中Call接口的封装。它实现了Retrofit的Call接口,并内部持有一个OkHttp的Call对象。
为什么要创建一个OkHttpCall?这样做的好处是,Retrofit可以在不直接依赖OkHttp具体实现的情况下,提供一套自己的网络请求API,从而增加了代码的抽象层次和灵活性。
OkHttpCall在请求发送前会负责填充请求所需的所有信息,包括URL、请求头、请求体等。在收到响应后,它还会负责处理响应数据,将其转换为开发者期望的格式(如Java对象)。
这篇文章就介绍到这里。