Retrofit浅析

334 阅读12分钟

为什么需要对 OKHttp 进行封装?

Retrofit 本质上是对 OKHttp 的进一步封装。我们之所以需要 Retrofit,是因为 OKHttp 作为底层网络框架,存在以下几个“缺陷”或者说不够友好的地方:

  1. 接口配置繁琐
    用户在配置网络请求接口时,特别是当请求的 body、header、参数等复杂时,配置过程相当繁琐。
  2. 数据解析需要手动处理
    OKHttp 只负责网络传输,返回的是 ResponseBody,数据解析(如 JSON 解析)需要用户手动完成,不能复用通用的解析逻辑。
  3. 线程切换不自动
    OKHttp 回调在子线程,用户需要手动将回调切换到主线程,体验不友好。
  4. 回调地狱
    当存在嵌套网络请求时,很容易陷入回调地狱(Callback Hell),代码维护性和可读性差。

Retrofit 的设计思路

Retrofit 利用了**面向切面编程(AOP)**的思想,对所有接口调用进行了“切面代理”,实现了统一处理。

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 主要解决了哪些问题?

主要处理事项


常见疑问

  1. Interface 接口能直接用吗?
    能用。Retrofit 通过动态代理技术为接口生成实现类,无需手写实现。
  2. 参数是怎么传递的?
    Retrofit 会自动把接口方法的参数解析出来,根据注解(如 @Query@Body@Header 等)组装成请求参数。
  3. 具体请求 URL 怎么生成的?
    基于 baseUrl 和接口方法上的路径注解(如 @GET("xxx")),拼接生成最终 URL。
  4. 为什么所有请求都能统一管理?
    因为所有接口请求都通过动态代理的 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 过程

  1. 组装 OkHttpCall(封装 OkHttp 的实际请求)
  2. 通过 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. 流程总览

  1. Builder 构建 Retrofit 实例
  2. create() 用动态代理生成 API 实现
  3. 代理的 invoke 负责方法参数和注解解析,封装为 ServiceMethod
  4. ServiceMethod.invoke 组装成 OkHttpCall,适配为 Call
  5. 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 条。

【答案与解析】

答案要点:

  1. 声明式接口定义网络请求,代码更简洁,避免重复模板代码。
  2. 支持多种数据格式自动解析(如 JSON),通过 ConverterFactory 实现解耦。
  3. 自动线程切换,回调默认主线程,提升 UI 友好性。
  4. 统一参数注解和请求注解,支持灵活配置请求类型、参数、Header 等。
  5. 支持 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 实例,既避免资源浪费又保证配置解耦。