为什么需要对 OKHttp 进行封装?
Retrofit 本质上是对 OKHttp 的进一步封装。我们之所以需要 Retrofit,是因为 OKHttp 作为底层网络框架,存在以下几个“缺陷”或者说不够友好的地方:
- 接口配置繁琐
用户在配置网络请求接口时,特别是当请求的 body、header、参数等复杂时,配置过程相当繁琐。 - 数据解析需要手动处理
OKHttp 只负责网络传输,返回的是ResponseBody,数据解析(如 JSON 解析)需要用户手动完成,不能复用通用的解析逻辑。 - 线程切换不自动
OKHttp 回调在子线程,用户需要手动将回调切换到主线程,体验不友好。 - 回调地狱
当存在嵌套网络请求时,很容易陷入回调地狱(Callback Hell),代码维护性和可读性差。
Retrofit 的设计思路
Retrofit 利用了**面向切面编程(AOP)**的思想,对所有接口调用进行了“切面代理”,实现了统一处理。
Retrofit 内部通过动态代理完成这种统一操作:
- 动态代理 -> AOP -> 切面
- 所有的网络请求都可以在 BaseUrl 下归一化管理
典型用法
retrofit = new Retrofit.Builder()
.baseUrl("https://ww.wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.build();
ISharedListService sharedListService = retrofit.create(ISharedListService.class);
Retrofit 的 create 方法使用了动态代理,为接口生成了代理对象,所有调用都被拦截和归一化处理。
Call<SharedListBean> sharedListCall = sharedListService.getSharedList(2, 1);
调用 getSharedList 时,实际执行的是代理的 invoke 方法,所以 Retrofit 可以对所有接口的调用做统一拦截和处理,将请求参数、配置等流程归一化。
Retrofit 主要解决了哪些问题?
常见疑问
- Interface 接口能直接用吗?
能用。Retrofit 通过动态代理技术为接口生成实现类,无需手写实现。 - 参数是怎么传递的?
Retrofit 会自动把接口方法的参数解析出来,根据注解(如@Query、@Body、@Header等)组装成请求参数。 - 具体请求 URL 怎么生成的?
基于baseUrl和接口方法上的路径注解(如@GET("xxx")),拼接生成最终 URL。 - 为什么所有请求都能统一管理?
因为所有接口请求都通过动态代理的invoke方法执行,Retrofit 可以统一拦截、配置、解析和适配所有网络请求。
Retrofit 核心源码分析
Retrofit 类的核心成员如下:
public final class Retrofit {
// 缓存每个接口方法解析出来的 ServiceMethod
private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();
// 网络请求的工厂,默认用 OkHttp
final okhttp3.Call.Factory callFactory;
// 网络请求的 baseUrl
final HttpUrl baseUrl;
// 数据转换器工厂集合(如 Gson、Moshi、Scalars 等)
final List<Converter.Factory> converterFactories;
// Call 适配器工厂集合(如同步/异步/协程等适配)
final List<CallAdapter.Factory> callAdapterFactories;
// 回调方法执行器(线程切换)
final @Nullable Executor callbackExecutor;
// 是否启动时立即验证接口方法
final boolean validateEagerly;
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
@Nullable Executor callbackExecutor, boolean validateEagerly) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
this.converterFactories = converterFactories; // 不可变列表
this.callAdapterFactories = callAdapterFactories; // 不可变列表
this.callbackExecutor = callbackExecutor;
this.validateEagerly = validateEagerly;
}
}
Retrofit.Builder 的 build 方法分析
public Retrofit build() {
if (baseUrl == null) {
throw new IllegalStateException("Base URL required.");
}
okhttp3.Call.Factory callFactory = this.callFactory;
// 默认使用 OkHttpClient 作为底层实现
if (callFactory == null) {
callFactory = new OkHttpClient();
}
// 回调线程切换,安卓下默认用 Handler 切到主线程
Executor callbackExecutor = this.callbackExecutor;
if (callbackExecutor == null) {
callbackExecutor = platform.defaultCallbackExecutor();
}
// 构建 CallAdapter.Factory 集合
List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
// 构建 Converter.Factory 集合,内置的 BuiltInConverters 放最前,防止被覆盖
List<Converter.Factory> converterFactories = new ArrayList<>(
1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());
converterFactories.add(new BuiltInConverters());
converterFactories.addAll(this.converterFactories);
converterFactories.addAll(platform.defaultConverterFactories());
return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
总结
- Retrofit 主要是为了解决 OKHttp 在接口定义、数据解析、线程切换、请求归一化等方面的不足。
- 它采用了 AOP(面向切面编程)和动态代理,实现了对所有网络请求的统一封装和拦截。
- 通过各种注解、适配器、转换器,Retrofit 实现了声明式的接口定义和请求管理,大大降低了开发成本和出错概率。
Retrofit 简单使用案例
首先,Retrofit 要求只能使用接口,这是因为其内部用的是 JDK 动态代理技术。动态代理只能为接口生成代理类,不能直接代理普通类(因为 Java 单继承,Proxy 必须继承 Proxy 基类)。
1. 定义 API 接口
public interface WeatherApi {
@POST("/v3/weather/weatherInfo")
@FormUrlEncoded
Call<ResponseBody> postWeather(@Field("city") String city, @Field("key") String key);
@GET("/v3/weather/weatherInfo")
Call<ResponseBody> getWeather(@Query("city") String city, @Query("key") String key);
// 支持RxJava等响应式编程
@GET("/v3/weather/weatherInfo")
Observable<ResponseBody> getWeather3(@Query("city") String city, @Query("key") String key);
}
2. 创建 Retrofit 实例和请求调用
// 创建 Retrofit 实例,配置 baseUrl
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://restapi.amap.com")
.build();
// 生成接口实现类(实际是代理对象)
WeatherApi weatherApi = retrofit.create(WeatherApi.class);
// 通过接口方法获取 Call 对象,这一步实际上完成了参数、注解等的解析和请求准备
Call<ResponseBody> call = weatherApi.getWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
// 异步发起请求(enqueue 内部自动线程切换,无需手动切换线程)
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
ResponseBody body = response.body();
try {
String string = body.string();
Log.i(TAG, "onResponse get: " + string);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (body != null) body.close();
}
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
// 处理失败
}
});
3. 模式解析
- 构建者模式(Builder Pattern)
Retrofit.Builder()用于灵活构造 Retrofit 实例,支持链式调用和参数自定义。 - 外观模式(Facade Pattern/门面模式)
Retrofit 封装了 OkHttp、Converter、CallAdapter 等多个子系统,向用户只暴露一个简洁接口(即Retrofit实例和 API 接口),用户不用关心内部的复杂细节,极大降低了使用难度和维护成本。
4. 总结
- 定义接口 → 生成代理对象 → 参数/注解自动解析 → 统一管理网络请求,极大简化了网络层代码。
- 支持同步(
execute())、异步(enqueue())和响应式(如 RxJava)的多种调用方式。 - 封装了线程切换、参数注解、数据解析等重复性操作,让开发者专注于业务。
Retrofit 源码调用流程解析
1. 构建 Retrofit 实例(Builder 模式)
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://restapi.amap.com")
.build();
Retrofit 实例的构建就是配置一堆成员变量(如 baseUrl、callFactory、callbackExecutor、adapterFactories、converterFactories),如果用户没配置,则用默认实现。
2. 动态代理生成 API 实现类
weatherApi = retrofit.create(WeatherApi.class);
create 方法利用 JDK 动态代理为接口生成实现。所有对 API 方法的调用,都会走到 InvocationHandler 的 invoke() 方法:
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) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args); // 如 toString
}
if (Platform.get().isDefaultMethod(method)) {
return Platform.get().invokeDefaultMethod(method, service, proxy, args);
}
// Retrofit 的核心:把接口方法和参数解析为一个 ServiceMethod
return loadServiceMethod(method).invoke(args != null ? args : new Object[0]);
}
}
);
}
3. 方法与参数的解析
3.1 ServiceMethod 加载与缓存
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
核心思想:所有接口方法会被反射分析注解,并缓存成 ServiceMethod 对象。
3.2 方法注解、参数注解分析
- 方法相关(@GET、@POST、@Url...)
- 参数相关(@Query、@Body、@Header...)
解析都在 RequestFactory 里完成,ServiceMethod.parseAnnotations 会做这些事。
4. 调用 invoke,得到 Call 对象
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// 方法注解、参数注解解析
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
...
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
abstract T invoke(Object[] args);
}
invoke 过程
- 组装 OkHttpCall(封装 OkHttp 的实际请求)
- 通过 CallAdapter 进行适配,生成真正对外暴露的 Call 或 Observable/RxJava/协程返回对象
@Override
final ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args); // 用适配器处理
}
5. CallAdapter 和线程切换
CallAdapter 的作用
将 OkHttpCall 适配成不同风格(普通 Call、RxJava、协程等)的返回类型,同时处理线程切换。
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
...
return new CallAdapter<Object, Call<?>>() {
public Type responseType() { return responseType; }
public Call<Object> adapt(Call<Object> call) {
// 用 MainThreadExecutor 包装,保证回调在主线程
return (executor == null) ? call : new ExecutorCallbackCall(executor, call);
}
};
}
ExecutorCallbackCall 内部就是用 Handler.post 保证回调在主线程(Android 平台)。
6. OkHttpCall 的 enqueue 过程
真正发起网络请求,其内部调用的还是 OkHttp 的 enqueue,但数据解析、异常处理、线程切换都被 Retrofit 封装掉了。
@Override
public void enqueue(final Callback<T> callback) {
...
call.enqueue(new okhttp3.Callback() {
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response = parseResponse(rawResponse); // 解析数据
callback.onResponse(OkHttpCall.this, response); // 回调到主线程
}
public void onFailure(okhttp3.Call call, IOException e) {
callback.onFailure(OkHttpCall.this, e);
}
});
}
7. MainThreadExecutor 保证主线程回调(Android)
static final class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override public void execute(Runnable r) {
handler.post(r);
}
}
8. 流程总览
- Builder 构建 Retrofit 实例
- create() 用动态代理生成 API 实现
- 代理的 invoke 负责方法参数和注解解析,封装为 ServiceMethod
- ServiceMethod.invoke 组装成 OkHttpCall,适配为 Call
- enqueue 调用 OkHttp 实际发请求,回调自动切换主线程
总结
- 核心本质:用动态代理统一封装网络请求和数据解析,CallAdapter/Converter/Executor 实现解耦和灵活扩展。
- 主线程回调、多风格适配、参数注解自动映射,极大简化开发。
一些问题
Retrofit 完全可以封装为单例,实际上这是推荐做法
原因和官方建议
-
Retrofit 线程安全、无状态,可以安全单例化
- Retrofit 的核心成员都是不可变对象(final),比如 ConverterFactory、CallAdapterFactory、baseUrl、OkHttpClient 等。
- Retrofit 没有全局状态,不会持有 Context 或 Activity 等资源引用,也不会造成内存泄漏。
-
官方文档和绝大多数业界项目都把 Retrofit 封装为单例
- Retrofit 官网例子、Google Jetpack 官方 Demo、各大公司架构代码,都是把 Retrofit 作为全局单例持有。
实际建议用法
public class RetrofitManager {
private static volatile Retrofit retrofit;
private static final Object LOCK = new Object();
public static Retrofit getInstance() {
if (retrofit == null) {
synchronized (LOCK) {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
}
return retrofit;
}
}
- 配合 OkHttpClient 的单例,减少连接池和线程池资源浪费。
- 每次 new Retrofit 反而更低效,而且有可能导致资源重复、管理混乱。
学后检测
一、单选题
1. 下列关于 Retrofit 的说法,哪项是正确的?
A. Retrofit 只能用作每次请求新建实例,不能单例
B. Retrofit 的接口可以是普通类或抽象类
C. Retrofit 采用动态代理技术为接口生成实现类
D. Retrofit 必须手动切换线程,回调默认在子线程
【答案与解析】
答案:C
解析:
A 错误,Retrofit 推荐单例;B 错误,只能接口;C 正确,Retrofit 内部采用 JDK 动态代理;D 错误,Retrofit 默认主线程回调。
2. Retrofit 的数据解析(如 JSON -> JavaBean)依赖于哪种设计?
A. CallAdapter
B. ConverterFactory
C. OkHttpClient
D. Proxy
【答案与解析】
答案:B
解析:
ConverterFactory 决定数据的解析方式,比如 GsonConverterFactory 用于 JSON 转 JavaBean。
3. Retrofit 默认的网络请求底层是基于哪一个框架实现的?
A. HttpURLConnection
B. HttpClient
C. OkHttp
D. Volley
【答案与解析】
答案:C
解析:
Retrofit 默认底层实现是 OkHttp,代码中默认 callFactory 就是 OkHttpClient。
二、多选题
4. 下列哪些选项属于 Retrofit 对 OKHttp 的改进和封装?
A. 自动进行线程切换,回调到主线程
B. 支持声明式接口定义网络请求
C. 手动解析 response body
D. 支持多种数据格式解析,如 JSON、XML
E. 统一参数注解和请求类型注解(如 @GET、@POST)
【答案与解析】
答案:A、B、D、E
解析:
C 错误,Retrofit 自动解析 response body,不用手动。其他都是 Retrofit 的封装优势。
5. 关于 Retrofit 的回调线程,下列哪些说法正确?
A. 回调线程默认是主线程
B. 可以通过 Executor 配置回调线程
C. 如果不用 Android,可以自定义回调线程
D. Retrofit 强制只能在主线程回调
【答案与解析】
答案:A、B、C
解析:
D 错误,可以自定义 Executor,并不强制主线程;A、B、C 都是对的。
三、判断题
6. Retrofit 只能为接口生成代理对象,不能为普通类或抽象类生成代理对象。 ( )
A. 正确
B. 错误
【答案与解析】
答案:A
解析:
Retrofit 采用 JDK Proxy 动态代理,只能代理接口。
7. Retrofit 每次请求都必须 new 一个实例,不能单例化。 ( )
A. 正确
B. 错误
【答案与解析】
答案:B
解析:
Retrofit 推荐单例,官方和大多数企业项目都这么做。
8. Retrofit 默认使用 OkHttp 的同步和异步请求能力。 ( )
A. 正确
B. 错误
【答案与解析】
答案:A
解析:
底层请求本质就是 OkHttp 的 execute/enqueue。
四、简答题
9. 简述 Retrofit 封装 OkHttp 的优势,至少列举 3 条。
【答案与解析】
答案要点:
- 声明式接口定义网络请求,代码更简洁,避免重复模板代码。
- 支持多种数据格式自动解析(如 JSON),通过 ConverterFactory 实现解耦。
- 自动线程切换,回调默认主线程,提升 UI 友好性。
- 统一参数注解和请求注解,支持灵活配置请求类型、参数、Header 等。
- 支持 RxJava、协程等多种响应式/异步编程风格,通过 CallAdapterFactory 适配。
10. Retrofit 为什么采用动态代理生成接口实现?如果不用接口,Retrofit 还能实现同样的灵活性吗?为什么?
【答案与解析】
答案要点:
- 动态代理能实现“声明式接口”转“真实 HTTP 请求”的自动化,无需手写实现类,极大降低开发量。
- 只能代理接口是 JDK Proxy 的技术限制(Java 单继承)。
- 如果不用接口,只能反射或手写实现,灵活性和可维护性都大打折扣,且难以动态统一管理和拦截。
五、编程题
11. 封装一个 Retrofit 单例工具类,要求支持自定义 baseUrl、添加 Gson 转换器,保证线程安全。
【答案与解析】
public class RetrofitSingleton {
private static volatile Retrofit retrofit;
public static Retrofit getInstance(String baseUrl) {
if (retrofit == null) {
synchronized (RetrofitSingleton.class) {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
}
return retrofit;
}
}
解析:
- 使用双重检查锁单例模式保证线程安全。
- 支持自定义 baseUrl,支持 Gson 转换器。
- retrofit 本身是线程安全的,可以全局复用。
六、附加题(应用理解)
12. 假如你的项目中需要同时请求两个不同的 baseUrl(比如主 API 和第三方服务),Retrofit 单例怎么设计?
【答案与解析】
答案思路:
- 可以为每个 baseUrl 单独创建一个 Retrofit 单例,分别管理。
- 推荐使用 Map<String, Retrofit> 做多 baseUrl 管理。例如:
public class RetrofitManager {
private static final Map<String, Retrofit> retrofitMap = new ConcurrentHashMap<>();
public static Retrofit getRetrofit(String baseUrl) {
return retrofitMap.computeIfAbsent(baseUrl, key ->
new Retrofit.Builder()
.baseUrl(key)
.addConverterFactory(GsonConverterFactory.create())
.build()
);
}
}
解析:
- 这样每个域名只持有一个 Retrofit 实例,既避免资源浪费又保证配置解耦。