Android随笔-Retrofit

一、定位

Retrofit 是一个类型安全的 HTTP 客户端,本质是对 OkHttp 的"声明式上层封装":你用接口 + 注解描述请求,它在运行时用动态代理生成接口实现,把方法调用翻译成 OkHttp 请求,再通过CallAdapter决定返回类型、Converter负责序列化/反序列化。

Retrofit = 动态代理(Proxy) + 注解解析(RequestFactory) + 调用适配(CallAdapter) + 数据转换(Converter) + 底层执行(OkHttp Call)

二、基本用法

// 1. 定义接口interfaceApiService{@GET("users/{id}")suspendfungetUser(@Path("id")id:Int):User@POST("users")suspendfuncreateUser(@Bodyuser:User):Response<User>}// 2. 构建 Retrofitvalretrofit=Retrofit.Builder().baseUrl("https://api.example.com/")// 必须以 / 结尾.client(okHttpClient)// 底层 OkHttp.addConverterFactory(GsonConverterFactory.create()).build()// 3. 创建代理实例并调用valapi=retrofit.create(ApiService::class.java)valuser=api.getUser(1)// 一行代码背后是下面整条链路

三、核心原理逐层拆解

3.1 动态代理:create() 发生了什么

// Retrofit#create() 源码核心(简化)public<T>Tcreate(finalClass<T>service){validateServiceInterface(service);// 校验必须是接口、不能继承其他接口return(T)Proxy.newProxyInstance(service.getClassLoader(),newClass<?>[]{service},newInvocationHandler(){@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args){if(method.getDeclaringClass()==Object.class){returnmethod.invoke(this,args);// toString/equals 等直接执行}// 核心:加载(或从缓存取)该方法的 ServiceMethod,执行returnloadServiceMethod(method).invoke(args);}});}

关键点:

  • 为什么用动态代理:接口没有实现类,JDK 动态代理在运行时生成实现,所有方法调用统一收编到 InvocationHandler.invoke()——这里就是把"方法调用"转成"HTTP 请求"的总入口。
  • ServiceMethod 缓存:loadServiceMethod() 内部是一个 ConcurrentHashMap<Method, ServiceMethod>。反射解析注解的性能开销只发生在每个方法第一次调用时,之后命中缓存。所以 Retrofit 的反射不是性能问题。
  • create() 本身很便宜,可以全局单例 Retrofit、按需 create 多个 Service。

3.2 注解解析:ServiceMethod 与 RequestFactory

每个接口方法最终被解析成一个 ServiceMethod,它封装了一次请求的全部信息:

ServiceMethod.parseAnnotations(retrofit, method) │ ├── RequestFactory.parseAnnotations() → 解析出"请求长什么样" │ ├── 方法注解:@GET/@POST/@HTTP → httpMethod + relativeUrl │ └── 参数注解:逐个解析成 ParameterHandler │ (@Path→替换URL占位符 / @Query→拼查询串 / @Body→RequestBody / @Header...) │ ├── createCallAdapter() → 决定"返回类型怎么适配" └── createResponseConverter() → 决定"响应体怎么反序列化"

RequestFactory.Builder 解析出的关键要素:HTTP 方法、相对路径、Headers、ParameterHandler[] 数组(每个参数对应一个处理器,负责把自己写进 RequestBuilder)。这一步的产物是一份与执行无关的请求模板,线程安全、可复用。

3.3 返回类型分发:HttpServiceMethod 的三态

HttpServiceMethod.parseAnnotations() 会根据方法签名走三条分支:

方法签名分支类行为
fun get(): Call<User>CallAdapted交给 CallAdapter 适配(默认返回 Call,或 RxJava 的 Observable 等)
suspend fun get(): Response<User>SuspendForResponse挂起执行,恢复时给完整 Response
suspend fun get(): UserSuspendForBody挂起执行,恢复时只给 body,非 2xx 抛 HttpException

判断 suspend 的方式:method.getParameterTypes() 最后一个参数是 Continuation 类型(Kotlin suspend 函数编译后会多一个续体参数)——这是 Retrofit 支持协程的入口识别点

3.4 底层执行:OkHttpCall

retrofit2.Call 的默认实现是OkHttpCall,它是 okhttp3.Call 的装饰器:

OkHttpCall.enqueue(callback) └── callFactory.newCall(requestFactory.create(args)) // 用模板 + 实参构建真实 OkHttp 请求 └── okhttp3.Call.enqueue(okhttp Callback) └── onResponse → parseResponse(rawResponse) ├── code 2xx → Response.success(converter.convert(body)) └── 非 2xx → 缓冲 errorBody → Response.error(...)

