Android随笔-Retrofit

📅 2026/7/21 17:59:38
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):UserPOST(users)suspendfuncreateUser(Bodyuser:User):ResponseUser}// 2. 构建 RetrofitvalretrofitRetrofit.Builder().baseUrl(https://api.example.com/)// 必须以 / 结尾.client(okHttpClient)// 底层 OkHttp.addConverterFactory(GsonConverterFactory.create()).build()// 3. 创建代理实例并调用valapiretrofit.create(ApiService::class.java)valuserapi.getUser(1)// 一行代码背后是下面整条链路三、核心原理逐层拆解3.1 动态代理create() 发生了什么// Retrofit#create() 源码核心简化publicTTcreate(finalClassTservice){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() 内部是一个 ConcurrentHashMapMethod, 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(): CallUserCallAdapted交给 CallAdapter 适配默认返回 Call或 RxJava 的 Observable 等suspend fun get(): ResponseUserSuspendForResponse挂起执行恢复时给完整 Responsesuspend fun get(): UserSuspendForBody挂起执行恢复时只给 body非 2xx 抛 HttpException判断 suspend 的方式method.getParameterTypes() 最后一个参数是 Continuation 类型Kotlin suspend 函数编译后会多一个续体参数——这是 Retrofit 支持协程的入口识别点。3.4 底层执行OkHttpCallretrofit2.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 主线程调用会崩NetworkOnMainThreadExceptionenqueue()异步网络请求跑在 OkHttp Dispatcher 的线程池回调默认经过 callbackExecutor 切回主线程——Android 上 Retrofit 通过 Platform 检测到 Android 环境注入 MainThreadExecutor内部 Handler.post。所以 Retrofit 的 onResponse 默认在主线程这就是为什么老代码可以直接在回调里更新 UI3.5 suspend 支持的本质suspend fun getUser(): User 最终走到 KotlinExtensions.await()核心代码suspendfunTCallT.await():T{returnsuspendCancellableCoroutine{continuation-// 协程取消 → 取消 OkHttp 请求continuation.invokeOnCancellation{cancel()}enqueue(object:CallbackT{overridefunonResponse(call:CallT,response:ResponseT){if(response.isSuccessful){continuation.resume(response.body()!!)// 成功恢复协程}else{continuation.resumeWithException(HttpException(response))}}overridefunonFailure(call:CallT,t:Throwable){continuation.resumeWithException(t)// 失败带异常恢复}})}}一句话答案Retrofit 对 suspend 的支持 suspendCancellableCoroutine 把回调式 enqueue 桥接成挂起函数请求仍在 OkHttp 的 IO 线程池执行协程挂起不阻塞线程取消协程会联动 call.cancel() 断掉 HTTP 连接。3.6 两大扩展点策略模式CallAdapter.Factory —— 决定方法返回类型工厂按添加顺序遍历get(returnType, ...) 返回第一个非 null 的适配器 ├── DefaultCallAdapterFactory内置兜底支持 CallT包一层 ExecutorCallbackCall 切主线程 ├── RxJava2CallAdapterFactoryObservable/Single/Completable └── 自定义比如返回 LiveDataT、ResultTConverter.Factory —— 负责数据转换三个层级方法用途responseBodyConverterResponseBody → Java/Kotlin 对象Gson/Moshi/kotlinx.serializationrequestBodyConverter对象 → RequestBodyBody 参数序列化stringConverter对象 → StringPath/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 拼接。五、实战标准配置valokHttpClientOkHttpClient.Builder().connectTimeout(15,TimeUnit.SECONDS).readTimeout(15,TimeUnit.SECONDS)// 日志拦截器release 包记得关掉或降级为 BASIC/NONE.addInterceptor(HttpLoggingInterceptor().apply{levelif(BuildConfig.DEBUG)BODYelseNONE})// Token 注入拦截器应用拦截器能看到最终请求.addInterceptor{chain-valrequestchain.request().newBuilder().addHeader(Authorization,Bearer${TokenManager.get()}).build()chain.proceed(request)}// Token 过期自动刷新重试Authenticator只在 401 时触发.authenticator{route,response-valnewTokenrunBlocking{TokenManager.refresh()}response.request.newBuilder().header(Authorization,Bearer$newToken).build()}.build()统一错误封装现代写法sealedinterfaceApiResultoutT{dataclassSuccessT(valdata:T):ApiResultTdataclassError(valcode:Int,valmessage:String?):ApiResultNothingdataclassException(valthrowable:Throwable):ApiResultNothing}suspendfunTapiCall(block:suspend()-T):ApiResultTtry{ApiResult.Success(block())}catch(e:HttpException){// 非 2xxApiResult.Error(e.code(),e.message())}catch(e:IOException){// 网络异常ApiResult.Exception(e)}六、工作流程以这行代码为起点拆解它背后发生的全部事情valuserapi.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(methodgetUser, args[1]) │ ├─ method 属于 ObjecttoString 等→ 直接执行返回 │ └─ loadServiceMethod(method) │ ├─ 查缓存 ConcurrentHashMapMethod, ServiceMethod │ ├─ 命中 → 直接返回以后每次调用都走这里零反射 │ └─ 未命中 → 解析仅此一次↓ │ └─ ServiceMethod.parseAnnotations(retrofit, method) │ ├─ ① RequestFactory.parseAnnotations() │ 解析方法注解GET → httpMethodGET, relativeUrlusers/{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/MoshiResponseBody → User 对象 └─ 非 2xx → 缓冲 errorBody → Response.error(...)支线 ACallExecutorCallbackCall装饰器 → callbackExecutor.execute { callback.onResponse(...) } → Handler.post 切回主线程 → 你在回调里直接更新 UI支线 Bsuspend 路线现代主流suspendCancellableCoroutine ├─ 成功 → continuation.resume(user) 协程在调用处恢复拿到 User ├─ 失败 → resumeWithException(...) 走 catch └─ 协程被取消 → call.cancel() 联动断开 HTTP 连接七、 整体流程概览你的代码 api.getUser(1) │ ▼ 动态代理 InvocationHandler.invoke ← create() 时生成 │ ▼ loadServiceMethodConcurrentHashMap 缓存 ← 首次反射解析注解之后零反射 │ ▼ RequestFactory 实参 → okhttp3.Request ← ParameterHandler 逐个写入 │ ▼ OkHttpCall → OkHttp 拦截器链 → 服务器 ← 真正的网络 IOIO 线程池 │ ▼ 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.Builder、Request.Builder链式配置复杂对象适配器模式CallAdapter把 OkHttp Call 适配成 Call/RxJava/suspend 各种返回类型策略模式Converter序列化策略可插拔Gson/Moshi/Protobuf工厂模式CallAdapter.Factory、Converter.Factory按类型遍历匹配装饰器模式OkHttpCall 装饰 okhttp3.CallExecutorCallbackCall 装饰回调切线程外观模式Retrofit 本身是 OkHttp 复杂能力的简化门面九、常见问题Retrofit 的原理—— 用第一节那个公式回答然后逐层展开动态代理 → 注解解析 → CallAdapter/Converter → OkHttp 执行。反射性能差Retrofit 为什么敢用—— 注解解析只在方法首次调用时发生ServiceMethod 存 ConcurrentHashMap 缓存代理创建本身只生成字节码热点在 OkHttp。suspend 函数是怎么支持的—— 编译后方法多一个 Continuation 参数被识别 → SuspendForBody/SuspendForResponse 分支 → suspendCancellableCoroutine 桥接 enqueue 回调取消联动 call.cancel()。回调在哪个线程—— enqueue 回调经 callbackExecutor 切主线程Android 平台注入MainThreadExecutorsuspend 版本由协程调度器决定恢复在调用方上下文。CallAdapter 和 Converter 的区别—— CallAdapter 管返回类型Call/Observable/suspend bodyConverter 管数据怎么转JSON↔对象。匹配都是工厂顺序遍历、先到先得。如何上传文件 / 下载大文件—— Multipart Part(MultipartBody.Part)Streaming ResponseBody.byteStream() 分块写盘注意别开 Gson converter 转它。如何做 Token 过期自动刷新—— OkHttp Authenticator401 触发同步刷新并重放请求与应用拦截器的 Token 注入配合。Retrofit 与 OkHttp 分工—— Retrofit 管接口抽象、注解解析、类型适配、数据转换OkHttp 管连接池、拦截器链、缓存、HTTP/2、实际收发。Retrofit 自己不做任何网络 IO。