Retrofit 是一个类型安全的 HTTP 客户端库,用于 Android 和 Java 应用程序,由 Square 公司开发。它基于 OkHttp构建,提供了简洁的注解驱动的 API 来定义 HTTP 请求。Retrofit 允许你通过接口和注解来声明 HTTP 请求,并将响应转换为 Java 对象。
1, Retrofit的关键特性:
类型安全:通过接口和注解定义请求,确保在编译时捕获潜在的错误。
简洁的 API:使用注解(如 @GET, @POST 等)来定义请求方法和 URL。
易于集成:与 OkHttp 无缝集成,可以共享配置和拦截器。
转换器支持:支持多种数据格式的转换(如 JSON, XML 等)。
拦截器:允许在请求发送前和响应接收后进行自定义处理。
2, Retrofit的核心组件:
Retrofit:Retrofit 的入口类,负责配置和创建 Service 接口的实现。
Service 接口:定义 HTTP 请求的接口,使用注解来描述请求的方法、URL、参数等。
CallAdapter:将 Call 对象转换为其他类型,例如 Observable(用于 RxJava)。
Converter:负责将 HTTP 响应体转换为 Java 对象,以及将 Java 对象转换为请求体。
3, 使用retrofit进行网络请求的代码示例如下。
首先需要定义接口,代码示例如下:
kotlin
//定义service接口
package com.eric.kotlin
// 导入了 Retrofit 库中的 GET 注解。
// Retrofit 是一个类型安全的 HTTP 客户端,用于简化与 RESTful API 的交互。
// GET 注解是 Retrofit 提供的,用于标记一个方法为 HTTP GET 请求。
import retrofit2.http.GET
interface EricApiService {
// @GET 是 Retrofit 的注解,用于指定该方法对应的 HTTP 请求类型为 GET。
// "users" 是请求的相对路径。当使用 Retrofit 构建 API 调用时,
// 这个路径会与 Retrofit 实例中设置的基础 URL 组合成完整的请求 URL。
// 例如,如果基础 URL 是 https://example.com/api/,
// 那么这个请求的完整 URL 就是 https://example.com/api/users。
// suspend 是 Kotlin 协程中的关键字,用于标记一个挂起函数。
// 挂起函数可以在不阻塞当前线程的情况下暂停执行,等待某个操作完成后再继续执行。
// 在 Android 开发或其他异步编程场景中,使用 suspend 函数可以更方便地处理异步操作,避免回调地狱。
@GET("users")
suspend fun getUsers(): List<EricUser>
}
在上面的接口中看返回值有EricUser类,需要定义相关的数据类,代码示例如下:
kotlin
//定义数据类
package com.eric.kotlin
// 导入 kotlinx.serialization 库中的 Serializable 注解。
// kotlinx.serialization 是 Kotlin 官方提供的一个用于序列化和反序列化 Kotlin 对象的库,
// Serializable 注解则是该库的核心注解之一,用于标记一个类可以被序列化和反序列化。
import kotlinx.serialization.Serializable
// @Serializable 注解被应用于 EricUser 类,表明这个类的实例可以被序列化为某种格式(如 JSON、XML 等), 也可以从相应的格式反序列化为 EricUser 对象。
// 在使用 kotlinx.serialization 库进行序列化和反序列化操作时,只有被 @Serializable 注解标记的类才能被处理。
@Serializable
// data class 是 Kotlin 中的一种特殊类,它会自动为类生成一些常用的方法,
// 包括 equals()、hashCode()、toString() 和 copy() 方法。
// 这些方法的实现基于类的属性,使得数据类在处理数据时更加方便和简洁。
data class EricUser(val name: String, val email: String)
为反映网络请求状态,需要再定义一个密封类,代码示例如下:
kotlin
package com.eric.kotlin
// 定义了一个密封类 Result,它用于封装网络请求或其他异步操作的结果状态。
// 密封类是 Kotlin 中一种特殊的类,它的子类是固定的,并且必须在与密封类相同的文件中声明。
// sealed class 关键字用于定义密封类。
// 密封类的主要作用是限制继承关系,所有的子类必须在密封类的同一文件中定义,
// 这样在进行 when 表达式匹配时,编译器可以确保所有可能的情况都被处理,避免出现未处理的分支。
// <out T> 是 Kotlin 的协变类型参数声明。
// out 关键字表示类型参数 T 是协变的,即如果 A 是 B 的子类型,那么 Result<A> 也是 Result<B> 的子类型。这使得 Result 类在处理不同类型的数据时更加灵活。
// EricResult 类有三个子类:Loading、Success 和 Error,分别表示操作正在进行、操作成功和操作失败三种状态。
sealed class EricResult<out T> {
// Nothing 是 Kotlin 中的一个特殊类型,表示没有任何值,在这里使用 Nothing 表示 Loading 状态不携带任何具体的数据。
data object Loading : EricResult<Nothing>()
// <out T> 表示 Success 类也是协变的。val data: T 是一个只读属性,用于存储操作成功后返回的数据。
// 当异步操作成功完成时,可以使用 Result.Success(data) 来表示操作成功,并携带具体的数据。
data class Success<out T>(val data: T) : EricResult<T>()
// Error 类用于表示操作失败的状态。val exception: Throwable 是一个只读属性,用于存储操作失败时抛出的异常信息。
// EricResult<Nothing> 表示 Error 状态不携带其他额外的数据,只关注异常信息。
data class Error(val exception: Throwable) : EricResult<Nothing>()
}
接着需要创建service实例,代码示例如下:
kotlin
//创建service实例
package com.eric.kotlin
// Retrofit 是一个用于构建类型安全的 HTTP 客户端的核心类,通过它可以方便地与 RESTful API 进行交互。
import retrofit2.Retrofit
// GsonConverterFactory 是 Retrofit 的一个数据转换器工厂,
// 用于将服务器返回的 JSON 数据转换为 Kotlin 对象,以及将 Kotlin 对象转换为 JSON 数据发送给服务器。
import retrofit2.converter.gson.GsonConverterFactory
// object 关键字在 Kotlin 中用于定义单例对象。单例模式确保一个类只有一个实例,并提供一个全局访问点。
object EricNetworkClient {
// https://jsonplaceholder.typicode.com/ 是一个免费的、公开的 RESTful API 服务,专门为开发者提供模拟数据,在开发和测试过程中具有广泛的用途。
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
// by lazy 是 Kotlin 提供的一种延迟初始化方式。
// 使用 lazy 委托的属性会在第一次被访问时进行初始化,而不是在对象创建时就初始化。这样可以避免不必要的资源开销,提高性能。
val ericApi: EricApiService by lazy {
// Retrofit.Builder()用于创建一个 Retrofit 构建器实例,用于配置 Retrofit 客户端的各项参数。
Retrofit.Builder()
// 设置 Retrofit 客户端的基础 URL,使用前面定义的 BASE_URL 常量。
.baseUrl(BASE_URL)
// 添加 GsonConverterFactory 作为数据转换器工厂,使得 Retrofit 能够自动处理 JSON 数据的序列化和反序列化。
.addConverterFactory(GsonConverterFactory.create())
// 调用 build() 方法构建 Retrofit 客户端实例。
.build()
// 通过 create() 方法创建 EricApiService 接口的实现对象。
// Retrofit 会根据接口中定义的注解和方法,动态生成一个实现类,用于处理具体的网络请求。
.create(EricApiService::class.java)
}
}
下面需要发起网络请求,代码示例如下:
kotlin
package com.eric.kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow // Kotlin 协程库中用于表示异步数据流的类型
import kotlinx.coroutines.flow.flow // 是一个构建 Flow 的工厂函数,用于创建一个冷流。
class EricDataRepository {
// 创建了一个私有属性 apiService,它是 EricApiService 接口的实例,通过 EricNetworkClient 单例对象获取。
private val apiService = EricNetworkClient.ericApi
// 使用 Flow 包装网络请求
fun getUsers() = flow { // 使用 flow 构建一个冷流,冷流只有在被收集时才会开始执行。
emit(EricResult.Loading)
try {
val users = apiService.getUsers() // 调用挂起函数
// 发射一个 EricResult.Success 状态,并携带获取到的用户数据。
emit(EricResult.Success(users))
} catch (e: Exception) {
// 发射一个 EricResult.Error 状态,并携带捕获到的异常。
emit(EricResult.Error(e))
}
}
// 带重试的版本
fun getUsersWithRetry(retries: Int = 3) = flow {
var retryCount: Long = 0
while (true) {
emit(EricResult.Loading)
try {
val users = apiService.getUsers()
emit(EricResult.Success(users))
break
} catch (e: Exception) {
if (retryCount++ >= retries) {
emit(EricResult.Error(e))
break
}
delay(1000 * retryCount)
}
}
}
}
最后接收请求的数据,代码示例如下:
kotlin
package com.eric.kotlin
// encodeToString是一个扩展函数,用于将一个可序列化的对象编码为字符串,通常用于将对象序列化为 JSON 格式。
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString
// Json是 kotlinx.serialization 库中用于处理 JSON 序列化和反序列化的核心类,它提供了一系列方法来完成 JSON 数据的编码和解码操作。
import kotlinx.serialization.json.Json
fun main() {
runBlocking {
val repository = EricDataRepository()
// 获取 Flow:调用 repository.getUsers() 方法,
// 该方法返回一个 Flow<EricResult<List<EricUser>>> 类型的数据流。
val flow = repository.getUsers()
// 收集 Flow:使用 collect 方法对 Flow 进行收集。
// collect 是一个挂起函数,它会依次处理 Flow 中发射的每个元素。
try {
flow.collect {
result ->
when(result) {
is EricResult.Loading -> println("loading")
is EricResult.Error -> println("error:${result.exception.message}")
is EricResult.Success -> println("success:${result.data}")
}
}
} catch (e: Exception) {
println("Exception occurred while collecting flow: ${e.message}")
}
val flow1 = repository.getUsersWithRetry(3)
try {
flow1.collect {
result ->
when(result) {
is EricResult.Loading -> println("loading1")
is EricResult.Error -> println("error1:${result.exception.message}")
is EricResult.Success -> println("success1:${result.data}")
}
}
} catch (e: Exception) {
println("Exception occurred while collecting flow: ${e.message}")
}
val user = EricUser("Eric", "eric@163.com")
// 调用了 Json 类的 encodeToString 方法,将 user 对象序列化为 JSON 格式的字符串,并将结果赋值给 jsonUser 变量。
val jsonUser = Json.encodeToString(user)
println("json serialized:$jsonUser")
// 调用了 Json 类的 decodeFromString 方法,将之前序列化得到的 JSON 字符串 jsonUser 反序列化为 EricUser 类型的对象。
val deJsonUser = Json.decodeFromString<EricUser>(jsonUser)
println("deJsonUser:$deJsonUser")
}
}
上面代码的运行结果如下:
less
loading
success:[EricUser(name=Leanne Graham, email=Sincere@april.biz), ...]
loading1
success1:[EricUser(name=Leanne Graham, email=Sincere@april.biz), ...]
json serialized:{"name":"Eric","email":"eric@163.com"}
deJsonUser:EricUser(name=Eric, email=eric@163.com)
4, 核心模块与流程
动态代理(Proxy):Retrofit 通过 Proxy.newProxyInstance() 创建接口的代理对象,所有接口方法调用均被路由到 InvocationHandler 的 invoke() 方法。
typescript
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] { service },
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) {
// 解析方法注解,构建 ServiceMethod
ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method);
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall); // 通过 CallAdapter 转换返回值
}
});
}
ServiceMethod:请求解析核心。
每个接口方法对应一个 ServiceMethod,负责解析注解和参数:
注解解析:通过 parseAnnotations() 解析方法上的 @GET、@POST 等注解,构建 HTTP 请求模板。
参数处理:使用 ParameterHandler 处理方法参数(如 @Path、@Query),动态填充 URL 和请求体。
java
abstract class ServiceMethod<T, R> {
static <T, R> ServiceMethod<T, R> parseAnnotations(Retrofit retrofit, Method method) {
// 解析方法注解,生成 RequestFactory
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
// 解析返回类型,确定 CallAdapter 和 Converter
Type returnType = method.getGenericReturnType();
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
}
OkHttpCall:请求执行。
封装 OkHttp 的 Call 对象,处理异步/同步请求。
java
final class OkHttpCall<T> implements Call<T> {
@Override public Response<T> execute() {
okhttp3.Call call = createRawCall(); // 创建 OkHttp 原生 Call
return parseResponse(call.execute()); // 解析响应
}
}
Converter:数据序列化。
作用:将 HTTP 请求体/响应体与 Java 对象相互转换。
csharp
public interface Converter<F, T> {
T convert(F value) throws IOException;
}
实现类GsonConverterFactory 使用 Gson 解析 JSON。
CallAdapter:异步处理适配。
作用:将 Call 适配为其他类型(如 Observable、CompletableFuture)。
csharp
public interface CallAdapter<R, T> {
Type responseType();
T adapt(Call<R> call);
}
实现类RxJava3CallAdapter 将 Call 转换为 RxJava 的 Observable。
5, 源码模块分析。
Retrofit.Builder:
Retrofit.Builder 是用于创建 Retrofit 实例的构建器。它允许你配置基 URL、转换器、调用适配器(如 RxJava)等。
kotlin
public final class Retrofit {
// ...
public static final class Builder {
// 配置项,如baseUrl, converters, callAdapterFactories等
private HttpUrl baseUrl;
private List<Converter.Factory> converterFactories = new ArrayList<>();
private List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
// ...
public Builder baseUrl(HttpUrl baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public Builder addConverterFactory(Converter.Factory converterFactory) {
converterFactories.add(converterFactory);
return this;
}
// ... 其他配置方法
public Retrofit build() {
// 校验配置并创建Retrofit实例
// ...
return new Retrofit(this);
}
}
// ...
}
Retrofit 实例:
Retrofit 实例持有配置信息,并提供用于创建 API 接口代理的方法。
java
final class Retrofit {
// 配置项
final HttpUrl baseUrl;
final List<Converter.Factory> converterFactories;
final List<CallAdapter.Factory> callAdapterFactories;
// ...
Retrofit(Builder builder) {
// 初始化配置项
// ...
}
// 创建 API 接口代理的方法
<T> T create(final Class<T> service) {
// 使用动态代理创建接口实例
Utils.validateServiceInterface(service);
if (RxJavaCallAdapterFactory.class.isAssignableFrom(service)) {
throw new IllegalArgumentException(...);
}
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] { service },
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// 处理接口方法的调用
// ...
}
});
}
// ...
}
在 invoke 方法中,Retrofit 会解析接口方法的注解,构建 OkHttp Request 对象,并使用配置的 Converter 和 CallAdapter 来处理请求和响应。
注解处理:
Retrofit 使用注解(如 @GET, @POST 等)来定义 HTTP 请求。这些注解在运行时通过反射被解析,以构建请求。
less
@Documented // @Documented:表示该注解会被包含在 JavaDoc 中。
@Target(METHOD) // @Target(ElementType.METHOD):指定该注解只能应用于方法上。
@Retention(RUNTIME) // @Retention(RetentionPolicy.RUNTIME):表示该注解在运行时可见,这样 Retrofit 可以在运行时通过反射获取该注解的信息。
public @interface GET {
String value() default ""; // String value() default "";:定义了一个名为 value 的属性,用于指定请求的 URL 路径,默认值为空字符串。
}
在 invoke 方法中,Retrofit 会检查方法的注解,并根据注解类型(如 @GET, @POST)和值(如 URL 路径)来构建请求。
下面分析下代理模式和invoke方法。
代理模式与 invoke 方法: Retrofit 使用代理模式创建 Service 接口的实现。当调用 Service 接口的方法时,实际上会调用代理对象的 invoke 方法。
下面是简化后的 invoke 方法代码:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class RetrofitInvocationHandler implements InvocationHandler {
private final Retrofit retrofit;
public RetrofitInvocationHandler(Retrofit retrofit) {
this.retrofit = retrofit;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是 Object 类的方法,直接调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 创建 ServiceMethod 对象
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
// 创建 OkHttpCall 对象
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
// 返回 CallAdapter 转换后的对象
return serviceMethod.callAdapter.adapt(okHttpCall);
}
private ServiceMethod<?, ?> loadServiceMethod(Method method) {
// 从缓存中获取 ServiceMethod 对象,如果不存在则创建
ServiceMethod<?, ?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder<>(retrofit, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
}
下面再分析下注解解析与请求构建:
在 ServiceMethod.Builder 类的 build 方法中,会对方法的注解进行解析,并根据注解类型和值构建请求。
下面是简化后的 build 方法代码:
ini
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import retrofit2.http.GET;
import retrofit2.http.POST;
public class ServiceMethodBuilder {
private final Retrofit retrofit;
private final Method method;
private String httpMethod;
private String relativeUrl;
public ServiceMethodBuilder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
}
public ServiceMethod build() {
// 获取方法的注解
Annotation[] methodAnnotations = method.getAnnotations();
for (Annotation annotation : methodAnnotations) {
if (annotation instanceof GET) {
httpMethod = "GET";
relativeUrl = ((GET) annotation).value();
} else if (annotation instanceof POST) {
httpMethod = "POST";
relativeUrl = ((POST) annotation).value();
}
// 其他注解处理...
}
// 解析方法参数的注解
Annotation[][] parameterAnnotationsArray = method.getParameterAnnotations();
List<ParameterHandler<?>> parameterHandlers = new ArrayList<>();
for (int i = 0; i < parameterAnnotationsArray.length; i++) {
Annotation[] parameterAnnotations = parameterAnnotationsArray[i];
if (parameterAnnotations != null) {
// 处理参数注解,如 @Path、@Query 等
// ...
}
}
// 构建 ServiceMethod 对象
return new ServiceMethod.Builder()
.httpMethod(httpMethod)
.relativeUrl(relativeUrl)
.parameterHandlers(parameterHandlers)
.build();
}
}
获取方法注解:在 build 方法中,首先通过 method.getAnnotations() 获取方法的所有注解。然后遍历这些注解,根据注解的类型(如 @GET、@POST)来确定 HTTP 请求方法和相对 URL 路径。
解析参数注解:通过 method.getParameterAnnotations() 获取方法参数的注解数组。对于每个参数的注解数组,会进一步解析其中的注解,如 @Path、@Query 等,以确定请求参数的处理方式。
构建请求:根据解析得到的 HTTP 请求方法、相对 URL 路径和参数处理方式,构建 ServiceMethod 对象。ServiceMethod 对象封装了请求的所有信息,后续会用于创建 OkHttpCall 对象,最终发起网络请求。
请求构建:
一旦确定了请求方法(如 GET, POST)和 URL,Retrofit 会使用 OkHttp 来构建和发送请求。这包括设置请求头、请求体(对于 POST 请求)等。
在 Retrofit 的源码中,ServiceMethod 类负责解析接口方法的注解信息,构建请求。具体来说,HttpServiceMethod 类继承自 ServiceMethod,负责处理 HTTP 请求。 当调用 Retrofit.create() 方法创建接口实例时,Retrofit 会使用动态代理生成接口的实现类。在代理类中,会调用 ServiceMethod 的 invoke() 方法,该方法会根据注解信息构建请求,并使用 OkHttp 发送请求。
响应处理:
当 OkHttp 接收到响应时,Retrofit 会使用配置的 Converter 来将响应体转换为 Java 对象。这通常涉及 JSON 或其他格式的解析。 在 Retrofit 中,当 OkHttp 接收到响应后,Retrofit 会利用配置的 Converter 将响应体转换为 Java 对象。整个过程主要涉及到以下几个关键组件: Converter.Factory:用于创建 Converter 实例,不同的 Converter.Factory 可以处理不同的数据格式,例如 GsonConverterFactory 用于处理 JSON 数据。 Converter:负责将响应体从一种格式转换为另一种格式,通常是将响应体的字节流转换为 Java 对象。 CallAdapter:用于适配 Call 对象,将 Call 转换为其他类型,例如 Observable(用于 RxJava)。
在创建 Retrofit 实例时,我们通常会配置 Converter.Factory,例如使用 GsonConverterFactory,即调用addConverterFactory(GsonConverterFactory.create())
在 Retrofit.Builder 的 addConverterFactory 方法中,会将传入的 Converter.Factory 添加到 converterFactories 列表中。 响应处理的核心逻辑在 HttpServiceMethod 类中 HttpServiceMethod 是 ServiceMethod 的子类,负责处理 HTTP 请求和响应。在 HttpServiceMethod 的 toResponse 方法中,会调用 responseConverter 进行响应体的转换:
@Override
protected Response<T> toResponse(okhttp3.Response rawResponse) throws IOException {
return Response.success(
responseConverter.convert(rawResponse.body()), rawResponse);
}
responseConverter 是一个 Converter<ResponseBody, T> 类型的对象,它是通过 Retrofit 实例的 nextResponseBodyConverter 方法获取的:
ini
responseConverter = retrofit.responseBodyConverter(responseType, methodAnnotations);
nextResponseBodyConverter 方法会遍历 converterFactories 列表,找到第一个能够处理该响应类型的 Converter.Factory,并调用其 responseBodyConverter 方法创建 Converter 实例:
less
public <T> Converter<ResponseBody, T> nextResponseBodyConverter(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter<ResponseBody, T>) converter;
}
}
...
}
以 GsonConverterFactory 为例 GsonConverterFactory 是一个用于处理 JSON 数据的 Converter.Factory,它的 responseBodyConverter 方法会创建一个 GsonResponseBodyConverter 实例:
typescript
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
GsonResponseBodyConverter 实现了 Converter<ResponseBody, T> 接口,它的 convert 方法会使用 Gson 解析响应体的 JSON 数据:
java
@Override
public T convert(ResponseBody value) throws IOException {
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
return adapter.read(jsonReader);
} finally {
value.close();
}
}
踪上可知,Retrofit 的响应处理过程主要依赖于配置的 Converter.Factory 和 Converter。当 OkHttp 接收到响应后,Retrofit 会遍历 converterFactories 列表,找到合适的 Converter.Factory 并创建 Converter 实例,然后使用该 Converter 将响应体转换为 Java 对象。这种设计使得 Retrofit 具有很好的扩展性,可以方便地支持不同的数据格式。
错误处理:
Retrofit 提供了简洁的错误处理机制。如果请求失败(如网络错误、HTTP 错误状态码等),它会抛出一个 HttpException 或 IOException。你可以通过 try-catch 块来捕获和处理这些异常。
在 Retrofit 中,当请求失败时,会根据不同的错误情况抛出相应的异常,常见的有 HttpException 和 IOException。在使用 Retrofit 发起请求时,无论是同步还是异步请求,都有对应的错误处理逻辑。下面从源码层面逐步分析。
同步请求的错误处理:
同步请求的调用入口
当我们使用 Call 对象的 execute() 方法发起同步请求时,会进入 OkHttpCall 类的 execute() 方法。
java
@Override public Response<T> execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
}
captureCallStackTrace();
try {
// 调用 serviceMethod 的 invoke 方法执行请求
return serviceMethod.toResponse(okHttpCall.execute());
} catch (IOException e) {
// 这里捕获到的 IOException 会抛给调用者
throw exceptionCatchingBody(e);
}
}
在这个方法中,会调用 okHttpCall.execute() 执行实际的网络请求,这是 OkHttp 的请求执行方法。如果在这个过程中发生网络错误,比如连接失败、超时等,会抛出 IOException。
toResponse 方法中的错误处理:
serviceMethod.toResponse(okHttpCall.execute()) 方法会尝试将 OkHttp 的响应转换为 Retrofit 的 Response 对象。在 HttpServiceMethod 的 toResponse 方法中:
java
@Override
protected Response<T> toResponse(okhttp3.Response rawResponse) throws IOException {
// 检查响应状态码
if (!rawResponse.isSuccessful()) {
try (ResponseBody bufferedBody = Utils.buffer(rawResponse.body())) {
// 如果响应状态码不是 200 - 399,抛出 HttpException
throw new HttpException(Response.error(bufferedBody, rawResponse));
}
}
return Response.success(
responseConverter.convert(rawResponse.body()), rawResponse);
}
这里会检查响应的状态码,如果状态码不在 200 - 399 范围内,会将响应体包装成 Response.error 对象,并抛出 HttpException。
异步请求的错误处理:
异步请求的调用入口
当我们使用 Call 对象的 enqueue() 方法发起异步请求时,会进入 OkHttpCall 类的 enqueue() 方法。
typescript
@Override public void enqueue(final Callback<T> callback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
}
captureCallStackTrace();
okHttpCall.enqueue(new okhttp3.Callback() {
@Override public void onFailure(okhttp3.Call call, IOException e) {
// 处理网络错误
callFailure(exceptionCatchingBody(e));
}
@Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
try {
// 处理响应
Response<T> response = parseResponse(rawResponse);
callSuccess(response);
} catch (Throwable t) {
// 处理解析响应时的错误
callFailure(t);
}
}
});
}
在 enqueue() 方法中,会调用 OkHttp 的 enqueue() 方法发起异步请求。当请求失败时,会调用 onFailure 方法,这里会捕获到 IOException,并调用 callFailure 方法将错误传递给 Retrofit 的回调。
parseResponse 方法中的错误处理:
parseResponse 方法用于解析 OkHttp 的响应,在 OkHttpCall 类中:
scss
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
if (code < 200 || code >= 300) {
try {
// Buffer the entire body to avoid future I/O.
ResponseBody bufferedBody = Utils.buffer(rawBody);
// 如果状态码不在 200 - 399 范围内,抛出 HttpException
throw new HttpException(Response.error(bufferedBody, rawResponse));
} finally {
rawBody.close();
}
}
if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
// 调用 responseConverter 转换响应体
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}
在这个方法中,同样会检查响应状态码,如果状态码不在 200 - 399 范围内,会抛出 HttpException。同时,在使用 responseConverter 转换响应体时,如果发生异常,也会进行相应的处理。
综上可知,从源码角度来看,Retrofit 的错误处理机制主要依赖于 OkHttp 的网络请求结果和状态码检查。在同步请求中,会直接抛出 IOException 或 HttpException;在异步请求中,会通过回调将错误信息传递给调用者。这样的设计使得开发者可以方便地使用 try-catch 块捕获和处理这些异常,从而实现错误处理逻辑。
6, Retrofit 中使用到的设计模式
代理模式:
Retrofit 使用代理模式来创建 Service 接口的实现。当调用 retrofit.create(GitHubService.class) 时,Retrofit 会动态生成一个实现了 GitHubService 接口的代理对象。
scss
// Retrofit 中的 create 方法
public <T> T create(final Class<T> service) {
// 检查接口是否合法
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override
public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// 如果是 Object 类的方法,直接调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
// 创建 ServiceMethod 对象
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
// 创建 OkHttpCall 对象
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
// 返回 CallAdapter 转换后的对象
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
建造者模式:
Retrofit 使用建造者模式来配置 Retrofit 实例。通过 Retrofit.Builder 类,可以设置基础 URL、CallAdapterFactory、ConverterFactory 等。
scss
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
策略模式:
Retrofit 使用策略模式来处理不同的请求和响应转换。CallAdapterFactory 和 ConverterFactory 就是策略模式的体现,不同的工厂类可以实现不同的转换策略。
scss
// 添加 Gson 转换器工厂
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
7, 设计模式在Retrofit中如何实现解耦
在 Retrofit 中,多种设计模式的运用有效地实现了解耦,以下分别从代理模式、建造者模式、策略模式三个方面详细分析其如何实现解耦。
代理模式:
解耦原理:
代理模式在 Retrofit 中用于创建 Service 接口的动态代理对象。它将 Service 接口的定义与具体的实现逻辑分离开来,客户端只需要关注 Service 接口的方法调用,而不需要了解实际的网络请求处理过程。这样,Service 接口的定义可以独立于网络请求的实现,实现了接口定义和请求处理逻辑的解耦。 代码示例及解耦体现
less
// 定义 Service 接口
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
// 创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
// 创建 Service 实例
GitHubService service = retrofit.create(GitHubService.class);
// 发起请求
Call<List<Repo>> call = service.listRepos("octocat");
在上述代码中,GitHubService 接口定义了网络请求的方法和参数,客户端只需要调用 service.listRepos("octocat") 方法即可发起请求。而具体的网络请求实现是由 Retrofit 通过代理模式动态生成的,客户端无需关心这些细节。如果需要更换网络请求的实现方式,只需要修改 Retrofit 的配置,而不会影响到 GitHubService 接口的定义和客户端的调用代码。
建造者模式
解耦原理:
建造者模式用于配置 Retrofit 实例。它将 Retrofit 的配置过程与使用过程分离开来,允许用户通过链式调用的方式逐步配置 Retrofit 的各种参数,如基础 URL、CallAdapterFactory、ConverterFactory 等。这样,Retrofit 的配置逻辑可以独立于其使用逻辑,实现了配置和使用的解耦。 代码示例及解耦体现
scss
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
在上述代码中,Retrofit.Builder 类提供了一系列的配置方法,用户可以根据需要选择配置哪些参数。如果需要修改配置,只需要在 Builder 中进行修改,而不会影响到使用 Retrofit 实例的代码。例如,如果需要更换 JSON 解析器,只需要将 GsonConverterFactory 替换为其他的 ConverterFactory 即可,而不会影响到 Service 接口的定义和请求的调用。
策略模式
解耦原理:
策略模式在 Retrofit 中用于处理不同的请求和响应转换。CallAdapterFactory 和 ConverterFactory 就是策略模式的体现,不同的工厂类可以实现不同的转换策略。通过将转换策略封装在不同的工厂类中,Retrofit 可以根据需要选择不同的策略,实现了转换策略与网络请求核心逻辑的解耦。 代码示例及解耦体现
scss
// 添加 Gson 转换器工厂
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 若要更换为 Moshi 转换器工厂
Retrofit retrofitWithMoshi = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(MoshiConverterFactory.create())
.build();
在上述代码中,GsonConverterFactory 和 MoshiConverterFactory 分别实现了不同的 JSON 解析策略。通过在 Retrofit.Builder 中添加不同的 ConverterFactory,可以轻松地切换 JSON 解析器,而不会影响到 Retrofit 的其他部分。同样,CallAdapterFactory 也可以根据需要选择不同的适配器,如 RxJava2CallAdapterFactory 用于将 Call 对象转换为 Observable 对象,实现了与不同异步编程库的解耦。 综上所述,代理模式、建造者模式和策略模式在 Retrofit 中分别从接口定义与实现、配置与使用、转换策略与核心逻辑等方面实现了解耦,使得 Retrofit 具有良好的可维护性和扩展性。
8, OkHttp 在 Retrofit 中的作用
底层网络请求实现:
Retrofit 是一个类型安全的 HTTP 客户端,它本身并不直接处理网络请求,而是依赖于 OkHttp 来完成实际的网络通信。OkHttp 是一个高效的 HTTP 客户端,它提供了强大的网络请求功能,包括连接池管理、请求重试、缓存策略等。Retrofit 将用户定义的 HTTP 请求转换为 OkHttp 可以处理的请求对象,然后由 OkHttp 负责将请求发送到服务器并接收响应。
性能优化:
OkHttp 具有许多性能优化特性,如连接池复用、HTTP/2 支持、GZIP 压缩等。在 Retrofit 中使用 OkHttp 可以充分利用这些特性,提高网络请求的性能和效率。例如,连接池复用可以减少 TCP 连接的建立和销毁开销,从而加快请求的响应速度。
拦截器机制:
OkHttp 提供了拦截器机制,允许开发者在请求发送和响应接收的过程中插入自定义的逻辑。在 Retrofit 中使用 OkHttp 可以方便地添加拦截器,用于日志记录、请求头添加、请求重试等功能。通过拦截器,开发者可以对网络请求进行更加灵活的控制和处理。
OkHttp 在 Retrofit 中的使用方式:
默认使用:
当不进行额外配置时,Retrofit 会默认使用 OkHttp 作为底层网络请求库。
java
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Path;
import java.util.List;
// 定义 Service 接口
interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
class Repo {
// 定义 Repo 类的属性
}
public class Main {
public static void main(String[] args) {
// 创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
// 创建 Service 实例
GitHubService service = retrofit.create(GitHubService.class);
// 发起请求
Call<List<Repo>> call = service.listRepos("octocat");
call.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
if (response.isSuccessful()) {
List<Repo> repos = response.body();
// 处理响应数据
}
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
// 处理请求失败
}
});
}
}
在上述示例中,虽然没有显式地配置 OkHttp,但 Retrofit 会自动使用默认的 OkHttp 客户端来处理网络请求。
自定义 OkHttp 客户端:
开发者可以自定义 OkHttp 客户端,并将其配置到 Retrofit 中。这样可以根据具体需求对 OkHttp 进行个性化配置,例如添加拦截器、设置连接超时时间等。
java
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Path;
import java.io.IOException;
import java.util.List;
// 定义 Service 接口
interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
class Repo {
// 定义 Repo 类的属性
}
public class Main {
public static void main(String[] args) {
// 创建自定义的 OkHttp 客户端
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request newRequest = originalRequest.newBuilder()
.header("User-Agent", "MyApp/1.0")
.build();
return chain.proceed(newRequest);
}
})
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.build();
// 创建 Retrofit 实例,并配置自定义的 OkHttp 客户端
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(okHttpClient)
.build();
// 创建 Service 实例
GitHubService service = retrofit.create(GitHubService.class);
// 发起请求
Call<List<Repo>> call = service.listRepos("octocat");
call.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
if (response.isSuccessful()) {
List<Repo> repos = response.body();
// 处理响应数据
}
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
// 处理请求失败
}
});
}
}
在上述示例中,首先创建了一个自定义的 OkHttp 客户端,并添加了一个拦截器用于添加请求头。然后将自定义的 OkHttp 客户端配置到 Retrofit 中,这样 Retrofit 就会使用该客户端来处理网络请求。
通过以上方式,Retrofit 可以灵活地使用 OkHttp 进行网络请求,同时开发者可以根据具体需求对 OkHttp 进行个性化配置,以满足不同的业务场景。
9, Retrofit的缓存机制
Retrofit 本身并没有直接提供缓存机制,但它依赖于 OkHttp 来处理网络请求,而 OkHttp 具备强大的缓存功能,因此 Retrofit 可以借助 OkHttp 的缓存机制来实现请求的缓存。
下面从缓存原理、配置使用、缓存策略以及注意事项几个方面详细分析 Retrofit 的缓存机制。
缓存原理:
OkHttp 的缓存机制基于 HTTP 协议的缓存头(如 Cache-Control、ETag、Last-Modified 等)来工作。当客户端发起一个请求时,OkHttp 会先检查本地缓存中是否存在符合条件的响应。如果存在,并且根据缓存头判断该响应仍然有效,那么 OkHttp 会直接返回缓存的响应,而不需要再次向服务器发送请求,从而提高了请求的响应速度,减少了网络流量。
配置使用:
要在 Retrofit 中使用缓存,需要进行以下几个步骤:
添加 OkHttp 缓存
首先,创建一个 OkHttpClient 实例,并为其配置缓存目录和缓存大小。
java
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.io.File;
public class RetrofitClient {
private static final String BASE_URL = "https://api.example.com/";
private static Retrofit retrofit;
public static Retrofit getClient() {
if (retrofit == null) {
// 设置缓存目录和大小
File cacheDirectory = new File("your_cache_directory");
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDirectory, cacheSize);
// 创建 OkHttpClient 并配置缓存
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
// 创建 Retrofit 实例并使用自定义的 OkHttpClient
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
在请求中设置缓存策略:
可以通过添加拦截器来设置请求的缓存策略。以下是一个示例,展示如何添加一个拦截器来设置缓存策略:
java
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 对于网络可用的情况,设置缓存时间为 0 秒,即不使用缓存
request = request.newBuilder()
.header("Cache-Control", "public, max-age=" + 0)
.build();
Response response = chain.proceed(request);
// 对于网络不可用的情况,设置缓存时间为 3600 秒(1 小时)
return response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + 3600)
.build();
}
}
然后将该拦截器添加到 OkHttpClient 中:
scss
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new CacheInterceptor())
.build();
缓存策略
OkHttp 支持多种缓存策略,主要通过 Cache-Control 头来控制:
max-age:指定缓存的最大有效时间,单位为秒。例如,max-age=3600 表示缓存的响应在 1 小时内是有效的。
max-stale:允许使用过期的缓存响应,指定了可以接受的最大过期时间,单位为秒。例如,max-stale=3600 表示即使缓存的响应已经过期,但只要过期时间不超过 1 小时,仍然可以使用该缓存。
only-if-cached:表示只使用缓存的响应,如果缓存中没有符合条件的响应,则返回 504(Gateway Timeout)错误。
no-cache:表示每次请求都需要向服务器验证缓存的有效性,即使缓存存在也需要重新请求。
no-store:表示不使用缓存,每次请求都直接从服务器获取响应。
注意事项
服务器支持:缓存机制的有效性依赖于服务器端是否正确设置了缓存头。如果服务器没有返回合适的 Cache-Control、ETag 或 Last-Modified 头,那么 OkHttp 可能无法正确地使用缓存。
缓存清理:需要定期清理缓存以避免占用过多的存储空间。可以通过 Cache 对象的 evictAll() 方法来清空缓存,或者根据需要手动删除缓存文件。
缓存更新:当数据发生变化时,需要确保缓存能够及时更新。可以通过在请求中添加合适的缓存头或使用 ETag 来实现缓存的验证和更新。
通过以上的配置和策略,Retrofit 可以借助 OkHttp 的缓存机制来提高应用的性能和响应速度,减少网络流量。
10, 如何根据需求调整Retrofit的缓存策略?
Retrofit 本身没有直接的缓存功能,但其依赖的 OkHttp 提供了强大的缓存机制。可以通过配置 OkHttp 来根据不同需求调整 Retrofit 的缓存策略,下面详细介绍具体的操作方法。
基本缓存配置: 在使用 Retrofit 时,首先要对 OkHttp 进行缓存配置,包括设置缓存目录和大小。
java
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.io.File;
public class RetrofitClient {
private static final String BASE_URL = "https://api.example.com/";
private static Retrofit retrofit;
public static Retrofit getClient() {
if (retrofit == null) {
// 设置缓存目录
File cacheDirectory = new File("your_cache_directory");
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDirectory, cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
根据不同需求调整缓存策略:
强制使用缓存(离线模式): 当设备处于离线状态时,希望应用尽可能使用本地缓存的数据。可以通过添加拦截器来实现:
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class OfflineCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 当网络不可用时,设置缓存策略为仅使用缓存,最大过期时间为 7 天
request = request.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7)
.build();
return chain.proceed(request);
}
}
然后将该拦截器添加到 OkHttpClient 中:
scss
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new OfflineCacheInterceptor())
.build();
优先使用网络,网络不可用时使用缓存:
在大多数情况下,我们希望优先使用最新的网络数据,但在网络不可用的时候可以使用缓存数据。可以添加一个网络拦截器和一个普通拦截器来实现:
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
// 网络拦截器,网络可用时设置缓存时间
public class OnlineCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
// 网络可用时,缓存有效时间为 60 秒
return response.newBuilder()
.header("Cache-Control", "public, max-age=" + 60)
.build();
}
}
// 普通拦截器,网络不可用时设置缓存时间
public class OfflineCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 网络不可用时,缓存最大过期时间为 7 天
request = request.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7)
.build();
return chain.proceed(request);
}
}
//将这两个拦截器添加到 OkHttpClient 中:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new OfflineCacheInterceptor())
.addNetworkInterceptor(new OnlineCacheInterceptor())
.build();
禁用缓存:
如果某些请求不希望使用缓存,可以在请求时设置 no-cache 或 no-store 策略:
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class DisableCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 禁用缓存
request = request.newBuilder()
.header("Cache-Control", "no-cache, no-store, must-revalidate")
.build();
return chain.proceed(request);
}
}
//将该拦截器添加到 OkHttpClient 中:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new DisableCacheInterceptor())
.build();
动态调整缓存策略:
可以根据不同的请求方法或请求 URL 动态调整缓存策略。例如,为不同的 API 接口设置不同的缓存时间:
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class DynamicCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String url = request.url().toString();
if (url.contains("/api/v1/data1")) {
// 为 /api/v1/data1 接口设置缓存时间为 300 秒
request = request.newBuilder()
.header("Cache-Control", "public, max-age=" + 300)
.build();
} else if (url.contains("/api/v1/data2")) {
// 为 /api/v1/data2 接口设置缓存时间为 600 秒
request = request.newBuilder()
.header("Cache-Control", "public, max-age=" + 600)
.build();
}
return chain.proceed(request);
}
}
//将该拦截器添加到 OkHttpClient 中:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new DynamicCacheInterceptor())
.build();
通过以上方法,我们可以根据不同的需求灵活调整 Retrofit 的缓存策略,以满足各种业务场景的要求。
11, Retrofit中OkHttp缓存的实现原理
Retrofit 本身没有直接实现缓存功能,而是依赖于 OkHttp 来处理网络请求和缓存。下面详细分析 OkHttp 缓存的实现原理。
缓存的基本概念:
OkHttp 的缓存机制基于 HTTP 协议的缓存头(如 Cache-Control、ETag、Last-Modified 等)来工作。这些缓存头用于控制缓存的行为,包括缓存的有效期、验证方式等。当客户端发起一个请求时,OkHttp 会先检查本地缓存中是否存在符合条件的响应。如果存在,并且根据缓存头判断该响应仍然有效,那么 OkHttp 会直接返回缓存的响应,而不需要再次向服务器发送请求。
缓存的配置与初始化:
在使用 OkHttp 缓存之前,需要进行一些配置和初始化工作。主要包括设置缓存目录和缓存大小,示例代码如下:
java
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import java.io.File;
public class OkHttpCacheExample {
public static void main(String[] args) {
// 设置缓存目录
File cacheDirectory = new File("your_cache_directory");
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDirectory, cacheSize);
// 创建 OkHttpClient 并配置缓存
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
}
}
在上述代码中,首先创建了一个 Cache 对象,指定了缓存目录和缓存大小。然后将该 Cache 对象配置到 OkHttpClient 中。
缓存的存储结构:
OkHttp 使用文件系统来存储缓存数据。每个缓存项对应一个文件,文件的命名规则是根据请求的 URL 进行哈希处理得到的。缓存文件包含两部分:元数据和响应数据。
元数据:存储了响应的一些基本信息,如状态码、响应头、缓存头(Cache-Control、ETag、Last-Modified 等)。
响应数据:存储了服务器返回的实际响应内容。
缓存的查找与验证:
当客户端发起一个请求时,OkHttp 会按照以下步骤查找和验证缓存:
查找缓存:根据请求的 URL 生成哈希值,然后在缓存目录中查找对应的缓存文件。如果找到缓存文件,则读取其中的元数据和响应数据。
验证缓存有效性:根据元数据中的缓存头信息判断缓存是否有效。主要考虑以下几个方面:
Cache-Control 头:检查 max-age、max-stale 等字段。如果 max-age 未过期,则认为缓存有效;如果 max-age 已过期,但 max-stale 允许使用过期缓存,则仍然可以使用缓存。
ETag 和 Last-Modified 头:如果 max-age 已过期,并且 Cache-Control 不允许使用过期缓存,则需要向服务器发送验证请求。使用 ETag 时,发送 If-None-Match 头;使用 Last-Modified 时,发送 If-Modified-Since 头。服务器根据这些头信息判断资源是否有更新,如果没有更新,则返回 304(Not Modified)状态码,客户端可以继续使用缓存;如果有更新,则返回新的响应数据。
缓存的更新与清理:
缓存更新:当服务器返回新的响应数据时,OkHttp 会更新缓存文件。将新的元数据和响应数据写入缓存文件,并更新文件的时间戳。
缓存清理:为了避免缓存占用过多的存储空间,OkHttp 会在缓存达到一定大小时进行清理。清理策略是根据缓存文件的访问时间和大小来决定的,优先删除最久未使用的缓存文件。
拦截器在缓存中的作用:
OkHttp 的拦截器机制可以用于控制缓存的行为。例如,可以添加一个拦截器来修改请求头或响应头,从而调整缓存策略。以下是一个简单的拦截器示例,用于设置缓存时间:
java
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 对于网络可用的情况,设置缓存时间为 60 秒
request = request.newBuilder()
.header("Cache-Control", "public, max-age=" + 60)
.build();
Response response = chain.proceed(request);
// 对于网络不可用的情况,设置缓存时间为 3600 秒(1 小时)
return response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + 3600)
.build();
}
}
//将该拦截器添加到 OkHttpClient 中
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new CacheInterceptor())
.build();
综上所述,OkHttp 的缓存机制通过文件系统存储缓存数据,根据 HTTP 缓存头信息进行缓存的查找、验证、更新和清理,同时可以使用拦截器来灵活控制缓存策略。