在 Android 项目里,
@GET("users/{id}")加上一段接口声明,就能得到可调用的网络 API;问题是,接口没有实现类时,请求究竟由谁构造、谁执行、谁把结果交回业务代码?Retrofit 用运行时注解、反射和 JDK 动态代理在运行时把接口方法解析为可复用并缓存的请求说明,再把每次实参送入 HTTP 调用;Room、Hilt 更倾向在编译期生成实现,而 AIDL 生成的是 Binder 跨进程协议边界,不能把它当作 Retrofit 式 HTTP 代理。
版本与边界: 本文以 Retrofit 2.x 的公开源码结构和 Java 8+ 的Proxy/InvocationHandler为主;ServiceMethod、协程适配和 AndroidX 生成类的具体内部名称会随版本变化。文中的 Java 代码是教学性简化,不是 Retrofit 源码。
先看一条 Android API 调用
java
interface UserApi {
@GET("users/{id}")
Call<User> loadUser(@Path("id") long id);
}
UserApi api = retrofit.create(UserApi.class);
Call<User> call = api.loadUser(42L);
UserApi 只有接口和注解,api 却能响应调用。关键不是让反射直接做网络 I/O,而是把 第一次的方法元数据解析 与 之后每次的参数绑定和执行 分开:代理接住 Method,缓存保存解析后的计划,OkHttp 等执行层处理真正的网络工作。
#mermaid-svg-pfqU0loNJyGSSlnE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pfqU0loNJyGSSlnE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pfqU0loNJyGSSlnE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pfqU0loNJyGSSlnE .error-icon{fill:#552222;}#mermaid-svg-pfqU0loNJyGSSlnE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pfqU0loNJyGSSlnE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pfqU0loNJyGSSlnE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pfqU0loNJyGSSlnE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pfqU0loNJyGSSlnE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pfqU0loNJyGSSlnE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pfqU0loNJyGSSlnE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pfqU0loNJyGSSlnE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pfqU0loNJyGSSlnE .marker.cross{stroke:#333333;}#mermaid-svg-pfqU0loNJyGSSlnE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pfqU0loNJyGSSlnE p{margin:0;}#mermaid-svg-pfqU0loNJyGSSlnE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pfqU0loNJyGSSlnE .cluster-label text{fill:#333;}#mermaid-svg-pfqU0loNJyGSSlnE .cluster-label span{color:#333;}#mermaid-svg-pfqU0loNJyGSSlnE .cluster-label span p{background-color:transparent;}#mermaid-svg-pfqU0loNJyGSSlnE .label text,#mermaid-svg-pfqU0loNJyGSSlnE span{fill:#333;color:#333;}#mermaid-svg-pfqU0loNJyGSSlnE .node rect,#mermaid-svg-pfqU0loNJyGSSlnE .node circle,#mermaid-svg-pfqU0loNJyGSSlnE .node ellipse,#mermaid-svg-pfqU0loNJyGSSlnE .node polygon,#mermaid-svg-pfqU0loNJyGSSlnE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pfqU0loNJyGSSlnE .rough-node .label text,#mermaid-svg-pfqU0loNJyGSSlnE .node .label text,#mermaid-svg-pfqU0loNJyGSSlnE .image-shape .label,#mermaid-svg-pfqU0loNJyGSSlnE .icon-shape .label{text-anchor:middle;}#mermaid-svg-pfqU0loNJyGSSlnE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pfqU0loNJyGSSlnE .rough-node .label,#mermaid-svg-pfqU0loNJyGSSlnE .node .label,#mermaid-svg-pfqU0loNJyGSSlnE .image-shape .label,#mermaid-svg-pfqU0loNJyGSSlnE .icon-shape .label{text-align:center;}#mermaid-svg-pfqU0loNJyGSSlnE .node.clickable{cursor:pointer;}#mermaid-svg-pfqU0loNJyGSSlnE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pfqU0loNJyGSSlnE .arrowheadPath{fill:#333333;}#mermaid-svg-pfqU0loNJyGSSlnE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pfqU0loNJyGSSlnE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pfqU0loNJyGSSlnE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pfqU0loNJyGSSlnE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pfqU0loNJyGSSlnE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pfqU0loNJyGSSlnE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pfqU0loNJyGSSlnE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pfqU0loNJyGSSlnE .cluster text{fill:#333;}#mermaid-svg-pfqU0loNJyGSSlnE .cluster span{color:#333;}#mermaid-svg-pfqU0loNJyGSSlnE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pfqU0loNJyGSSlnE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pfqU0loNJyGSSlnE rect.text{fill:none;stroke-width:0;}#mermaid-svg-pfqU0loNJyGSSlnE .icon-shape,#mermaid-svg-pfqU0loNJyGSSlnE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pfqU0loNJyGSSlnE .icon-shape p,#mermaid-svg-pfqU0loNJyGSSlnE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pfqU0loNJyGSSlnE .icon-shape .label rect,#mermaid-svg-pfqU0loNJyGSSlnE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pfqU0loNJyGSSlnE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pfqU0loNJyGSSlnE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pfqU0loNJyGSSlnE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务代码:调用 UserApi
接口方法:@GET 与 @Path
Retrofit:JDK 动态代理
InvocationHandler:invoke
ServiceMethod:解析缓存
RequestFactory:构造请求
OkHttp:HTTP Call
Converter:响应转换
CallAdapter:交还结果
谁创建:Retrofit.create(UserApi.class) 创建代理;谁持有:Retrofit 持有服务方法缓存及工厂配置;何时触发:调用接口方法时;结果交给谁:CallAdapter 将底层调用包装为 Call、RxJava 类型或协程相关结果,再返回调用者。
注解不是魔法:保留策略与目标
注解由 @interface 定义。@Target 限制它可标在类、方法、参数或字段的哪里;@Retention 决定它在哪个阶段还存在。
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface GET {
String value();
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface Path {
String value();
}
| 保留策略 | 注解何时可见 | 适合什么 | Retrofit 是否可直接读取 |
| --- | --- | --- |
| SOURCE | 仅源码阶段 | Lint、编译器提示 | 否,编译后已丢失 |
| CLASS | 写入 class,但运行时通常不可反射读取 | 字节码工具 | 不应依赖 |
| RUNTIME | class 与运行时均可见 | 运行时框架解析 | 是 |
@GET 放在方法、@Path 放在参数不是形式主义:它让解析器能区分"请求是什么"和"调用者提供了什么"。 来源事实: Retrofit 会从 Method 读取方法和参数注解,并组合返回类型、泛型信息与配置工厂。 生产建议: 自定义运行时读取的注解必须使用 RUNTIME,并在解析阶段报清楚的配置错误。
#mermaid-svg-u8ODfTBA3IMBk28g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-u8ODfTBA3IMBk28g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-u8ODfTBA3IMBk28g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-u8ODfTBA3IMBk28g .error-icon{fill:#552222;}#mermaid-svg-u8ODfTBA3IMBk28g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-u8ODfTBA3IMBk28g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-u8ODfTBA3IMBk28g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-u8ODfTBA3IMBk28g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-u8ODfTBA3IMBk28g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-u8ODfTBA3IMBk28g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-u8ODfTBA3IMBk28g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-u8ODfTBA3IMBk28g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-u8ODfTBA3IMBk28g .marker.cross{stroke:#333333;}#mermaid-svg-u8ODfTBA3IMBk28g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-u8ODfTBA3IMBk28g p{margin:0;}#mermaid-svg-u8ODfTBA3IMBk28g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-u8ODfTBA3IMBk28g .cluster-label text{fill:#333;}#mermaid-svg-u8ODfTBA3IMBk28g .cluster-label span{color:#333;}#mermaid-svg-u8ODfTBA3IMBk28g .cluster-label span p{background-color:transparent;}#mermaid-svg-u8ODfTBA3IMBk28g .label text,#mermaid-svg-u8ODfTBA3IMBk28g span{fill:#333;color:#333;}#mermaid-svg-u8ODfTBA3IMBk28g .node rect,#mermaid-svg-u8ODfTBA3IMBk28g .node circle,#mermaid-svg-u8ODfTBA3IMBk28g .node ellipse,#mermaid-svg-u8ODfTBA3IMBk28g .node polygon,#mermaid-svg-u8ODfTBA3IMBk28g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-u8ODfTBA3IMBk28g .rough-node .label text,#mermaid-svg-u8ODfTBA3IMBk28g .node .label text,#mermaid-svg-u8ODfTBA3IMBk28g .image-shape .label,#mermaid-svg-u8ODfTBA3IMBk28g .icon-shape .label{text-anchor:middle;}#mermaid-svg-u8ODfTBA3IMBk28g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-u8ODfTBA3IMBk28g .rough-node .label,#mermaid-svg-u8ODfTBA3IMBk28g .node .label,#mermaid-svg-u8ODfTBA3IMBk28g .image-shape .label,#mermaid-svg-u8ODfTBA3IMBk28g .icon-shape .label{text-align:center;}#mermaid-svg-u8ODfTBA3IMBk28g .node.clickable{cursor:pointer;}#mermaid-svg-u8ODfTBA3IMBk28g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-u8ODfTBA3IMBk28g .arrowheadPath{fill:#333333;}#mermaid-svg-u8ODfTBA3IMBk28g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-u8ODfTBA3IMBk28g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-u8ODfTBA3IMBk28g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u8ODfTBA3IMBk28g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-u8ODfTBA3IMBk28g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u8ODfTBA3IMBk28g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-u8ODfTBA3IMBk28g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-u8ODfTBA3IMBk28g .cluster text{fill:#333;}#mermaid-svg-u8ODfTBA3IMBk28g .cluster span{color:#333;}#mermaid-svg-u8ODfTBA3IMBk28g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-u8ODfTBA3IMBk28g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-u8ODfTBA3IMBk28g rect.text{fill:none;stroke-width:0;}#mermaid-svg-u8ODfTBA3IMBk28g .icon-shape,#mermaid-svg-u8ODfTBA3IMBk28g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u8ODfTBA3IMBk28g .icon-shape p,#mermaid-svg-u8ODfTBA3IMBk28g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-u8ODfTBA3IMBk28g .icon-shape .label rect,#mermaid-svg-u8ODfTBA3IMBk28g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u8ODfTBA3IMBk28g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-u8ODfTBA3IMBk28g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-u8ODfTBA3IMBk28g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Method:loadUser
方法注解:@GET
请求模型:HTTP 方法与路径模板
参数注解:@Path
参数绑定器:id
类型元数据:Call
CallAdapter:选择返回包装
ServiceMethod:不可变执行计划
每次调用:绑定实参并创建 Request
这里谁创建:首次调用对应方法时,解析器创建请求模型和参数绑定器;谁持有:缓存中的 ServiceMethod;何时触发:缓存未命中时;结果交给谁:请求构造器将本次实参填入模板,交给 HTTP 调用工厂。
反射能看到什么,又看不到什么
Java 反射能从 Class<?> 和 Method 读取方法名、参数、注解、返回 Type 与泛型签名的一部分。它不能恢复编译时已经擦除的全部泛型信息,也不会凭空知道 Kotlin 业务语义、网络线程策略或混淆后的字符串约定。
例如 List<User> 可以以 ParameterizedType 形式描述元素类型,但 T、通配符、嵌套泛型、桥接方法和 Kotlin suspend 的 JVM 形态都让通用解析更复杂。Retrofit 会校验服务接口,解析 Type 时保留需要的结构;这是框架内部实现,不应把其私有工具类当作稳定 API。
反射本身不等于"慢"。一次冷启动路径上的深度扫描可能值得优化;同一 Method 解析一次后缓存,热点调用通常主要花在请求构建、序列化、调度和网络 I/O。先用 Trace、Benchmark 或真实启动数据定位,再决定是否迁移生成代码。
JDK 动态代理:接口调用进入 InvocationHandler
Proxy.newProxyInstance 只能为接口创建代理。代理对象接到接口方法后,将 Method 和实参数组交给 InvocationHandler.invoke。equals、hashCode、toString 等 Object 方法与默认接口方法还需要框架单独处理;抽象类、final 类和任意对象并不能用同一种 JDK 代理方式。
java
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface GET { String value(); }
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface Path { String value(); }
interface DemoApi {
@GET("users/{id}")
String user(@Path("id") long id);
}
final class DeclarativeApiHandler implements InvocationHandler {
private final Map<Method, String> templates = new HashMap<>();
@Override public Object invoke(Object proxy, Method method, Object[] args) {
if (method.getDeclaringClass() == Object.class) {
switch (method.getName()) {
case "equals":
return args != null && args.length == 1 && proxy == args[0];
case "hashCode":
return System.identityHashCode(proxy);
case "toString":
return "DemoApi proxy";
default:
throw new UnsupportedOperationException(method.toString());
}
}
String template = templates.computeIfAbsent(method, this::parseTemplate);
Annotation[][] annotations = method.getParameterAnnotations();
String path = template;
for (int index = 0; index < annotations.length; index++) {
Path pathAnnotation = findPath(annotations[index]);
if (pathAnnotation == null || args[index] == null) {
throw new IllegalArgumentException("missing @Path argument");
}
path = path.replace("{" + pathAnnotation.value() + "}", String.valueOf(args[index]));
}
return "GET https://api.example/" + path;
}
private String parseTemplate(Method method) {
GET get = method.getAnnotation(GET.class);
if (get == null) throw new IllegalArgumentException("missing @GET");
return get.value();
}
private Path findPath(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation instanceof Path) return (Path) annotation;
}
return null;
}
}
DemoApi api = (DemoApi) Proxy.newProxyInstance(
DemoApi.class.getClassLoader(),
new Class<?>[] { DemoApi.class },
new DeclarativeApiHandler()
);
String preview = api.user(42L);
简化说明: 这段代码只展示"方法注解 -> 缓存模板 -> 实参校验 -> 请求预览"。它故意省略网络、URL 编码、泛型签名边界、默认方法、线程切换、取消、重试、错误体、序列化、R8 keep 规则,以及安全的日志脱敏。它不是生产网络层。
Retrofit 源码锚点与职责
| 源码锚点或生成边界 | 回答的问题 | 协作关系 |
|---|---|---|
Retrofit.create(Class) |
谁把接口变成对象 | 校验服务接口后创建 JDK 代理 |
Proxy 与 InvocationHandler |
谁截获调用 | 取得 Method 与本次参数,分发到服务方法 |
Retrofit.loadServiceMethod(Method) |
何时解析、如何复用 | 缓存 ServiceMethod,避免重复反射解析 |
ServiceMethod.parseAnnotations、RequestFactory |
注解如何成为请求模型 | 读取 HTTP、路径、查询和 Body 规则,生成参数处理器 |
HttpServiceMethod、Converter.Factory、CallAdapter.Factory |
如何选转换与返回模型 | 选择响应转换器和 Call、RxJava、协程等适配器 |
| Room 注解处理器 / KSP 生成 DAO | 谁实现 @Dao 接口 |
编译期生成 DAO 实现,运行时主要调用生成代码 |
| Hilt / Dagger 生成组件与绑定 | 谁装配依赖图 | 编译期验证绑定并生成组件、工厂和注入入口 |
AIDL 生成的 Stub 与 Proxy |
谁跨进程传输 | Proxy 编组 Parcel,Stub.onTransact 解包并调用服务端 |
前五项是 Retrofit 2.x 的典型运行时链路。后面三项是 Android 工具链的不同策略:Room 与 Hilt 的具体类名和生成目录会随 KAPT/KSP、AGP 和版本而变化,应看生成源码或官方版本文档,而非依赖某一个内部类名。
运行时解析与编译期生成,边界在哪里
#mermaid-svg-YEx6OxQPtQWpkJLP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YEx6OxQPtQWpkJLP .error-icon{fill:#552222;}#mermaid-svg-YEx6OxQPtQWpkJLP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YEx6OxQPtQWpkJLP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YEx6OxQPtQWpkJLP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YEx6OxQPtQWpkJLP .marker.cross{stroke:#333333;}#mermaid-svg-YEx6OxQPtQWpkJLP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YEx6OxQPtQWpkJLP p{margin:0;}#mermaid-svg-YEx6OxQPtQWpkJLP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YEx6OxQPtQWpkJLP .cluster-label text{fill:#333;}#mermaid-svg-YEx6OxQPtQWpkJLP .cluster-label span{color:#333;}#mermaid-svg-YEx6OxQPtQWpkJLP .cluster-label span p{background-color:transparent;}#mermaid-svg-YEx6OxQPtQWpkJLP .label text,#mermaid-svg-YEx6OxQPtQWpkJLP span{fill:#333;color:#333;}#mermaid-svg-YEx6OxQPtQWpkJLP .node rect,#mermaid-svg-YEx6OxQPtQWpkJLP .node circle,#mermaid-svg-YEx6OxQPtQWpkJLP .node ellipse,#mermaid-svg-YEx6OxQPtQWpkJLP .node polygon,#mermaid-svg-YEx6OxQPtQWpkJLP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YEx6OxQPtQWpkJLP .rough-node .label text,#mermaid-svg-YEx6OxQPtQWpkJLP .node .label text,#mermaid-svg-YEx6OxQPtQWpkJLP .image-shape .label,#mermaid-svg-YEx6OxQPtQWpkJLP .icon-shape .label{text-anchor:middle;}#mermaid-svg-YEx6OxQPtQWpkJLP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YEx6OxQPtQWpkJLP .rough-node .label,#mermaid-svg-YEx6OxQPtQWpkJLP .node .label,#mermaid-svg-YEx6OxQPtQWpkJLP .image-shape .label,#mermaid-svg-YEx6OxQPtQWpkJLP .icon-shape .label{text-align:center;}#mermaid-svg-YEx6OxQPtQWpkJLP .node.clickable{cursor:pointer;}#mermaid-svg-YEx6OxQPtQWpkJLP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YEx6OxQPtQWpkJLP .arrowheadPath{fill:#333333;}#mermaid-svg-YEx6OxQPtQWpkJLP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YEx6OxQPtQWpkJLP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YEx6OxQPtQWpkJLP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YEx6OxQPtQWpkJLP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YEx6OxQPtQWpkJLP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YEx6OxQPtQWpkJLP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YEx6OxQPtQWpkJLP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YEx6OxQPtQWpkJLP .cluster text{fill:#333;}#mermaid-svg-YEx6OxQPtQWpkJLP .cluster span{color:#333;}#mermaid-svg-YEx6OxQPtQWpkJLP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YEx6OxQPtQWpkJLP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YEx6OxQPtQWpkJLP rect.text{fill:none;stroke-width:0;}#mermaid-svg-YEx6OxQPtQWpkJLP .icon-shape,#mermaid-svg-YEx6OxQPtQWpkJLP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YEx6OxQPtQWpkJLP .icon-shape p,#mermaid-svg-YEx6OxQPtQWpkJLP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YEx6OxQPtQWpkJLP .icon-shape .label rect,#mermaid-svg-YEx6OxQPtQWpkJLP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YEx6OxQPtQWpkJLP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YEx6OxQPtQWpkJLP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YEx6OxQPtQWpkJLP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接口与注解源码
运行时:反射读取 Method
运行时:动态代理分发
运行时:缓存解析计划
Retrofit:执行 HTTP 调用
编译期:KAPT 或 KSP
Room:生成 DAO 实现
Hilt:生成依赖装配
运行时:直接调用生成代码
AIDL:生成 Stub 与 Proxy
Binder:Parcel 与 transact
共同模式是"声明 -> 解释或生成 -> 执行",但时机不同。Retrofit 在运行时读取 Method;Room/Hilt 把大量校验提前到编译期,因此许多错误更早暴露、运行时反射更少;AIDL 虽然同样有 Proxy,但它的目标是跨进程 Binder 调用、事务码和 Parcel 编解码,不负责 HTTP URL、转换器或网络重试。AIDL 不是 Retrofit 风格的 HTTP 动态代理。
如何选择:收益、代价与 Android 映射
| 方案 | 主要收益 | 主要代价 | 适用提醒 |
|---|---|---|---|
| Retrofit 运行时反射与代理 | 接口声明简洁,集中校验,能缓存解析结果,扩展 Converter 和 CallAdapter | 错误可能在首次调用才出现,反射和泛型解析需谨慎 | 保持接口规则明确,预热不是默认必需 |
| Room 编译期生成 | DAO 实现可见,SQL/映射问题较早报错 | 构建链路更复杂,生成代码随版本变化 | 查生成源码理解行为,不把生成名写死 |
| Hilt 编译期生成 | 依赖关系集中验证,减少手写装配 | 注解处理配置和构建时间成本 | 区分 Dagger/Hilt 的生成实现与通用 DI 概念 |
| AIDL Binder Stub/Proxy | 明确的 IPC 契约与跨进程调用 | Parcel、线程、死亡通知和兼容性复杂 |
把它视为 IPC 边界,而不是 HTTP 客户端 |
声明式接口的价值是减少重复和统一验证,而不是隐藏所有复杂度。尤其在 Android 中,线程、生命周期、取消和错误展示仍属于调用层与架构层的职责。
R8、内存与错误时机
- 运行时读取的注解不能用
@Retention(SOURCE);它们在 APK 运行时不存在。 - 需要反射读取的接口、方法、注解或泛型签名可能需要 R8/ProGuard keep 规则。优先使用库官方 consumer rules,按实际反射入口收窄规则,并在 release 变体测试。
- 不要长生命周期缓存带
Activity/Context的对象,也不要让静态缓存无界保存Method或类加载器相关对象。按服务接口或框架实例有界缓存,并审查生命周期。 - 解析阶段失败通常比网络执行失败更早:注解冲突、路径占位符缺失、返回类型不支持,应给出方法名和可行动的错误信息。
- "反射自动慢"与"代码生成天然正确"都不成立。两者都要测量、测试,并考虑构建成本、可调试性和错误时机。
可动手改造的练习:不联网验证一个注解 API
- 复制上面的
GET、Path、DemoApi和DeclarativeApiHandler。 - 新增
@GET("teams/{teamId}/members/{memberId}") String member(...),为两个参数标上@Path。 - 调用代理并断言返回文本是
GET https://api.example/teams/7/members/9。 - 再传入
null或删掉一个@Path,断言抛出IllegalArgumentException;目标是验证声明和实参,不发任何网络请求。
延伸:把 templates 的值从 String 升级为不可变 RequestPlan,记录 HTTP 方法、路径占位符集合与参数索引;这就是理解 Retrofit ServiceMethod 缓存的一个小台阶。
决策清单
- 该元数据是否必须在运行时读取?是则用
RUNTIME,否则优先让编译期工具处理。 - 声明目标是接口吗?JDK 动态代理适合接口,不适合"每个类型都同样代理"。
- 是否把首次解析结果缓存为无状态、可复用的计划?
- 是否为 release 构建验证了注解、接口与泛型签名的 R8 keep 规则?
- 是否把网络线程、取消、重试和 UI 生命周期放在清晰的边界,而非藏进反射代码?
- 是否比较过 Room/Hilt 的生成代码方案与 Retrofit 的运行时扩展性,而不是混称为"注解自动实现"?
面试表达与下一站
面试表达: "Retrofit 的核心不是注解本身,而是用 JDK 动态代理截获接口调用,再把 Method 上的运行时注解、参数和返回 Type 解析为 ServiceMethod 并缓存;执行时只绑定实参、交给 OkHttp,再由 Converter 与 CallAdapter 回到业务。Room/Hilt 更偏编译期生成,AIDL 的 Stub/Proxy 则服务于 Binder IPC,三者共享声明驱动思想,但执行边界不同。"
下一步可以把这条链路接到并发与 Handler:网络回调在哪个线程抵达、如何切换到主线程、取消后为什么不能再更新 UI,以及消息队列如何保存这些边界,才是声明式 API 在 Android 页面中真正落地的后半程。