第四部分:Retrofit 高级特性
第十三章:Retrofit 数据转换器(Converter)
13.1 Converter 的作用
一句话:Converter 把 Java 对象 与 HTTP 请求体/响应体 互相转换,接口里可直接用 User、List<User>,不必手写 JSON 拼装与解析。
为什么需要:没有 Converter 时只能写 Call<ResponseBody>,自己 body().string() 再解析,发请求也要自己 RequestBody.create(gson.toJson(user), ...)。有 Converter 后写成 Call<User>、@Body User,由 Retrofit 在拼 Request 时、拿到 Response 后自动转换。
两个方向:
| 方向 | 输入 | 输出 |
|---|---|---|
| 请求体 | 方法参数(如 @Body User user) | RequestBody(如 JSON 字节流) |
| 响应体 | ResponseBody(网络返回的原始字节) | 方法返回类型的泛型(如 User) |
类型从哪来:请求体看 @Body 的参数类型,响应体看 返回类型泛型(如 Call<User> 的 User)。如何注册:addConverterFactory(Factory),可多个;按添加顺序问每个 Factory 能否处理该类型,第一个返回非 null 的被选用。
13.2 常用转换器(Gson、Jackson、Moshi)
三种都是“添加依赖 → addConverterFactory(XXX)”的用法,选一个即可(通常 Gson 或 Moshi)。
| 转换器 | 依赖(示例) | 用法 | 特点 |
|---|---|---|---|
| Gson | converter-gson + gson | addConverterFactory(GsonConverterFactory.create()) 或 create(gson) | 常用,可自定义 GsonBuilder(日期、null、字段名策略等) |
| Jackson | converter-jackson + jackson-databind | addConverterFactory(JacksonConverterFactory.create()) 或 create(mapper) | Java 生态常用,注解丰富,可配置 ObjectMapper |
| Moshi | converter-moshi + moshi | addConverterFactory(MoshiConverterFactory.create(moshi)) | Kotlin 友好,支持 data class、可空类型;Square 系与 Retrofit 同源 |
Gson 示例(基本 + 自定义):
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.10.1'
// 基本
addConverterFactory(GsonConverterFactory.create())
// 自定义:日期、null、下划线命名等
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.serializeNulls()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
addConverterFactory(GsonConverterFactory.create(gson));
Jackson:自定义日期等可 ObjectMapper mapper = new ObjectMapper().setDateFormat(...); 再 JacksonConverterFactory.create(mapper)。
Moshi:Kotlin 常用 Moshi.Builder().add(KotlinJsonAdapterFactory()).build() 再 MoshiConverterFactory.create(moshi)。
注意:Bean 与服务端字段不一致时,Gson 用 @SerializedName;版本与工程其他依赖保持一致。
13.3 如何自定义 Converter
继承 Converter.Factory(抽象类),按需实现 responseBodyConverter(响应体→对象)和 requestBodyConverter(对象→请求体);不支持的 type 返回 null,让后续 Factory 或默认逻辑处理。
1. 响应体转对象
- 方法:
responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) - 若
type是你要处理的类型(如MyDto.class),返回一个Converter<ResponseBody, MyDto>。 - 在 Converter 的
convert(ResponseBody value)里:用value.string()或value.byteStream()拿到原始数据,再解析成 MyDto;解析失败可抛IOException或包装成业务异常供上层统一处理。
2. 对象转请求体
- 方法:
requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) - 若 type 是你要处理的类型,返回
Converter<MyDto, RequestBody>。 - 在
convert(MyDto value)里:将 value 序列化成字节或字符串,再RequestBody.create(bytes, MediaType.parse("application/json; charset=utf-8"))。
示例骨架(仅响应体):
public class MyConverterFactory extends Converter.Factory {
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(type) != MyDto.class) return null;
return value -> {
String json = value.string();
return parseToMyDto(json); // 自己实现解析,失败抛 IOException
};
}
}
面试追问:多个 ConverterFactory 顺序有什么影响?→ 按 add 顺序询问,第一个返回非 null 的被采用(详见 13.4);更具体的 Factory 应放前面,避免被 Gson 等通用 Converter 抢先。
13.4 转换器工作原理(选 → 用,一条线说清)
两件事:
① 选:解析方法时按“类型”选出用哪个 Converter,并缓存在 ServiceMethod;
② 用:发请求时用请求体 Converter 把对象转成 RequestBody,收响应时用响应体 Converter 把 ResponseBody 转成对象。
类型来源与选用规则在 13.1 已说明,这里只讲全过程与谁在做。
全过程(一张图串起来):
① 解析阶段(第一次调用某方法时,只做一次并缓存)
loadServiceMethod(method) → ServiceMethod.parseAnnotations(...)
├─ 若有 @Body 等请求体参数:按参数 Type 遍历 Factory.requestBodyConverter(...),第一个非 null 存到 ServiceMethod
└─ 若返回类型泛型不是 ResponseBody:按泛型 Type 遍历 Factory.responseBodyConverter(...),第一个非 null 存到 ServiceMethod
② 发请求时(每次调用接口方法都会走)
RequestFactory 拼 Request → 遇到 @Body 参数
→ 取出 ServiceMethod 里存的 requestBodyConverter,调用 convert(参数值) 得到 RequestBody
→ 塞进 Request,交给 OkHttpCall → OkHttp 发出
③ 收响应时(OkHttp 返回 Response 之后)
OkHttpCall 拿到 response.body() 即 ResponseBody
→ 取出 ServiceMethod 里存的 responseBodyConverter,调用 convert(responseBody) 得到业务对象(如 User)
→ 把该对象交给 CallAdapter 或作为方法返回值
谁在选、谁在用:选在 ServiceMethod 解析时(调 retrofit.requestBodyConverter / retrofit.responseBodyConverter,内部遍历已注册的 Factory);用在 RequestFactory 拼 Request 时(请求体)、OkHttpCall 解析 Response 时(响应体)。解析阶段只跟“方法签名”有关,所以按类型选一次即可;真正转换在每次请求/响应时各做一次。
边界:无 @Body 等请求体参数时不会选、也不会用请求体 Converter;返回类型为 Call<ResponseBody> 等时不需要把 body 再转成别的类型,可能不选响应体 Converter 或做恒等处理。
源码对应:选 → ServiceMethod 解析里调 Retrofit 的 requestBodyConverter/responseBodyConverter;请求体用 → RequestFactory/ParameterHandler 里 convert(value);响应体用 → OkHttpCall 的 parseResponse 里 convert(body)。
13.5 转换错误处理
| 项目 | 说明 |
|---|---|
| 常见异常 | Gson 解析失败抛 JsonSyntaxException;类型不匹配、缺失字段等也可能抛异常。 |
| 处理方式 | 调用处:对 execute() 或 enqueue 的回调里用 try-catch,转成业务错误或提示。Converter 内:自定义 Converter 的 convert() 里 catch 后包装成 IOException 或业务异常再抛。RxJava/协程:onErrorResumeNext 或 runCatching 转 Result.failure() / ApiException。 |
| 建议 | 不要吞掉异常;日志可记录原始 body 便于排查,注意脱敏。 |
13.6 支持多种数据格式
多次 addConverterFactory 即可(如 Gson、Jackson、自定义);每个方法按自己的参数类型与返回类型在解析时各选一个 Converter(规则同 13.4),无需在接口上写“用哪种格式”。同一接口里有的方法返回 User、有的返回 String 也可以,只要对应类型有 Factory 能处理;更具体的 Factory 需排在前面,避免被通用 Gson 抢先。
常见追问:
- 没有 Converter 时返回类型只能写什么?→
Call<ResponseBody>,需自己body().string()再解析。 - 解析失败抛什么?→ Gson 抛 JsonSyntaxException,应在调用处或 Converter 内 catch 并转成业务异常或 Result.failure。
- Gson 和 Moshi 怎么选?→ Java 项目常用 Gson;Kotlin 项目可选 Moshi(支持 data class、可空、同 Square 系)。
第十四章:Retrofit 适配器(CallAdapter)
14.1 CallAdapter 的作用
一句话:CallAdapter 把 Retrofit 内部的 OkHttpCall 适配成接口方法声明的返回类型(如 Call<User>、Observable<User>、suspend fun …: User),这样业务层可以直接用 Call 的 execute/enqueue、RxJava 链式、或协程挂起,而不必自己包一层。
为什么需要:Retrofit 发请求后拿到的是封装了 OkHttp 的 OkHttpCall;若没有 CallAdapter,接口只能统一返回 Call<T>,业务要么全用 call.enqueue(callback),要么自己把 Call 转成 Observable/LiveData 等。有了 CallAdapter,按方法返回类型选一个适配器,在内部对 OkHttpCall 执行 execute/enqueue,把结果(或异常)转成 Observable、LiveData、suspend 返回值等,接口声明什么类型就返回什么类型。
适配关系:
| 方法返回类型 | 谁在做适配 | 适配结果 | 典型用途 |
|---|---|---|---|
Call<T> | 默认 DefaultCallAdapter | 直接返回 OkHttpCall | 同步 execute()、异步 enqueue(Callback) |
Observable<T> / Single<T> | RxJava CallAdapterFactory | 包装成 Observable/Single,内部 enqueue 后 onNext/onError | 链式调用、线程切换、合并请求、背压 |
| suspend fun …: T | Retrofit 2.6+ 内置 | 挂起直到拿到 T 或抛异常 | Kotlin 协程内直接拿结果 |
Deferred<T>、Flow<T> | 需额外 CallAdapterFactory | 社区或自写 | 协程 Deferred、Flow 场景 |
LiveData<T> 等 | 自定义 Factory | 内部 enqueue,postValue 等 | 与 ViewModel/Lifecycle 配合 |
各类型依赖与用法见 14.2。
类型从哪来:看方法声明的返回类型(如 Call<User> 的 raw type 是 Call、泛型是 User)。Retrofit 用该类型去问已注册的 CallAdapter.Factory“能否处理”;同时用泛型中的实际数据类型(如 User)作为 responseType(),去选 Converter 做 ResponseBody→T 的转换。
如何注册:addCallAdapterFactory(Factory),可多个;按添加顺序问每个 Factory 的 get(returnType, ...),第一个返回非 null 的 CallAdapter 被选用。未显式添加时,Retrofit 会加默认的 Call 适配器(和 2.6+ 的 suspend 支持)。
14.2 常用适配器(Call、RxJava、协程)
三种常见用法:Call 为默认无需配置;RxJava、协程 suspend 需添加依赖或依赖 Retrofit 2.6+ 内置。
| 适配器 | 依赖/要求 | 用法 | 特点 |
|---|---|---|---|
Call<T> | 无,Retrofit 默认 | 接口返回 Call<User>,不添加 Factory 即用默认 | execute() 同步、enqueue(callback) 异步;简单直接 |
| RxJava3 | adapter-rxjava3 + rxjava + rxandroid | addCallAdapterFactory(RxJava3CallAdapterFactory.create()) | 返回 Observable/Single/Maybe/Completable;链式、线程切换、背压 |
| RxJava2 | adapter-rxjava2 + rxjava | RxJava2CallAdapterFactory.create() | 与 RxJava3 二选一,不可同时用 |
| suspend | Retrofit 2.6+,Kotlin | 接口声明 suspend fun getUser(): User,无需额外 Factory | 内置支持;挂起直到拿到 T 或抛异常,与协程作用域配合 |
RxJava 示例(依赖 + 配置 + 接口 + 调用):
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) // 或 createWithScheduler(Schedulers.io())
.build();
// 接口
@GET("users/{id}")
Observable<User> getUser(@Path("id") int id);
// 调用:线程切换在 subscribe 前完成
service.getUser(123)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> { /* 更新 UI */ }, throwable -> { /* 错误 */ });
协程 suspend 示例(无需额外 CallAdapter):
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
// 调用
viewModelScope.launch {
val user = withContext(Dispatchers.IO) { api.getUser(123) }
// 或直接 api.getUser(123),注意当前线程
}
注意:若需 Deferred<T> 或 Flow<T>,需社区或自写 CallAdapterFactory;Retrofit 2.6+ 只内置“suspend 返回 T”。
14.3 如何自定义 CallAdapter
继承 CallAdapter.Factory(抽象类),实现 get(Type returnType, Annotation[] annotations, Retrofit retrofit);不支持的 returnType 返回 null,让后续 Factory 或默认 Call 适配器处理。
1. 判断返回类型
- 用
Utils.getRawType(returnType)得到原始类型(如 LiveData),再取泛型参数(如ApiResult<User>、User)。 - 若与你要适配的类型一致(如
LiveData<ApiResult<?>>),则 new 一个自定义 CallAdapter 并 return;否则 return null。
2. 声明 responseType()
- CallAdapter 的 responseType() 必须返回“网络响应体要转成的类型”(如
User),即 returnType 里最内层泛型。 - Retrofit 用这个类型去选 Converter 做 ResponseBody → T 的转换,所以必须与接口泛型一致。
3. 实现 adapt(Call<R> call)
- 在内部对传入的 OkHttpCall 执行
call.enqueue(callback)或call.execute()。 - 在 onResponse/onFailure 或 execute 的 try/catch 里,把结果或异常转成你的目标类型(如 LiveData.postValue、Result 封装、Flow 发射等),最后 return 该对象。
示例思路(LiveData<ApiResult<T>>):
get(returnType, ...):若 rawType 为 LiveData 且泛型为 ApiResult,返回自定义 CallAdapter;responseType() 返回 ApiResult 的泛型 T(如 User)。adapt(call):创建MutableLiveData<ApiResult<T>>,call.enqueue()→ onResponse 且成功则postValue(ApiResult.success(body())),否则postValue(ApiResult.failure(code, message));onFailure 则postValue(ApiResult.failure(e)),最后 return 该 LiveData。
示例骨架(LiveData 适配器):
public final class LiveDataCallAdapterFactory extends CallAdapter.Factory {
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (Utils.getRawType(returnType) != LiveData.class) return null;
Type bodyType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
return new CallAdapter<Object, LiveData<?>>() {
@Override
public Type responseType() { return bodyType; }
@Override
public LiveData<?> adapt(Call<Object> call) {
MutableLiveData<Object> liveData = new MutableLiveData<>();
call.enqueue(new Callback<Object>() {
@Override
public void onResponse(Call<Object> c, Response<Object> r) {
liveData.postValue(r.isSuccessful() ? r.body() : new ApiError(r.code(), r.message()));
}
@Override
public void onFailure(Call<Object> c, Throwable t) {
liveData.postValue(new ApiError(t));
}
});
return liveData;
}
};
}
}
(实际可把 Object 换成泛型 T,用 ApiResult<T> 封装成功/失败,见上示例思路。)
面试追问:CallAdapter 与 Converter 谁先执行?→ 先由 Converter 把 ResponseBody 转成 T(在 OkHttpCall 内部),再把得到的 T 交给 CallAdapter.adapt;adapt 拿到的是已经转好的 Call<T>,所以 CallAdapter 只做“包装成返回类型”,不负责解析 body。
14.4 适配器工作原理(选 → 用,一条线说清)
两件事:
① 选:解析方法时按方法返回类型选出用哪个 CallAdapter,并缓存在 ServiceMethod;
② 用:方法被调用时,用选好的 CallAdapter 对 OkHttpCall 做 adapt,把“已由 Converter 转好的 Call<T>”包装成方法声明的返回类型(Observable、LiveData、suspend 返回值等)。
类型来源与选用规则在 14.1 已说明,这里只讲全过程与谁在做。
全过程(一张图串起来):
① 解析阶段(第一次调用某方法时,只做一次并缓存)
loadServiceMethod(method) → ServiceMethod 解析返回类型
→ 按 returnType 遍历 Factory.get(returnType, ...),第一个返回非 null 的 CallAdapter 存到 ServiceMethod
→ 用 CallAdapter.responseType() 得到“响应体要转成的类型”,再按 13.4 的规则选 Converter
② 方法被调用时(每次调用接口方法都会走)
RequestFactory + 参数 → 拼 Request → 创建 OkHttpCall(request)
→ OkHttp 发请求 → 拿到 Response 后,OkHttpCall 内部用 Converter 把 ResponseBody 转成 T(如 User)
→ 调用 CallAdapter.adapt(okHttpCall):传入的 call 已是“带结果或异常的 Call”,适配器只负责把结果/异常转成 Observable、LiveData、suspend 返回值等
→ 把适配后的对象作为方法返回值交给调用方
谁在选、谁在用:选在 ServiceMethod 解析时(调 retrofit.callAdapter,内部遍历已注册的 CallAdapter.Factory);用在方法调用时,先 OkHttpCall 执行请求并用 Converter 得到 T,再 CallAdapter.adapt(okHttpCall) 包装成声明类型。CallAdapter 不负责发请求,只负责“把 Call<T> 包装成返回类型”。
与 Converter 配合:选 Converter 时用的“响应体类型”来自 CallAdapter.responseType(),所以先根据 returnType 选 CallAdapter,再用其 responseType 去选 Converter;执行时先 Converter 转 body,再 adapt 成返回类型。
边界:若 returnType 没有任何 Factory 能处理(都返回 null),会报错;默认会加一个处理 Call 的 Factory,所以至少 Call<T> 一定可用。取消、超时、解析错误等由底层 OkHttpCall/Converter 产生,CallAdapter 只负责把成功或失败的结果转成声明类型(如 Observable 的 onNext/onError、LiveData 的 postValue)。
源码对应:选 → ServiceMethod 里调 retrofit.callAdapter(returnType, annotations);用 → 创建 OkHttpCall 后,在调用链里对返回值执行 callAdapter.adapt(okHttpCall)(如 DefaultCallAdapter 直接返回 call,RxJava 的 adapt 里 enqueue 后转 Observable)。
14.5 多返回类型与选择顺序
多次 addCallAdapterFactory 即可(如自定义 LiveData、RxJava3、默认 Call);同一接口里不同方法可声明不同返回类型:Call<User>、Observable<User>、suspend fun getUser(): User、LiveData<ApiResult<User>> 等。Retrofit 按每个方法的 returnType 在解析时各选一个 CallAdapter(规则同 14.4),因此同一 Retrofit 可同时支持多种风格。
选择顺序:按 addCallAdapterFactory 的添加顺序 依次询问 Factory.get(returnType, ...),第一个返回非 null 的被采用。自定义类型(如 LiveData<ApiResult<T>>)要优先匹配,须放在前面,否则可能被 RxJava 等先匹配。
面试一句话:CallAdapter 只负责把 OkHttpCall 包装成方法返回类型,不负责发请求、不解析 body;发请求是 OkHttp,解析 body 是 Converter。
常见追问速查:
| 追问 | 简答 |
|---|---|
| CallAdapter 和 Converter 谁先执行? | 先 Converter 把 ResponseBody 转成 T(OkHttpCall 内),再 CallAdapter.adapt 把 Call<T> 包装成返回类型。 |
| responseType() 干什么用? | 告诉 Retrofit“响应体要转成的类型”,用来选 Converter;必须与接口方法返回类型的泛型一致。 |
| 为什么先选 CallAdapter 再选 Converter? | 响应体要转成的类型来自 CallAdapter 的 responseType()(如 Call<User> 的 User),所以先定适配器才能知道用哪个 Converter。 |
| 多个 Factory 顺序有什么影响? | 按 addCallAdapterFactory 顺序询问,第一个对当前 returnType 返回非 null 的被采用;自定义要优先就放前面。 |
| CallAdapter 负责发请求吗? | 不负责;发请求是 OkHttpCall 内部的 OkHttp,CallAdapter 只做“包装成方法声明的返回类型”。 |
第十五章:Retrofit 与 OkHttp 拦截器集成
15.1 Retrofit 与拦截器的关系(如何挂上)
一句话:Retrofit 不实现网络层,所有请求都通过你传入的 OkHttpClient(或 Retrofit 默认 new 的)发出去,因此“给 Retrofit 加拦截器”就是给这个 OkHttpClient 配置拦截器,配置方式与单独用 OkHttp 完全相同。
职责划分:
| 谁 | 负责什么 |
|---|---|
| Retrofit | 拼 Request(注解→Request)、选 Converter/CallAdapter;不负责发请求、不持有拦截器。 |
| OkHttpClient | 发请求、拦截器链、连接池、缓存、超时等;拦截器全部在 OkHttpClient.Builder 上配置。 |
如何配置:先建好 OkHttpClient(在 Builder 上 addInterceptor / addNetworkInterceptor),再通过 .client(client) 传给 Retrofit;之后该 Retrofit 实例上所有 ApiService 的请求都会走这个 client 的拦截器链。若不传 client,Retrofit 会 new OkHttpClient(),可在此基础上 new OkHttpClient.Builder().addInterceptor(...).build() 再传入。
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.addNetworkInterceptor(new AuthInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
面试要点:Retrofit 没有“Retrofit 专属拦截器”,用的就是 OkHttp 的 Interceptor;执行顺序、应用拦截器 vs 网络拦截器的区别与上部第六章一致。
15.2 日志拦截器
| 项目 | 说明 |
|---|---|
| 依赖 | com.squareup.okhttp3:logging-interceptor |
| Level | NONE / BASIC(请求行+响应行)/ HEADERS / BODY(完整 body,调试用) |
| 添加方式 | addInterceptor 或 addNetworkInterceptor;前者命中缓存也会打印,后者仅真实发网络时打印。 |
| 生产环境 | 建议 NONE 或 BASIC,避免打 body/敏感信息;可按 BuildConfig.DEBUG 切换 Level。 |
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(BuildConfig.DEBUG ? Level.BODY : Level.NONE);
client = new OkHttpClient.Builder().addInterceptor(logging).build();
15.3 认证拦截器(Token、401 刷新)
| 场景 | 做法 | 注意 |
|---|---|---|
| 统一加 Token | 在 OkHttpClient 上 addInterceptor,在 intercept 里对 chain.request() 加 Header(如 Authorization: Bearer 后接 token),再 chain.proceed(newRequest)。Token 可从单例、AccountManager 等获取。 | 确保 Token 未过期时再请求,或配合 401 刷新。 |
| 401 自动刷新 | 使用 OkHttp 的 Authenticator,在 Builder 里 authenticator(...)。当响应 401/407 时 OkHttp 会回调,在其中刷新 Token 并 return 带新 Token 的 Request,OkHttp 会用新 Request 重试。 | 防止并发刷新:多请求同时 401 时只做一次刷新(加锁或单例 Future),再统一重试。 |
// 加 Token
.addInterceptor(chain -> {
Request req = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + tokenProvider.getToken())
.build();
return chain.proceed(req);
})
// 401 刷新(在 Builder 上)
.authenticator((route, response) -> {
String newToken = refreshToken(); // 需防并发
return response.request().newBuilder()
.header("Authorization", "Bearer " + newToken).build();
})
15.4 缓存
| 项目 | 说明 |
|---|---|
| 配置 | OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build(),再把该 client 传给 Retrofit。 |
| 谁处理 | 读写、过期策略、304 等均由 OkHttp 的 CacheInterceptor 处理;Retrofit 无需额外代码。 |
| 强制策略 | 某次请求需强制走网络或只用缓存时,在 Request 上设置 CacheControl(见上部第八章)。 |
15.5 关系与执行顺序小结
| 维度 | 说明 |
|---|---|
| 是否独立 | Retrofit 没有自己的拦截器类型,用的就是 OkHttp 的 Interceptor。 |
| 配置入口 | 全部在 OkHttpClient.Builder 上 addInterceptor / addNetworkInterceptor,再通过 .client(client) 传给 Retrofit。 |
| 执行顺序 | 与上部第六章一致:应用拦截器 → RetryAndFollowUp → Bridge → Cache → Connect → 网络拦截器 → CallServer。 |
| 应用 vs 网络 | 应用拦截器:每次请求都走,命中缓存也会执行。网络拦截器:仅真实发网络时执行,能看到重定向、中间请求。 |
面试一句话:Retrofit 只负责拼 Request、选 Converter/CallAdapter;发请求、拦截器、连接池、缓存全是 OkHttp 的活,给 Retrofit 加拦截器 = 给传入的 OkHttpClient 加拦截器。
常见追问:
- 应用拦截器和网络拦截器区别?→ 执行时机(命中缓存时网络拦截器不执行)、可见范围(网络层可见真实 Request 与重定向)。
- 如何加日志?→ 依赖 logging-interceptor,设 Level 后 addInterceptor,生产用 NONE/BASIC。
- 如何加缓存?→
OkHttpClient.Builder().cache(new Cache(dir, size)),再.client(client)传给 Retrofit。
第十六章:Retrofit 错误处理
16.1 网络错误
一句话:未连上、超时、DNS 失败、SSL 异常等会以 IOException 形式出现;同步在 execute() 抛异常,异步进 Callback.onFailure。
| 场景 | 表现 | 处理方式 |
|---|---|---|
| 同步 | call.execute() 抛 IOException | try-catch,按异常类型提示或重试(如 SocketTimeoutException 提示超时)。 |
| 异步 | 进入 Callback.onFailure,参数为 IOException | 在 onFailure 里按 e instanceof ... 区分;用 call.isCanceled() 区分用户取消与真实错误。 |
| 常见子类 | 含义 |
|---|---|
| SocketTimeoutException | 读写超时 |
| ConnectException | 连接失败 |
| UnknownHostException | 域名解析失败、无网 |
| SSLException | 证书等问题 |
可在统一错误层做映射,便于 UI 展示或上报。
16.2 HTTP 错误码(非 2xx)
一句话:在 onResponse 或同步拿到 Response 后,先判断 response.isSuccessful();为 false 时用 code/message 做业务分支,建议在 Repository 层转成 ApiException(code, message),UI 只处理一种错误类型。
| 项目 | 说明 |
|---|---|
| 分支 | 401 跳登录、403 无权限、404 不存在、5xx 提示重试或稍后再试。 |
| 错误体 | 可选 response.errorBody().string() 取服务端错误信息(body 只能读一次)。 |
16.3 解析错误
一句话:Converter 反序列化时可能抛异常(如 Gson 的 JsonSyntaxException、类型不匹配、缺字段);在调用处或 Repository 统一 catch,转成 Result.failure() / ApiException,并打日志(脱敏)便于排查。
| 常见异常 | 处理方式 | 建议 |
|---|---|---|
| JsonSyntaxException、类型不匹配、缺必填字段等 | 调用处:execute() 或 enqueue 的回调整体 try-catch,转业务错误或提示。Converter 内:自定义 Converter 的 convert() 里 catch 后包装成 IOException 或业务异常再抛。Rx/协程:onErrorResumeNext 或 runCatching 转 Result.failure() / ApiException。 | 不吞异常;日志可带部分 body 便于排查,注意脱敏。 |
16.4 统一错误处理方式
| 方式 | 做法 | 适用场景 |
|---|---|---|
| 封装 Call | 工具类 ApiCall.execute(call, callback) 内 enqueue;onResponse 里 isSuccessful→onSuccess(body),否则 onHttpError;onFailure→onNetworkError。Callback 定义 onSuccess(T)、onHttpError(int,String)、onNetworkError(Throwable)。 | 接口返回 Call<T>,希望业务层只面对一种回调。 |
| RxJava | map 转 Result<T>,onErrorResumeNext 把 IOException/非 2xx 转成 Result.failure,subscribe 里统一处理 onNext(Result)/onError。 | 全链路 RxJava。 |
| 协程 | Repository 里 runCatching { api.getUser() } 或 try-catch 返回 Result<User>/ApiException;ViewModel 统一处理,更新 LiveData/StateFlow。 | Kotlin 协程 + ViewModel。 |
| 拦截器 | 应用拦截器里对 response 判断 !isSuccessful() 时记录/上报或抛自定义异常(包装成 IOException 等);不要静默吞掉错误。 | 全局打点、监控。 |
建议:在 Repository 或 ViewModel 层收敛成一种类型,UI 只展示“成功/失败/加载中”。
16.5 自定义错误处理机制
| 方式 | 做法 | 注意 |
|---|---|---|
| 自定义 CallAdapter | 在 adapt(Call) 里执行 call 后判断 !response.isSuccessful(),抛 HttpException(code, message, errorBody) 或返回 Result.failure()。 | 上层 Observable/LiveData 只需处理一种失败形态。 |
| OkHttp 拦截器 | 应用拦截器里对 response 统一检查,非 2xx 时记录/上报或构造异常抛给上层(包装成 IOException 以便传递)。 | 不要吞掉错误,调用方必须能感知失败。 |
| 不推荐 | 拦截器里静默吞掉错误并返回假数据。 | 易导致业务逻辑错误。 |
面试一句话:错误分网络/HTTP 非 2xx/解析三类,在 Repository 或 ViewModel 收敛成一种类型(ApiException/Result),UI 只展示成功/失败/加载中。
常见追问:
- 网络错误和 HTTP 错误怎么区分?→ 网络错误进 onFailure、抛 IOException;HTTP 4xx/5xx 进 onResponse,需判 isSuccessful()。
- ResponseBody 能读几次?→ 只能读一次,
errorBody().string()与body().string()都只能调一次。 - 统一错误有哪几种实现?→ 封装 Call 回调、RxJava onErrorResumeNext、协程 runCatching、拦截器里抛异常。
第五部分:性能优化与最佳实践
第十七章:性能优化
17.1 OkHttp 性能优化
| 项 | 做法 | 说明 |
|---|---|---|
| 单例 OkHttpClient | 全局共享一个实例 | 共享连接池、缓存、Dispatcher,避免重复建连与线程池。 |
| 连接池 | 默认通常够用;高并发可适当调大 ConnectionPool(maxIdle, keepAlive, timeUnit) | 见上部第七章;不宜过大,避免占满文件描述符。 |
| 缓存 | 对可缓存 GET 配置 Cache(cacheDir, maxSize) | 减少重复请求与流量。 |
| HTTP/2 | 服务端支持时自动协商 | 多路复用、头部压缩。 |
| 超时 | 按场景设 connect/read/writeTimeout;大文件适当增大 | 过短易误判、过长易卡顿。 |
| 拦截器 | 少做重 CPU/IO,日志可异步或采样 | 每条请求都走链,耗时叠加到延迟。 |
17.2 Retrofit 性能优化
| 项 | 做法 | 说明 |
|---|---|---|
| 单例 Retrofit | 与单例 OkHttpClient 配合 | 避免重复创建代理与连接。 |
| Converter/CallAdapter | 只加需要的 Factory | 减少匹配遍历与多余对象。 |
| 大响应 | 返回 Call<ResponseBody> 或分页,避免一次性大 JSON | 流式或分页、Gson JsonReader 流式解析。 |
17.3 连接池与缓存要点
| 主题 | 要点 |
|---|---|
| 连接池 | ConnectionPool(maxIdle, keepAlive, timeUnit) 按并发与主机数调整;单例 Client 保证复用;可用 connectionPool().idleConnectionCount()、EventListener 做监控。详见上部第七章。 |
| 缓存 | 目录与 maxSize 合理(如 10MB~50MB);按业务区分策略(静态长缓存、接口短缓存);请求侧用 CacheControl;按需 evictAll 或按 URL 清理,不要每次请求前清空。详见上部第八章。 |
17.4 减少请求与合并、去重
| 手段 | 做法 |
|---|---|
| 减少请求 | HTTP 缓存(Cache + 304)、批量接口(一次传多 id)、合并/去重(见下)。 |
| 请求合并 | 后端支持批量:单接口多参数(如 @Query("ids") List<Long>)替代多次请求;不支持时用队列或 RxJava zip/merge 控制并发。 |
| 请求去重 | 相同请求(URL+方法+关键参数)短时内只发一次:Map<key, Call/Future>,key 存在则复用或等待;注意过期、取消、key 设计,防泄漏与误判。 |
17.5 网络性能监控
| 方式 | 做法 | 说明 |
|---|---|---|
| EventListener | 实现 callStart/callEnd、connectStart/connectEnd 等 | 统计单次请求与连接耗时、失败率。 |
| EventListener.Factory | eventListenerFactory(factory) 为每次 Call 创建监听器 | 便于按 URL、tag 统计。 |
| 拦截器 | 应用拦截器记录 URL、method、code、耗时并上报 | 上报异步,不打完整 body。 |
面试一句话:性能优化围绕单例复用、连接池与缓存、减少请求次数、拦截器轻量、大响应用流式/分页、EventListener 监控。
常见追问:
- 连接池默认参数?→ 一般 5 个空闲连接、5 分钟 keepAlive,高并发可适当调大。
- 为什么拦截器要轻?→ 每条请求都走完整链,拦截器耗时直接加在延迟上。
- 请求去重怎么实现?→
Map<key, Call/Future>,相同 key 复用或等待结果,注意过期与取消。
第十八章:最佳实践
18.1 OkHttp 最佳实践
| 项 | 建议 |
|---|---|
| Client | 单例 OkHttpClient,共享连接池、缓存、Dispatcher。 |
| 超时 | 按场景设 connect/read/writeTimeout;大文件适当增大。 |
| 拦截器 | 应用拦截器:鉴权、公共头、轻量日志(脱敏);网络拦截器:重试、监控。 |
| 异步与生命周期 | 页面销毁时 cancel 未完成 Call;Callback 用弱引用或静态内部类,避免强引用 Activity。 |
| 大文件 | 流式读写(byteStream、RequestBody 分段),避免 string()/bytes() 导致 OOM。 |
| 缓存 | 可缓存 GET 配置 Cache,按业务设 CacheControl。 |
18.2 Retrofit 最佳实践
| 项 | 建议 |
|---|---|
| 单例 | 单例 Retrofit + 单例 OkHttpClient,.client(client) 注入。 |
| 接口 | 按模块拆分(UserApi、OrderApi),统一 baseUrl 与 path 规范(末尾 / 与 @GET 拼接规则)。 |
| 错误 | Repository/ViewModel 层统一处理三类错误,以单一状态或 ApiException/Result 反馈 UI。 |
| RxJava/协程 | 线程切换与生命周期:subscribe 在 ViewModelScope、及时 dispose 防泄漏、避免 Activity 强引用。 |
18.3 网络层架构与测试
| 主题 | 要点 |
|---|---|
| 分层 | UI → ViewModel → Repository → ApiService(Retrofit),OkHttp 作为 client。Repository 封装 API、缓存策略、错误转换,暴露 LiveData/Flow/Observable。 |
| 依赖注入 | Dagger/Hilt/Koin 提供单例 Client、Retrofit、ApiService,便于测试时替换 Mock。 |
| 异常处理 | 网络/HTTP/解析三类在 Repository 映射为一种业务异常或 Result,UI 只根据“成功/失败/加载中”展示。 |
| 统一错误实现 | 与 16.4 一致:封装 Call、RxJava map/onErrorResumeNext、协程 runCatching;ViewModel 只处理一种失败形态。 |
| 日志 | HttpLoggingInterceptor 或自定义;DEBUG 可用 BODY/HEADERS,Release 用 NONE/BASIC;生产脱敏。 |
| 单元测试 | MockWebServer:依赖 com.squareup.okhttp3:mockwebserver,本地 HTTP,enqueue(MockResponse),Retrofit baseUrl 指向 server.url("/").toString();Mock ApiService:Mockito given().willReturn(call),验证 Repository 逻辑与错误分支。 |
常见追问:
- 网络层一般分几层?→ UI → ViewModel → Repository → ApiService(Retrofit),OkHttp 作 client。
- baseUrl 末尾要不要加 /?→ 与
@GET("users")拼接时,baseUrl 末尾有 / 则 .../users,无 / 则最后一节会被替换,需统一规范。 - 单测如何不依赖真实网络?→ MockWebServer 或 Mock ApiService。
第十九章:常见问题与解决方案
19.1 网络超时
| 项目 | 说明 |
|---|---|
| 配置 | OkHttpClient.Builder 上设 connectTimeout、readTimeout、writeTimeout;弱网、大文件可适当加大。 |
| 表现 | 超时后在 onFailure 或 execute() 抛出 SocketTimeoutException 或连接阶段超时。 |
| 处理 | 提示“请求超时,请重试”;或结合 19.2 做有限次重试(仅对幂等或业务允许的请求)。 |
19.2 网络重试
| 项目 | 说明 |
|---|---|
| 默认 | OkHttp 不会对业务请求自动重试(仅对部分连接失败、307/308 等有内置处理)。 |
| 实现 | 自定义拦截器:对 chain.proceed(request) try-catch;捕获 SocketTimeoutException、ConnectException、UnknownHostException 等且重试次数小于上限时,sleep 后再次 proceed。 |
| 幂等 | GET/PUT/DELETE 一般可重试;POST 创建类需谨慎,避免重复提交。详见上部第四章。 |
19.3 Token 过期与 401 刷新
| 方式 | 做法 |
|---|---|
| 加 Token | 应用拦截器为每个 request 加 Authorization: Bearer 后接 token,Token 从单例或 AccountManager 获取。 |
| 401 刷新 | Authenticator 中收到 401 时调刷新接口、存新 Token,return 带新 Token 的 Request,OkHttp 用新 Request 重试。 |
| 防并发 | 刷新时加锁或单例“正在刷新”的 Future,避免多请求同时触发多次刷新。 |
19.4 文件上传失败
| 原因/场景 | 处理 |
|---|---|
| 超时 | 大文件增大 writeTimeout,避免写 body 时超时。 |
| 重试 | onFailure 里判断可重试异常后重新组 Request;服务端支持断点续传时可记录已上传位置续传。 |
| 进度与取消 | 自定义 RequestBody(writeTo 分段写并回调进度);页面销毁时 call.cancel()。详见上部第五章。 |
19.5 内存泄漏
| 原因 | 处理 |
|---|---|
| 未取消 Call | Activity/Fragment 销毁时 call.cancel() 并清空对 Call 的引用。 |
| Callback 强引用 | 匿名 Callback 隐式持有 Activity;改用静态内部类 + WeakReference<Activity>,或 onResponse/onFailure 里先判 ref.get() 再更新 UI。 |
| 长生命周期 | OkHttpClient、Retrofit 由 Application 或单例持有,不由 Activity 直接持有。详见上部 3.6。 |
面试一句话:超时→调大 timeout、提示或重试;重试→自定义拦截器 + 注意幂等;Token/401→拦截器加头 + Authenticator 刷新 + 防并发;上传失败→增大 writeTimeout、断点续传、cancel;泄漏→cancel Call、弱引用/静态内部类、单例持 Client。
常见追问:
- 如何取消请求?→
call.cancel();或client.dispatcher().cancel(tag)按 tag 批量取消。 - 大文件上传要注意什么?→ 增大 writeTimeout、自定义 RequestBody 做进度回调、页面销毁时 cancel。
- 401 刷新为什么要防并发?→ 多请求同时 401 会多次调刷新接口,可能把 Token 刷失效或重复请求。
第六部分:源码解析
第二十章:OkHttp 源码解析
20.1 整体架构
| 层次 | 核心类/概念 | 职责 |
|---|---|---|
| 应用层 | OkHttpClient、Request、Response、Call | 配置入口、请求/响应封装;newCall → execute/enqueue。 |
| 拦截器链 | Interceptor、RealInterceptorChain | 责任链;应用 → RetryAndFollowUp → Bridge → Cache → Connect → 网络 → CallServer。 |
| 连接层 | ConnectionPool、RealConnection、Route、ExchangeFinder | 按 Route 匹配、复用或新建 RealConnection、Exchange 封装读写流。 |
| 网络层 | Socket、TLS、HTTP/2 帧 | 真实 TCP/TLS 与 HTTP。 |
面试一句话:Client.newCall(request) → RealCall → getResponseWithInterceptorChain() 建链 → 各拦截器 proceed 下推 → ConnectInterceptor 从 ConnectionPool 取/建 RealConnection、拿 Exchange → CallServerInterceptor 写请求、读响应 → Response 沿链返回。
20.2 请求流程(源码级)
| 步骤 | 要点 |
|---|---|
| 入口 | client.newCall(request) → new RealCall(this, request)。 |
| 执行 | execute() 调 getResponseWithInterceptorChain();enqueue() 交 Dispatcher,线程池里同样调 getResponseWithInterceptorChain()。 |
| 建链 | 构建 RealInterceptorChain(拦截器列表, index=0);顺序:用户应用 → RetryAndFollowUp → Bridge → Cache → Connect → 用户网络 → CallServerInterceptor。 |
| 链式 | proceed(request) → new Chain(index+1) → 当前拦截器 intercept(next) → 内部 next.proceed(request);index 到末尾时执行 CallServer 的真实 IO。 |
| 连接 | ConnectInterceptor 内 ExchangeFinder 从 ConnectionPool 取或建 RealConnection,拿 Exchange 交给 CallServer。 |
| 返回 | Response 从 CallServer 逐层 return;同步直接返回,异步在 Dispatcher 线程里调 Callback.onResponse。 |
20.3 拦截器链、连接池与缓存
| 主题 | 要点 |
|---|---|
| 链机制 | RealInterceptorChain 持列表 + index;proceed 时 index 未到末尾则创建 next(index+1)、交当前拦截器,拦截器内 next.proceed() 形成递归;到末尾才真实 IO。 |
| 连接池 | Deque<RealConnection>,按 Route 匹配;取时找 isEligible 且健康,用毕放回并记 idleAtNanos;清理:超 keepAlive 或超 maxIdle 时移除。 |
| 缓存 | DiskLruCache 存响应头与 body;CacheInterceptor 用 CacheStrategy 决定用缓存/走网络/条件请求;304 时用缓存+网络响应拼新 Response。 |
20.4 阅读顺序与核心类
| 阅读顺序 | 内容 |
|---|---|
| 1 | OkHttpClient、Request、Response(Builder、不可变)。 |
| 2 | RealCall:execute/enqueue → getResponseWithInterceptorChain()。 |
| 3 | RealInterceptorChain:proceed() 与 index、递归。 |
| 4 | 五内置拦截器:RetryAndFollowUp、Bridge、Cache、Connect、CallServer。 |
| 5 | ConnectionPool(get/put/cleanup)、RealConnection(connect、isEligible、isHealthy)。 |
| 6 | Cache、DiskLruCache、CacheStrategy。 |
技巧:对一次 GET 请求从 RealCall.execute() 断点跟到 getResponseWithInterceptorChain() 和 proceed() 递归,再看 ConnectInterceptor 取 connection 与 exchange。
| 核心类 | 职责简述 |
|---|---|
| OkHttpClient | 配置入口,持有 Dispatcher、ConnectionPool、拦截器、Cache 等。 |
| RealCall | 单次请求入口;execute/enqueue 最终进 getResponseWithInterceptorChain()。 |
| Request/Response | 不可变封装,Builder 构建。 |
| RealInterceptorChain | proceed() 按 index 调下一拦截器或真实 IO。 |
| ConnectionPool / RealConnection | 空闲连接 Deque、按 Route 取/建/清理;单条连接 isEligible、isHealthy。 |
| Cache / CacheInterceptor | DiskLruCache、CacheStrategy;读/写缓存、条件请求。 |
| Dispatcher | 异步 readyAsyncCalls、runningAsyncCalls、线程池。 |
设计模式(与上部 1.7 一致):责任链、建造者、单例、策略、工厂(Call.Factory、EventListener.Factory)、门面(OkHttpClient)。
常见追问:
- Dispatcher 干什么的?→ 管理异步请求的队列与线程池(readyAsyncCalls、runningAsyncCalls),控制并发。
- proceed() 怎么形成递归?→ 每次 proceed 创建 index+1 的 next Chain,当前拦截器里调 next.proceed(request),直到 index 到末尾才执行真实 IO。
- 连接池 isEligible 看什么?→ 看地址、Route、协议等是否一致且连接健康。
第二十一章:Retrofit 源码解析
21.1 整体架构
| 阶段 | 组件 | 做的事 |
|---|---|---|
| 创建 API | Retrofit.create(service) | 动态代理生成接口实现,方法调用进 InvocationHandler。 |
| 方法调用 | InvocationHandler.invoke | loadServiceMethod(method) → ServiceMethod(RequestFactory、CallAdapter、responseConverter)→ 用参数构建 Request → new OkHttpCall → adapt(call) 适配成返回类型。 |
| 执行请求 | OkHttpCall | OkHttpClient.newCall(request).execute()/enqueue(),Response 用 responseConverter 转成泛型 T。 |
| 返回 | CallAdapter | 将 OkHttpCall 适配成 Call<T>/Observable<T>/suspend 等。 |
面试一句话:动态代理接住调用 → ServiceMethod 解析注解拼 Request → OkHttpCall 发请求 → Converter 转响应体 → CallAdapter 适配返回类型。
21.2 动态代理与注解解析
| 主题 | 要点 |
|---|---|
| 创建代理 | create(Class) 内 Proxy.newProxyInstance(loader, interfaces, invocationHandler);所有方法调用走同一 InvocationHandler。 |
| invoke | Object 方法直接 method.invoke(args);否则 loadServiceMethod(method) → ServiceMethod.invoke(args) 建 Request、new OkHttpCall → ServiceMethod.adapt(call) 返回。 |
| 无实现类 | 接口“实现”由 JDK 动态代理运行时生成,统一走 invoke,Retrofit 按注解与参数拼 Request、发请求、适配返回。 |
| 注解解析 | ServiceMethod 解析方法注解(@GET、@POST)与参数注解(@Path、@Query、@Body);RequestFactory 拼 URL、Header、RequestBody 成 Request;ParameterHandler 各子类在 build 时把参数值填进 Request。 |
21.3 Converter、CallAdapter 与阅读顺序
| 主题 | 要点 |
|---|---|
| Converter | 按返回类型泛型与参数类型向 Factory 要 Converter;请求体在拼 Request 时 requestBodyConverter 转;响应体在 OkHttpCall 拿到 Response 后 responseBodyConverter 转。多 Factory 按添加顺序,第一个非 null。 |
| CallAdapter | 按方法返回类型向 Factory 要 CallAdapter;adapt(call) 内执行/enqueue Call,把结果/异常转成 Call/Observable/suspend 等。 |
| 阅读顺序 | ① create + InvocationHandler、② loadServiceMethod + ServiceMethod 缓存与 parseAnnotations、③ RequestFactory + ParameterHandler、④ OkHttpCall 建 Request 与 responseConverter、⑤ CallAdapter.adapt。 |
一条线:接口方法调用 → invoke → ServiceMethod.invoke(args) → Request → OkHttpCall 执行 → OkHttp 发请求 → Converter 转 body → CallAdapter 适配返回。
21.4 核心类一览
| 类 | 职责简述 |
|---|---|
| Retrofit | 配置 baseUrl、Converter、CallAdapter、client;create(service) 动态代理。 |
| ServiceMethod | 方法级缓存与解析;RequestFactory、parameterHandlers、callAdapter、responseConverter。 |
| RequestFactory | ServiceMethod + 参数 → OkHttp Request(URL、method、headers、body)。 |
| OkHttpCall | RequestFactory + args 建 Request,OkHttp 执行,responseConverter 转 body。 |
| CallAdapter / Converter | 适配返回类型;请求/响应体与对象互转。 |
| ParameterHandler | Path、Query、Body、Field、Part 等把参数值填进 Request。 |
常见追问:
- 为什么没有接口实现类?→ 用 JDK 动态代理在运行时生成,所有方法进 InvocationHandler.invoke,由 Retrofit 按注解拼 Request 并发请求。
- ServiceMethod 为什么缓存?→ 每个方法只解析一次(注解、参数类型、返回类型),解析后存
Map<Method, ServiceMethod>,避免重复解析。 - Retrofit 用了哪些设计模式?→ 动态代理、建造者、适配器(CallAdapter)、策略(Converter)、工厂(Converter.Factory、CallAdapter.Factory)。
面试速查与深挖
OkHttp 必答点:连接池(Route 匹配、复用与清理、maxIdle/keepAlive)、拦截器链顺序(应用 → RetryAndFollowUp → Bridge → Cache → Connect → 网络 → CallServer)、同步/异步线程模型(异步回调在 Dispatcher 线程池、主线程更新 UI 需切线程)、ResponseBody 只能读一次、内存泄漏(onDestroy 里 cancel Call、Callback 弱引用或静态内部类)。
Retrofit 必答点:基于 OkHttp;动态代理(Proxy + InvocationHandler)创建接口实现;ServiceMethod 解析注解拼 Request;CallAdapter 适配返回类型(Call/Observable/suspend);Converter 做请求/响应体与对象转换;拦截器即 OkHttp 的 Interceptor,通过 client 配置。
常被追问:
- 应用拦截器和网络拦截器区别?→ 执行时机(缓存命中时网络拦截器不执行)、可见范围(网络层可见真实请求与重定向)。
- 连接池怎么复用?→ 按 Route(host+port+协议+代理)匹配,取连接时检查健康,用完后放回池并记空闲时间,定时清理超时或超额空闲连接。
- Retrofit 和 OkHttp 分工?→ Retrofit 管接口定义、注解解析、Request 拼装、适配与转换;OkHttp 管连接、IO、缓存、拦截器执行。
- 如何保证异步不泄漏?→ Activity 销毁时 cancel Call、Callback 用弱引用或静态内部类、避免 Activity 直接持有 Call;Retrofit/OkHttp 由单例或 Application 持有。
- 多个 Converter/CallAdapter 顺序?→ 按 add 顺序询问,第一个返回非 null 的被采用;更具体的 Factory 应放前面。
常见面试题速查(下部对应章节)
| 题目 | 简答 / 参见 |
|---|---|
| Converter 的作用是什么? | 把 Java 对象与 HTTP 请求体/响应体互转,接口可直接用 Call<User>、@Body User;选型在 ServiceMethod 解析时按类型问 Factory,第一个非 null 的用。见 13.1、13.4。 |
| 为什么先选 CallAdapter 再选 Converter? | 响应体要转成的类型来自 CallAdapter 的 responseType(),所以先按返回类型定 CallAdapter,再用其 responseType 去选 Converter。见 14.1、14.4。 |
| CallAdapter 和 Converter 谁先执行? | 先 Converter 把 ResponseBody 转成 T(OkHttpCall 内),再 CallAdapter.adapt 把 Call<T> 包装成方法返回类型。见 14.3、14.4。 |
| Retrofit 怎么加拦截器? | 没有“Retrofit 专属拦截器”,给 OkHttpClient 配置 addInterceptor/addNetworkInterceptor,再 .client(client) 传给 Retrofit。见 15.1。 |
| 怎么给 Retrofit 加 Token、处理 401? | 应用拦截器里给 request 加 Authorization Header;401 用 OkHttp 的 Authenticator 刷新 Token 后 return 新 Request,注意防并发刷新。见 15.3、19.3。 |
| 统一错误处理怎么做? | 网络异常(IOException)、HTTP 非 2xx、解析异常在 Repository/ViewModel 收敛成一种(ApiException/Result);方式有封装 Call、RxJava onErrorResumeNext、协程 runCatching。见 16.4、18.3。 |
| OkHttp 拦截器链顺序? | 应用拦截器 → RetryAndFollowUp → Bridge → Cache → Connect → 网络拦截器 → CallServer。见 20.1、上部第六章。 |
| 连接池怎么复用、怎么清理? | 按 Route 匹配,取时找 isEligible 且健康,用毕放回记 idleAtNanos;清理:空闲超 keepAlive 或超 maxIdle 时移除。见 20.3、上部第七章。 |
| Token 过期、401 怎么处理? | 拦截器统一加 Token;401 时在 Authenticator 里调刷新接口、存新 Token,return 带新 Token 的 Request,并加锁防并发刷新。见 19.3、15.3。 |
| 如何避免异步请求导致内存泄漏? | Activity/Fragment 销毁时 call.cancel() 并清空引用;Callback 用静态内部类+WeakReference 或判 get() 再更新 UI;Client/Retrofit 由单例或 Application 持有。见 19.5、上部 3.6。 |
| Retrofit 动态代理做了什么? | create(service) 用 Proxy.newProxyInstance 生成接口实现,所有方法调用进 InvocationHandler.invoke;内部 loadServiceMethod → 拼 Request → new OkHttpCall → adapt(call) 返回。见 21.1、21.2。 |
| 性能优化有哪些? | 单例 OkHttpClient/Retrofit、连接池与缓存、减少请求(缓存/批量/去重)、拦截器轻量、大响应用流式或分页、EventListener 监控。见 17.1~17.5。 |
| 如何做网络层单元测试? | MockWebServer 起本地 HTTP、enqueue MockResponse,Retrofit baseUrl 指过去;或 Mock ApiService,given().willReturn(call),验证 Repository。见 18.3。 |
| ResponseBody 能读几次? | 只能读一次,body().string() 或 byteStream() 读过后流即关闭,再次读会报错;errorBody() 同理。见上部。 |
| baseUrl 末尾要不要加 /? | 与 @GET("users") 拼接时,baseUrl 末尾有 / 则 base/users,无 / 则最后一节可能被替换,团队需统一规范。见 18.2、18.3。 |
| 如何取消请求? | call.cancel() 取消单次;或 Request 设 tag、用 client.dispatcher().cancel(tag) 按 tag 批量取消。见上部 3.4、19.5。 |
| 大文件上传/下载注意什么? | 上传:增大 writeTimeout、自定义 RequestBody 分段写并回调进度、页面销毁 cancel。下载:流式读、避免 body().string() 大响应 OOM。见 17.2、19.4、上部第五章。 |
| OkHttp / Retrofit 用了哪些设计模式? | OkHttp:责任链、建造者、单例、策略、工厂、门面。Retrofit:动态代理、建造者、适配器、策略、工厂。见 20.4、21.4、上部 1.7。 |
| ServiceMethod 为什么用缓存? | 每个方法只解析一次(注解、参数、返回类型、Converter/CallAdapter 选型),解析后存 Map,后续调用直接取,避免重复解析。见 21.1、21.4。 |
| 应用拦截器与网络拦截器执行次数? | 应用拦截器:每次请求至少执行一次(含命中缓存)。网络拦截器:仅真实发网络时执行,命中缓存或重定向中间请求可能多次。见 15.5、上部第六章。 |