第3章 BaseUrl与动态端点:静态BaseUrl设置、@Url动态地址、多环境切换策略
在 Retrofit 中,BaseUrl 是网络请求的根地址,而 动态端点(@Url) 允许在运行时完全替换请求路径。本章将从原理到实践,讲解静态 BaseUrl 的设置方式、@Url 注解的工作机制,以及多环境切换的成熟策略。
3.1 静态 BaseUrl 设置
在创建 Retrofit 实例时,通过 baseUrl() 方法指定一个固定根地址。之后接口方法中所有使用相对路径的注解(如 @GET("user/list"))都会拼接在该 BaseUrl 之后。
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/") // 注意末尾必须带 "/"
.addConverterFactory(GsonConverterFactory.create())
.build()
拼接规则:OkHttp 的 HttpUrl 会解析 BaseUrl 的 scheme、host、port 和 path 前缀,接口中相对路径会附加在 BaseUrl 的 path 末尾。例如:baseUrl="https://api.example.com/v2/" + @GET("users") → 完整请求 https://api.example.com/v2/users。
3.2 @Url 动态地址
当接口的请求地址在运行时才能确定时,可在方法参数前使用 @Url 注解,传入完整的 URL 字符串或 HttpUrl 对象。此时 Retrofit 会完全忽略 BaseUrl,直接使用传入的地址。
interface ApiService {
@GET
suspend fun fetchByUrl(@Url url: String): Response<UserResponse>
@POST
suspend fun postToUrl(@Url url: HttpUrl, @Body body: RequestBody): Response<Any>
}
注意事项:
- 使用
@Url的方法仍然需要 HTTP 方法注解(如@GET、@POST),但其路径部分被忽略。 - 传入的 URL 必须包含 scheme(如
https://),否则会抛出IllegalArgumentException。 - 可在
@Url后继续使用@Query、@Header等注解,查询参数和请求头会追加到 URL 上。
3.3 多环境切换策略
在实际项目中,通常需要切换开发(dev)、测试(staging)、生产(prod)等环境。以下是三种主流策略:
| 策略 | 原理 | 适用场景 | 优缺点 |
|---|---|---|---|
| Retrofit 实例切换 | 每个环境创建独立的 Retrofit 对象 | 小型项目、环境少 | 简单直接;但实例多时浪费资源 |
| OkHttp 拦截器修改 | 在拦截器中动态替换请求的 Host/URL | 大型项目、需要运行时切换 | 灵活,可配合 BuildConfig 或配置中心;需注意拦截器顺序 |
| @Url 参数传递 | 将 BaseUrl 作为方法参数传入 | 请求地址完全由调用方决定 | 最灵活;但每个调用都需要传递 URL,增加耦合 |
3.3.1 拦截器动态切换实现
创建一个 Interceptor,根据当前环境标识修改请求的 URL。以下示例将 BaseUrl 的 host 部分替换为配置值:
class BaseUrlInterceptor(private val baseUrl: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newUrl = originalRequest.url.newBuilder()
.scheme(Uri.parse(baseUrl).scheme ?: "https")
.host(Uri.parse(baseUrl).host ?: originalRequest.url.host)
.port(Uri.parse(baseUrl).port)
.build()
val newRequest = originalRequest.newBuilder()
.url(newUrl)
.build()
return chain.proceed(newRequest)
}
}
// 使用时
val interceptor = BaseUrlInterceptor("https://staging.api.example.com")
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://placeholder.example.com/") // 占位 URL,会被拦截器替换
.client(okHttpClient)
.build()
⚠️ 注意:使用拦截器替换 BaseUrl 时,务必确保
baseUrl() 中填写一个合法的占位地址(否则 Retrofit 初始化会报错),且拦截器要放在 应用拦截器 中(addInterceptor 而非 addNetworkInterceptor),以保证在重试和重定向时仍然生效。
3.4 调试技巧
- 日志拦截器:添加
HttpLoggingInterceptor并设置为BODY级别,观察实际请求的 URL。 - 断点调试:在拦截器的
intercept方法中打印request.url确认拼接正确。 - 单元测试:使用 MockWebServer 模拟不同 BaseUrl,验证 @Url 和静态路径的解析。
3.5 常见错误与处理
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
| 请求 URL 缺少 scheme 或 host | 传入 @Url 的字符串不完整 | 确保传入形如 https://host/path 的完整 URL |
| 路径拼接多余双斜杠(//) | BaseUrl 末尾无 "/" 或接口路径前多 "/" | BaseUrl 末尾统一加 "/",路径前不加 "/" |
| 拦截器替换后请求仍使用占位 BaseUrl | 拦截器未正确修改 URL 或顺序错误 | 检查拦截器添加顺序,确认 chain.proceed 前已修改 |
| 运行时环境切换无效 | Retrofit 实例未重建 | 切换环境后重新创建 Retrofit,或使用拦截器运行时读取配置 |
3.6 性能与稳定性优化
- 避免频繁创建 Retrofit 实例:使用单例或依赖注入(Dagger / Hilt)管理,每个环境只创建一个实例。
- 拦截器轻量化:不在拦截器中执行耗时操作(如文件 I/O、网络请求),否则阻塞 OkHttp 线程池。
- HttpUrl 复用:如果使用
@Url且 URL 频繁相同,可预先解析为HttpUrl对象缓存,避免重复字符串解析。 - 配置中心:将环境 BaseUrl 存储在
BuildConfig或远程配置中,通过开关或 App 版本控制切换,避免硬编码。
📌 本章小结:静态 BaseUrl 适合固定环境,@Url 提供最大灵活性,而多环境切换应优先考虑拦截器方案。理解 OkHttp 的 URL 拼接规则和拦截器链是避免坑点的关键。