OkHttp & Retrofit 完全指南——下

28 阅读32分钟

第四部分:Retrofit 高级特性

第十三章:Retrofit 数据转换器(Converter)

13.1 Converter 的作用

一句话:Converter 把 Java 对象HTTP 请求体/响应体 互相转换,接口里可直接用 UserList<User>,不必手写 JSON 拼装与解析。

为什么需要:没有 Converter 时只能写 Call<ResponseBody>,自己 body().string() 再解析,发请求也要自己 RequestBody.create(gson.toJson(user), ...)。有 Converter 后写成 Call<User>@Body User,由 Retrofit 在拼 Request 时、拿到 Response 后自动转换。

两个方向

方向输入输出
请求体方法参数(如 @Body User userRequestBody(如 JSON 字节流)
响应体ResponseBody(网络返回的原始字节)方法返回类型的泛型(如 User

类型从哪来:请求体看 @Body 的参数类型,响应体看 返回类型泛型(如 Call<User>User)。如何注册addConverterFactory(Factory),可多个;按添加顺序问每个 Factory 能否处理该类型,第一个返回非 null 的被选用。


13.2 常用转换器(Gson、Jackson、Moshi)

三种都是“添加依赖 → addConverterFactory(XXX)”的用法,选一个即可(通常 Gson 或 Moshi)。

转换器依赖(示例)用法特点
Gsonconverter-gson + gsonaddConverterFactory(GsonConverterFactory.create())create(gson)常用,可自定义 GsonBuilder(日期、null、字段名策略等)
Jacksonconverter-jackson + jackson-databindaddConverterFactory(JacksonConverterFactory.create())create(mapper)Java 生态常用,注解丰富,可配置 ObjectMapper
Moshiconverter-moshi + moshiaddConverterFactory(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 …: TRetrofit 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) 异步;简单直接
RxJava3adapter-rxjava3 + rxjava + rxandroidaddCallAdapterFactory(RxJava3CallAdapterFactory.create())返回 Observable/Single/Maybe/Completable;链式、线程切换、背压
RxJava2adapter-rxjava2 + rxjavaRxJava2CallAdapterFactory.create()与 RxJava3 二选一,不可同时用
suspendRetrofit 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(): UserLiveData<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
LevelNONE / BASIC(请求行+响应行)/ HEADERS / BODY(完整 body,调试用)
添加方式addInterceptoraddNetworkInterceptor;前者命中缓存也会打印,后者仅真实发网络时打印。
生产环境建议 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.BuilderaddInterceptor / 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()IOExceptiontry-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>,希望业务层只面对一种回调。
RxJavamapResult<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.FactoryeventListenerFactory(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 上设 connectTimeoutreadTimeoutwriteTimeout;弱网、大文件可适当加大。
表现超时后在 onFailureexecute() 抛出 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 内存泄漏

原因处理
未取消 CallActivity/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)RealCallgetResponseWithInterceptorChain() 建链 → 各拦截器 proceed 下推 → ConnectInterceptorConnectionPool 取/建 RealConnection、拿 ExchangeCallServerInterceptor 写请求、读响应 → 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 内 ExchangeFinderConnectionPool 取或建 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 阅读顺序与核心类

阅读顺序内容
1OkHttpClient、Request、Response(Builder、不可变)。
2RealCall:execute/enqueue → getResponseWithInterceptorChain()。
3RealInterceptorChain:proceed() 与 index、递归。
4五内置拦截器:RetryAndFollowUp、Bridge、Cache、Connect、CallServer。
5ConnectionPool(get/put/cleanup)、RealConnection(connect、isEligible、isHealthy)。
6Cache、DiskLruCache、CacheStrategy。

技巧:对一次 GET 请求从 RealCall.execute() 断点跟到 getResponseWithInterceptorChain() 和 proceed() 递归,再看 ConnectInterceptor 取 connection 与 exchange。

核心类职责简述
OkHttpClient配置入口,持有 Dispatcher、ConnectionPool、拦截器、Cache 等。
RealCall单次请求入口;execute/enqueue 最终进 getResponseWithInterceptorChain()。
Request/Response不可变封装,Builder 构建。
RealInterceptorChainproceed() 按 index 调下一拦截器或真实 IO。
ConnectionPool / RealConnection空闲连接 Deque、按 Route 取/建/清理;单条连接 isEligible、isHealthy。
Cache / CacheInterceptorDiskLruCache、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 整体架构

阶段组件做的事
创建 APIRetrofit.create(service)动态代理生成接口实现,方法调用进 InvocationHandler。
方法调用InvocationHandler.invokeloadServiceMethod(method) → ServiceMethod(RequestFactory、CallAdapter、responseConverter)→ 用参数构建 Request → new OkHttpCall → adapt(call) 适配成返回类型。
执行请求OkHttpCallOkHttpClient.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
invokeObject 方法直接 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。
RequestFactoryServiceMethod + 参数 → OkHttp Request(URL、method、headers、body)。
OkHttpCallRequestFactory + args 建 Request,OkHttp 执行,responseConverter 转 body。
CallAdapter / Converter适配返回类型;请求/响应体与对象互转。
ParameterHandlerPath、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、上部第六章。