Hilt如何重新初始化Application作用域Retrofit实现BaseUrl切换
Hilt 场景下动态切换 Retrofit BaseUrl 实现方案
两种方案均能满足需求,可根据业务场景选择:
方案一:运行时动态替换URL(无需重建实例,优先推荐)
该方案不需要调整现有Retrofit的Application作用域配置,改造成本最低,性能最好。Retrofit本身没有开放修改baseUrl的公开API,但可以通过OkHttp拦截器在请求发起前动态替换目标地址,完全不受初始化时传入的baseUrl限制。
实现步骤:
- 现有Retrofit初始化逻辑不需要大改,
baseUrl()方法随便传入一个合法的默认地址即可,这个值后续会被拦截器覆盖,不会影响实际请求。 - 自定义OkHttp拦截器,每次请求前从SharedPreferences读取用户当前选中的BaseUrl,替换请求的地址信息:
class DynamicBaseUrlInterceptor @Inject constructor( private val sp: SharedPreferences // 直接通过Hilt注入你项目中已封装好的SP实例 ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { // 读取SP中存储的用户选中地址,没有存储值时返回默认环境地址 val currentBase = sp.getString("KEY_SELECTED_BASE_URL", "https://api.default.com/")!! val originalRequest = chain.request() val parsedBase = Uri.parse(currentBase) // 替换原请求的协议、域名、端口,保留原请求的path、query、header等信息 val newUrl = originalRequest.url.newBuilder() .scheme(parsedBase.scheme) .host(parsedBase.host) .port(parsedBase.port) .build() val newRequest = originalRequest.newBuilder().url(newUrl).build() return chain.proceed(newRequest) } }
- 将该拦截器添加到OkHttpClient的拦截器列表中,尽量放在靠前位置,避免其他拦截器拿到旧地址做逻辑处理。
- 注意事项:所有接口方法的注解中必须使用相对路径(比如
@GET("user/info")),不要写全路径地址,否则拦截器不会覆盖对应请求的url。用户切换地址后不需要做其他操作,下一次发起请求时会自动读取最新的SP配置。
方案二:动态重建Retrofit实例(适合多环境配置完全隔离的场景)
如果不同BaseUrl对应完全不同的网络配置(比如不同的超时规则、证书校验逻辑、专属拦截器),可以通过持有可替换的Retrofit实例实现切换,同时支持SP持久化:
实现步骤:
- 不要直接在Hilt中提供Application作用域的Retrofit实例,改为提供单例的Retrofit管理类,统一负责实例的创建和更新:
@Singleton class RetrofitManager @Inject constructor( private val client: OkHttpClient, private val sp: SharedPreferences, private val gson: Gson ) { @Volatile private var currentRetrofit: Retrofit? = null // 全局获取Retrofit实例统一走这个方法 fun get(): Retrofit { return currentRetrofit ?: synchronized(this) { // 初始化时优先读取SP中存储的用户选中地址 val savedBase = sp.getString("KEY_SELECTED_BASE_URL", "https://api.default.com/")!! buildInstance(savedBase).also { currentRetrofit = it } } } // 用户切换地址时调用这个方法触发实例重建 fun switchBaseUrl(newBaseUrl: String) { // 先持久化到SP,保证冷启动时能读取到最新配置 sp.edit().putString("KEY_SELECTED_BASE_URL", newBaseUrl).apply() synchronized(this) { currentRetrofit = buildInstance(newBaseUrl) } } private fun buildInstance(baseUrl: String): Retrofit { return Retrofit.Builder() .baseUrl(baseUrl) .client(client) .addConverterFactory(GsonConverterFactory.create(gson)) // 此处照抄原有Retrofit初始化的其他配置,比如CallAdapter、自定义转换器等 .build() } }
- 项目中所有需要创建Api Service的地方,都通过
retrofitManager.get().create(xxxApi::class.java)获取实例,不要直接注入Retrofit。 - 用户在设置页切换BaseUrl选项时,直接调用
retrofitManager.switchBaseUrl(选中的目标地址)即可,切换后新发起的请求都会走新创建的Retrofit实例,切换前已经在执行的请求会继续使用旧实例跑完,不会出现请求中断的问题,旧实例无引用后会被GC自动回收,不需要手动处理内存释放。
选型建议:如果只是单纯切换接口域名,没有额外的网络配置差异,直接选方案一即可,改造成本极低,没有任何兼容问题;只有当不同域名对应的网络栈逻辑完全独立时,再考虑方案二。
内容的提问来源于stack exchange,提问作者Dipesh Malkar
相关产品推荐
相关产品推荐

