Android Kotlin如何点击按钮动态切换Retrofit的BASE_URL?
运行时动态切换Retrofit BASE_URL的实现方案
核心问题根源
Hilt默认提供的Retrofit实例是单例,初始化时就绑定了固定BASE_URL,后续无法直接修改。要实现运行时切换,核心是让请求的BaseUrl可动态替换,同时调整Hilt的依赖提供逻辑适配这种动态性。
具体修改步骤
1. 实现动态BaseUrl拦截器
创建OkHttp拦截器,用于在请求发起时替换BaseUrl,并支持运行时更新:
class DynamicBaseUrlInterceptor(private var baseUrl: String) : Interceptor { @Synchronized fun updateBaseUrl(newUrl: String) { this.baseUrl = newUrl } override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() // 替换原始请求的BaseUrl,保留路径参数 val newRequest = originalRequest.newBuilder() .url(baseUrl + originalRequest.url.encodedPath) .build() return chain.proceed(newRequest) } }
2. 调整NetworkModule的依赖提供逻辑
修改Hilt模块,将固定BASE_URL改为从本地存储读取初始值,同时注入可动态更新的拦截器:
@Module @InstallIn(SingletonComponent::class) object NetworkModule { // 提供全局唯一的动态BaseUrl拦截器 @Provides @Singleton fun provideDynamicBaseUrlInterceptor(@ApplicationContext context: Context): DynamicBaseUrlInterceptor { // 从SharedPreferences读取初始BaseUrl(首次启动用默认值) val sp = context.getSharedPreferences("app_config", Context.MODE_PRIVATE) val initialUrl = sp.getString("base_url", "https://default-api.com/") ?: "https://default-api.com/" return DynamicBaseUrlInterceptor(initialUrl) } // 配置OkHttpClient,注入动态拦截器 @Provides @Singleton fun provideOkHttpClient(interceptor: DynamicBaseUrlInterceptor): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(interceptor) // 保留原有配置(日志、超时时间等) .build() } // 提供Retrofit实例,这里的baseUrl仅作为占位符(会被拦截器替换) @Provides @Singleton fun provideRetrofit(client: OkHttpClient): Retrofit { return Retrofit.Builder() .baseUrl("https://placeholder.com/") // 任意合法URL即可 .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() } // 提供ApiService,保持原有逻辑 @Provides @Singleton fun provideApiService(retrofit: Retrofit): ApiService { return retrofit.create(ApiService::class.java) } }
3. 封装BaseUrl管理类(可选,优化代码结构)
创建单例类统一管理BaseUrl的更新与持久化,避免直接操作拦截器:
class BaseUrlManager @Inject constructor( private val interceptor: DynamicBaseUrlInterceptor, @ApplicationContext private val context: Context ) { fun updateBaseUrl(newUrl: String) { // 持久化新URL,保证重启后生效 val sp = context.getSharedPreferences("app_config", Context.MODE_PRIVATE) sp.edit().putString("base_url", newUrl).apply() // 更新拦截器的BaseUrl interceptor.updateBaseUrl(newUrl) } fun getCurrentBaseUrl(): String { val sp = context.getSharedPreferences("app_config", Context.MODE_PRIVATE) return sp.getString("base_url", "https://default-api.com/") ?: "https://default-api.com/" } }
4. 在ViewModel中处理切换逻辑
通过Hilt注入BaseUrlManager,在ViewModel中封装切换方法:
@HiltViewModel class SettingsViewModel @Inject constructor( private val baseUrlManager: BaseUrlManager ) : ViewModel() { fun switchBaseUrl(newUrl: String) { // 简单校验URL格式(可选) if (newUrl.isNotEmpty() && newUrl.startsWith("http")) { baseUrlManager.updateBaseUrl(newUrl) // 可通过LiveData通知UI切换成功 } } }
5. 按钮点击触发切换
在Activity/Fragment中绑定ViewModel,响应按钮点击事件:
@AndroidEntryPoint class SettingsFragment : Fragment() { private val viewModel: SettingsViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) btn_switch_url.setOnClickListener { val newUrl = et_new_url.text.toString().trim() viewModel.switchBaseUrl(newUrl) } } }
关键注意事项
- 拦截器的
updateBaseUrl方法添加@Synchronized,避免多线程更新时的竞争问题 - Retrofit初始化的
baseUrl只需填任意合法URL,实际请求会被拦截器替换为动态设置的地址 - 如果ApiService中存在全路径请求,需在拦截器中增加判断逻辑,避免错误替换
内容的提问来源于stack exchange,提问作者Riddhish.Chaudhari
相关产品推荐
相关产品推荐