线程模型:

  • execute():同步,在调用线程执行,Android 主线程调用会崩(NetworkOnMainThreadException)
  • enqueue():异步,网络请求跑在 OkHttp Dispatcher 的线程池;回调默认经过 callbackExecutor 切回主线程——Android 上 Retrofit 通过 Platform 检测到 Android 环境,注入 MainThreadExecutor(内部 Handler.post)。所以 Retrofit 的 onResponse 默认在主线程,这就是为什么老代码可以直接在回调里更新 UI

3.5 suspend 支持的本质

suspend fun getUser(): User 最终走到 KotlinExtensions.await(),核心代码:

suspendfun<T>Call<T>.await():T{returnsuspendCancellableCoroutine{continuation->// 协程取消 → 取消 OkHttp 请求continuation.invokeOnCancellation{cancel()}enqueue(object:Callback<T>{overridefunonResponse(call:Call<T>,response:Response<T>){if(response.isSuccessful){continuation.resume(response.body()!!)// 成功:恢复协程}else{continuation.resumeWithException(HttpException(response))}}overridefunonFailure(call:Call<T>,t:Throwable){continuation.resumeWithException(t)// 失败:带异常恢复}})}}

一句话答案:Retrofit 对 suspend 的支持 = suspendCancellableCoroutine 把回调式 enqueue 桥接成挂起函数,请求仍在 OkHttp 的 IO 线程池执行,协程挂起不阻塞线程,取消协程会联动 call.cancel() 断掉 HTTP 连接。

3.6 两大扩展点(策略模式)

CallAdapter.Factory —— 决定方法返回类型:

工厂按添加顺序遍历,get(returnType, ...) 返回第一个非 null 的适配器 ├── DefaultCallAdapterFactory(内置兜底):支持 Call<T>,包一层 ExecutorCallbackCall 切主线程 ├── RxJava2CallAdapterFactory:Observable/Single/Completable └── 自定义:比如返回 LiveData<T>、Result<T>

Converter.Factory —— 负责数据转换,三个层级:

方法用途
responseBodyConverterResponseBody → Java/Kotlin 对象(Gson/Moshi/kotlinx.serialization)
requestBodyConverter对象 → RequestBody(@Body 参数序列化)
stringConverter对象 → String(@Path/@Query/@Header 参数转字符串,内置 EnumConverter 等)

同样是工厂按注册顺序遍历,先到先得——所以内置的 BuiltInConverters 在最后,你要自定义解析(比如加密响应)就把自己的 Factory 加在 Gson 前面。

四、注解速查表

分类注解说明
HTTP 方法@GET @POST @PUT @DELETE @PATCH @HEAD @OPTIONS括号内是相对路径
自定义方法@HTTP(method=“…”, path=“…”, hasBody=…)少见方法用
标记@FormUrlEncoded表单提交,配合 @Field/@FieldMap
标记@Multipart文件/多部分上传,配合 @Part/@PartMap
标记@Streaming大文件下载,不一次性读入内存,流式写盘
参数@Path(“id”)替换 URL 中{id}占位符
参数@Query(“page”) / @QueryMap拼接查询参数
参数@Url动态完整 URL,会覆盖 baseUrl 拼接
参数@Body对象序列化为请求体
参数@Header(“Authorization”) / @Headers动态/静态请求头
参数@Tag给请求打标,拦截器里request.tag()取出做差异化处理

baseUrl 拼接规则:baseUrl 必须以 / 结尾;接口路径以 / 开头表示域名根路径绝对定位,不以 / 开头则相对 baseUrl 拼接。

五、实战标准配置

valokHttpClient=OkHttpClient.Builder().connectTimeout(15,TimeUnit.SECONDS).readTimeout(15,TimeUnit.SECONDS)// 日志拦截器(release 包记得关掉或降级为 BASIC/NONE).addInterceptor(HttpLoggingInterceptor().apply{level=if(BuildConfig.DEBUG)BODYelseNONE})// Token 注入拦截器(应用拦截器,能看到最终请求).addInterceptor{chain->valrequest=chain.request().newBuilder().addHeader("Authorization","Bearer${TokenManager.get()}").build()chain.proceed(request)}// Token 过期自动刷新重试(Authenticator,只在 401 时触发).authenticator{route,response->valnewToken=runBlocking{TokenManager.refresh()}response.request.newBuilder().header("Authorization","Bearer$newToken").build()}.build()

统一错误封装(现代写法):

sealedinterfaceApiResult<outT>{dataclassSuccess<T>(valdata:T):ApiResult<T>dataclassError(valcode:Int,valmessage:String?):ApiResult<Nothing>dataclassException(valthrowable:Throwable):ApiResult<Nothing>}suspendfun<T>apiCall(block:suspend()->T):ApiResult<T>=try{ApiResult.Success(block())}catch(e:HttpException){// 非 2xxApiResult.Error(e.code(),e.message())}catch(e:IOException){// 网络异常ApiResult.Exception(e)}

六、工作流程

以这行代码为起点,拆解它背后发生的全部事情:

valuser=api.getUser(1)// suspend fun getUser(@Path("id") id: Int): User

阶段 0:初始化(App 启动时,只做一次)

Retrofit.Builder() .baseUrl(...) → 记录基础 URL(必须 / 结尾) .client(okHttpClient) → 记录 callFactory(真实执行者) .addConverterFactory(...) → 装入 Converter 工厂列表 .addCallAdapterFactory(...) → 装入 CallAdapter 工厂列表 .build() → 检测平台(Android → 注入 MainThreadExecutor) → 生成 Retrofit 实例(全局单例)

此阶段只存配置,不解析任何接口、不做任何网络操作

阶段 1:创建代理(retrofit.create())

retrofit.create(ApiService::class.java) │ ├─ 校验:必须是接口、不能继承其他接口、不能有类型参数 │ └─ Proxy.newProxyInstance(classLoader, [ApiService], invocationHandler) → 运行时动态生成 ApiService 的实现类(代理对象) → 该接口的所有方法调用,都会被收编到 InvocationHandler.invoke(proxy, method, args)

此阶段依然没有任何注解解析和网络操作,代理创建非常便宜

阶段 2:首次调用——方法解析(每个方法只做一次)

api.getUser(1) │ └─ InvocationHandler.invoke(method=getUser, args=[1]) │ ├─ method 属于 Object?(toString 等)→ 直接执行返回 │ └─ loadServiceMethod(method) │ ├─ 查缓存 ConcurrentHashMap<Method, ServiceMethod> │ ├─ 命中 → 直接返回(以后每次调用都走这里,零反射) │ └─ 未命中 → 解析(仅此一次)↓ │ └─ ServiceMethod.parseAnnotations(retrofit, method) │ ├─ ① RequestFactory.parseAnnotations() │ 解析方法注解:@GET → httpMethod="GET", relativeUrl="users/{id}" │ 解析参数注解:@Path("id") → ParameterHandler.Path │ 产物:与实参无关的"请求模板"(线程安全、可复用) │ ├─ ② 识别方法签名 → 确定执行分支 │ 最后一个参数是 Continuation?→ suspend 分支 │ (SuspendForResponse / SuspendForBody) │ 否则 → CallAdapted 分支 │ ├─ ③ createCallAdapter() │ 遍历 CallAdapter.Factory 列表,第一个匹配的胜出 │ └─ ④ createResponseConverter() 遍历 Converter.Factory 列表,找到 User 类型的反序列化器

反射开销全部集中在这里,且每个方法只发生一次——这是"Retrofit 用反射为什么不怕性能问题"的标准答案。

阶段 3:构建真实请求(每次调用都发生)

ServiceMethod.invoke(args=[1]) │ └─ RequestFactory.create(args) │ ├─ new RequestBuilder(以解析好的模板为底) ├─ 遍历 ParameterHandler[] 数组,把实参"写"进请求: │ @Path → "users/{id}" 中的 {id} 替换为 "1" → "users/1" │ @Query → 拼查询串 @Body → Converter 序列化为 RequestBody │ @Header→ 加请求头 ├─ baseUrl + relativeUrl 拼接出完整 URL └─ 生成 okhttp3.Request

阶段 4:交给 OkHttp 执行

OkHttpCall(装饰 okhttp3.Call) │ ├─ suspend/异步路线:call.enqueue(okhttp Callback) │ → OkHttp Dispatcher 线程池调度 │ → 拦截器链:应用拦截器 → RetryAndFollowUp → Bridge │ → Cache → Connect(连接池复用/TLS)→ CallServer │ → 真正发出 HTTP 请求,等待响应 │ └─ 同步路线:call.execute()(调用线程直接执行,主线程禁用)

Retrofit 自己到此为止——网络 IO 全是 OkHttp 的事。

阶段 5:响应处理与交付(分两条支线)

onResponse(rawResponse) │ └─ parseResponse(rawResponse) ├─ code 2xx → Response.success(converter.convert(body)) │ (Gson/Moshi:ResponseBody → User 对象) └─ 非 2xx → 缓冲 errorBody → Response.error(...)
支线 A:Call
ExecutorCallbackCall(装饰器) → callbackExecutor.execute { callback.onResponse(...) } → Handler.post 切回主线程 → 你在回调里直接更新 UI
支线 B:suspend 路线(现代主流)
suspendCancellableCoroutine ├─ 成功 → continuation.resume(user) 协程在调用处恢复,拿到 User ├─ 失败 → resumeWithException(...) 走 catch └─ 协程被取消 → call.cancel() 联动断开 HTTP 连接

七、 整体流程概览

你的代码 api.getUser(1) │ ▼ 动态代理 InvocationHandler.invoke ← create() 时生成 │ ▼ loadServiceMethod(ConcurrentHashMap 缓存) ← 首次反射解析注解,之后零反射 │ ▼ RequestFactory + 实参 → okhttp3.Request ← ParameterHandler 逐个写入 │ ▼ OkHttpCall → OkHttp 拦截器链 → 服务器 ← 真正的网络 IO(IO 线程池) │ ▼ parseResponse → Converter.convert ← JSON → 对象 │ ▼ 交付:Call 回调切主线程 / suspend 恢复协程 ← CallAdapter 决定的形态

Retrofit 用动态代理把接口方法调用收编到 invoke();首次调用时反射解析注解生成 ServiceMethod 并缓存,其中 RequestFactory 负责拼请求、CallAdapter 决定返回类型、Converter 负责数据转换;之后每次调用用模板+实参构建 OkHttp Request,交给 OkHttp 执行;响应回来后 Converter 反序列化,回调路线经 Executor 切回主线程,suspend 路线通过 suspendCancellableCoroutine 恢复协程。Retrofit 全程不做网络 IO,它是 OkHttp 的声明式封装层。

八、设计模式总结

模式体现
动态代理create()生成接口实现,统一收编方法调用
建造者模式Retrofit.BuilderRequest.Builder(链式配置复杂对象)
适配器模式CallAdapter(把 OkHttp Call 适配成 Call/RxJava/suspend 各种返回类型)
策略模式Converter(序列化策略可插拔:Gson/Moshi/Protobuf)
工厂模式CallAdapter.Factory、Converter.Factory(按类型遍历匹配)
装饰器模式OkHttpCall 装饰 okhttp3.Call;ExecutorCallbackCall 装饰回调切线程
外观模式Retrofit 本身是 OkHttp 复杂能力的简化门面

九、常见问题

  1. Retrofit 的原理?
    —— 用第一节那个公式回答,然后逐层展开动态代理 → 注解解析 → CallAdapter/Converter → OkHttp 执行。
  2. 反射性能差,Retrofit 为什么敢用?
    —— 注解解析只在方法首次调用时发生,ServiceMethod 存 ConcurrentHashMap 缓存;代理创建本身只生成字节码,热点在 OkHttp。
  3. suspend 函数是怎么支持的?
    —— 编译后方法多一个 Continuation 参数被识别 → SuspendForBody/SuspendForResponse 分支 → suspendCancellableCoroutine 桥接 enqueue 回调,取消联动 call.cancel()。
  4. 回调在哪个线程?
    —— enqueue 回调经 callbackExecutor 切主线程(Android 平台注入MainThreadExecutor);suspend 版本由协程调度器决定,恢复在调用方上下文。
  5. CallAdapter 和 Converter 的区别?
    —— CallAdapter 管"返回类型"(Call/Observable/suspend body),Converter 管"数据怎么转"(JSON↔对象)。匹配都是工厂顺序遍历、先到先得。
  6. 如何上传文件 / 下载大文件?
    —— @Multipart + @Part(MultipartBody.Part);@Streaming + ResponseBody.byteStream() 分块写盘(注意别开 Gson converter 转它)。
  7. 如何做 Token 过期自动刷新?
    —— OkHttp Authenticator(401 触发,同步刷新并重放请求),与应用拦截器的 Token 注入配合。
  8. Retrofit 与 OkHttp 分工?
    —— Retrofit 管"接口抽象、注解解析、类型适配、数据转换";OkHttp 管"连接池、拦截器链、缓存、HTTP/2、实际收发"。Retrofit 自己不做任何网络 IO。