Android Retrofit ParameterHandler 笔记

4 阅读6分钟

ParameterHandler 是 Retrofit 中一个非常核心但又容易被忽视的组件。如果说动态代理是 Retrofit 的“大脑”,负责指挥整个流程,那么 ParameterHandler 就是“四肢”,负责将方法参数的每一个具体值,准确地填充到 HTTP 请求的对应位置

下面我们从设计目的、类结构、创建过程和工作流程四个维度来详细剖析。


🎯 ParameterHandler 的设计目的

在 Retrofit 中,我们通过注解来描述接口方法:

@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user, @Query("sort") String sort);

这里的 @Path("user")@Query("sort") 就是参数的“标签”。Retrofit 需要知道:

  • 对于 @Path("user"),当方法调用时传入的 "square",应该替换 URL 路径中的 {user}
  • 对于 @Query("sort"),传入的 "updated" 应该作为 URL 查询参数 sort=updated

ParameterHandler 的职责就是:接收一个具体的参数值,并把它正确地写入到即将构建的 RequestBuilder。每个被注解的参数都会对应一个 ParameterHandler 实例。


🏗️ 类结构:抽象与实现

ParameterHandler 是 Retrofit 中的一个抽象类,定义在 retrofit2.ParameterHandler 中。它的核心是一个泛型抽象方法:

abstract class ParameterHandler<T> {
    abstract void apply(RequestBuilder builder, T value) throws IOException;
}
  • T:参数的类型(如 StringIntegerList<String> 等)。
  • apply:将参数值 value 应用到 RequestBuilder 上。

对于每种注解类型,Retrofit 都有对应的具体实现类(都是静态内部类):

注解对应 ParameterHandler 类作用
@PathPath替换 URL 路径中的占位符
@QueryQuery添加查询参数
@QueryMapQueryMap添加多个查询参数(Map)
@BodyBody设置请求体
@FieldField添加表单字段(用于 @FormUrlEncoded
@FieldMapFieldMap添加多个表单字段
@PartPart添加多部分请求的一部分
@PartMapPartMap添加多个多部分部分
@HeaderHeader添加请求头
@HeaderMapHeaderMap添加多个请求头
@UrlRelativeUrl动态设置 URL
@QueryNameQueryName添加无值的查询参数(如 ?debug

除了这些,还有一些辅助类用于处理集合、数组和可空性,比如 ParameterHandler.IterableParameter 会包装另一个 ParameterHandler 来处理 List 类型。


🔨 创建过程:ServiceMethod 解析参数

ParameterHandler 是在构建 ServiceMethod 时创建的。这个过程发生在 ServiceMethod.Builderbuild() 方法中,它遍历方法的所有参数,并为每个参数调用 parseParameter()

简化后的逻辑大致如下:

private ParameterHandler<?> parseParameter(int index, Type type, Annotation[] annotations) {
    ParameterHandler<?> result = null;
    // 遍历该参数上的所有注解
    for (Annotation annotation : annotations) {
        ParameterHandler<?> handler = parseParameterAnnotation(index, type, annotations, annotation);
        if (handler == null) continue;
        if (result != null) {
            throw methodError("Multiple Retrofit annotations found, only one allowed.");
        }
        result = handler;
    }
    if (result == null) {
        throw methodError("No Retrofit annotation found.");
    }
    return result;
}

核心在 parseParameterAnnotation 中,它根据注解类型创建对应的 ParameterHandler。例如:

  • 如果注解是 @Path,它会检查该参数类型是否可以被 URL 编码,然后返回一个 ParameterHandler.Path
  • 如果注解是 @Query,它会根据参数的实际类型(原始类型、可空类型、集合、数组等)决定返回哪种 ParameterHandler。例如,如果参数是 List<String>,它会创建一个 ParameterHandler.Query,并包装一个用于迭代的 ParameterHandler.IterableParameter

在创建 ParameterHandler 时,还会将注解中指定的名称(如 @Path("user") 中的 "user")和相关的转换器(如 Converter)传入。对于像 @Body 这样的注解,它会持有请求体转换器(Converter<T, RequestBody>)。

最终,所有参数的 ParameterHandler 被收集到一个数组中,保存在 ServiceMethodparameterHandlers 字段中。


⚙️ 工作流程:从参数到 RequestBuilder

当我们在运行时调用接口方法(如 listRepos("square", "updated"))时,动态代理会触发 InvocationHandler.invoke,然后调用 loadServiceMethod(method) 获取或创建 ServiceMethod,并用传入的参数数组 args 创建一个 OkHttpCall

OkHttpCall 内部,当需要真正构建 okhttp3.Request 时,会调用 ServiceMethod.toRequest(args)。这个方法的核心逻辑就是:

Request toRequest(Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(...);
    // 参数处理
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
        throw new IllegalArgumentException("Argument count mismatch.");
    }

    for (int i = 0; i < argumentCount; i++) {
        handlers[i].apply(requestBuilder, args[i]);
    }

    return requestBuilder.build();
}

这里就是 ParameterHandler 施展魔法的地方:遍历每个参数的 handler,调用其 apply 方法,将参数值写入 RequestBuilder


🔍 典型实现分析

我们挑几个典型的实现来看看它们是如何工作的。

1. Path

static final class Path<T> extends ParameterHandler<T> {
    private final String name;
    private final Converter<T, String> valueConverter;

    @Override
    void apply(RequestBuilder builder, T value) throws IOException {
        if (value == null) {
            throw new IllegalArgumentException("Path parameter \"" + name + "\" must not be null.");
        }
        // 将参数值转换为字符串(可能经过编码)
        String stringValue = valueConverter.convert(value);
        builder.addPathParam(name, stringValue);
    }
}

builder.addPathParam 内部会用 stringValue 替换 URL 模板中的 {name} 占位符。

2. Query

static final class Query<T> extends ParameterHandler<T> {
    private final String name;
    private final Converter<T, String> valueConverter;
    private final boolean encoded;

    @Override
    void apply(RequestBuilder builder, T value) throws IOException {
        if (value == null) return; // 忽略 null 值
        String stringValue = valueConverter.convert(value);
        builder.addQueryParam(name, stringValue, encoded);
    }
}

对于集合类型,Retrofit 会使用 ParameterHandler.IterableParameter 包装 Query,在 apply 时迭代调用 Query.apply 多次,从而生成 ?name=value1&name=value2

3. Body

static final class Body<T> extends ParameterHandler<T> {
    private final Converter<T, RequestBody> converter;

    @Override
    void apply(RequestBuilder builder, T value) {
        if (value == null) {
            throw new IllegalArgumentException("Body parameter value must not be null.");
        }
        RequestBody body;
        try {
            body = converter.convert(value);
        } catch (IOException e) {
            throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
        }
        builder.setBody(body);
    }
}

它直接使用配置的 Converter 将对象转换为 RequestBody,然后设置给 RequestBuilder

4. Header

static final class Header<T> extends ParameterHandler<T> {
    private final String name;
    private final Converter<T, String> valueConverter;

    @Override
    void apply(RequestBuilder builder, T value) throws IOException {
        if (value == null) return;
        String stringValue = valueConverter.convert(value);
        builder.addHeader(name, stringValue);
    }
}

类似地,它把参数值作为请求头的值添加进去。


🧩 处理集合与可空性的技巧

Retrofit 为了支持集合参数(如 @Query List<String>),巧妙地使用了组合模式。它定义了一个 ParameterHandler.IterableParameter 类:

static final class IterableParameter<T> extends ParameterHandler<Iterable<T>> {
    private final ParameterHandler<T> handler;

    @Override
    void apply(RequestBuilder builder, Iterable<T> values) throws IOException {
        if (values == null) return;
        for (T value : values) {
            handler.apply(builder, value);
        }
    }
}

当检测到参数是 Iterable 类型时,parseParameterAnnotation 会递归地为元素类型创建一个 ParameterHandler(比如 Query<String>),然后用 IterableParameter 包装它。这样,在调用 apply 时就会循环处理每个元素。

对于数组,也有类似的 ArrayParameterHandler

对于可空性,大多数 handler 都会检查 value == null 然后直接返回(不抛出异常),除非像 @Path 那样必须非空。


📦 与 RequestBuilder 的交互

RequestBuilder 是 OkHttp 的 Request 的建造器,它内部封装了 okhttp3.Request.Builder,并提供了各种添加参数的方法,例如:

  • addPathParam(name, value)
  • addQueryParam(name, value, encoded)
  • addHeader(name, value)
  • setBody(RequestBody)
  • addFormField(name, value)
  • addPart(Headers headers, RequestBody body)

每个 ParameterHandler 最终都会调用这些方法来修改请求。


💡 总结

ParameterHandler 是 Retrofit 将声明式接口转换为实际 HTTP 请求的“施工队”。它的设计体现了几个优秀的软件工程原则:

  1. 单一职责:每个 handler 只负责处理一种注解,逻辑清晰。
  2. 组合优于继承:通过 IterableParameter 等包装类,优雅地支持了集合和数组。
  3. 延迟绑定:handler 在构建 ServiceMethod 时创建,与实际参数值分离,保证了线程安全。
  4. 可扩展性:如果要支持新的注解类型,只需要新增一个 ParameterHandler 子类,并在 parseParameterAnnotation 中添加分支即可(虽然不开放给开发者,但框架内部可以轻易扩展)。

理解 ParameterHandler,能让你更清楚地知道当你在 Retrofit 接口中写一个 @Query Map<String, String> 时,背后发生了怎样的精妙转换。