日常项目中遇到除本公司接口还需要对接其他的三方业务的接口,这就要求网络请求框架需要支持不同BaseUrl域名的动态配置,否则就需要再单独写一套特殊的网络请求.
1.问题
多域名请求网络数据(域名/返回数据结构不可控)
解决方案梳理:
- 特殊请求单独请求
- 项目框架支持多域名配置(
采取这个方案)
2.项目框架支持多域名配置实现
思路:
配置多域名-->Api配置Header --->拦截器动态判断
开发环境:
- Kotlin
- Retrofit
- Okhttp
2.1 配置动态域名
kotlin
fun initFramework() {
val config = NetConfig.Builder()
.setBaseUrl("https://api.example.com/") // 主的
.putBaseUrl("google", "https://google.com/") // 演示动态域名
.putBaseUrl("github", "https://api.github.com/") // 演示动态域名
.setConnectTimeout(15L)
.setReadTimeout(20L)
.setWriteTimeout(20L)
.setDebugLogsEnabled(true)
.addDefaultHeader("Global-Version", "1.0.0")
.build()
NetManager.init(config)
}
kotlin
package com.wkq.net.core
import com.wkq.net.config.NetConfig
import com.wkq.net.interceptor.HeaderInterceptor
/**
* 高级网络框架的入口点和配置持有者。
* 应用必须在初始化时调用 NetManager.init() 以正确配置框架。
*/
object NetManager {
// 网络配置对象
private var config: NetConfig? = null
// 全局请求头拦截器
lateinit var headerInterceptor: HeaderInterceptor
private set
/**
* 使用自定义 NetConfig 配置初始化网络框架。
* 如果多次调用,则记录日志并返回(防止重复初始化)。
*/
fun init(netConfig: NetConfig) {
if (config != null) {
// 已初始化。跳过或抛出错误。
return
}
this.config = netConfig
// 基于默认配置初始化全局 HeaderInterceptor
headerInterceptor = HeaderInterceptor(netConfig.defaultHeaders).apply {
// 如果需要,可在此处添加进一步逻辑
}
}
/**
* 获取当前活动的配置。
* 如果在 init() 之前使用,将抛出错误。
*/
fun getConfig(): NetConfig {
return config ?: throw IllegalStateException("在 Application 中使用网络框架前,必须先调用 NetManager.init() 初始化配置。")
}
}
2.2 配置Header
less
interface ThirdPartyService {
@Headers("${HeaderInterceptor.HEADER_BASE_URL_KEY}:google")
@GET("search")
fun searchGoogle(@Query("q") query: String): Call<ResponseBody>
@Headers("${HeaderInterceptor.HEADER_BASE_URL_KEY}:github")
@GET("users/{user}/repos")
fun getGithubRepos(@Path("user") user: String): Call<ResponseBody>
// 5. 演示返回非 BaseResponse 格式 (例如直接返回 Any 或自定义 Bean)
@Headers("${HeaderInterceptor.HEADER_BASE_URL_KEY}:google")
@GET("search")
fun searchGoogleAny(@Query("q") query: String): Call<Any>
}
2.3 拦截器动态处理Header
kotlin
package com.wkq.net.interceptor
import com.wkq.net.core.NetManager
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.Response
import java.util.concurrent.ConcurrentHashMap
/**
* 负责全局将请求头注入到每个 OkHttp 请求中的拦截器。
* 支持在运行时动态添加或移除请求头。
* 同时支持通过 "BaseUrl-Key" 请求头动态切换 BaseUrl。
*/
class HeaderInterceptor(defaultHeaders: Map<String, String>) : Interceptor {
companion object {
const val HEADER_BASE_URL_KEY = "BaseUrl-Key"
}
private val dynamicHeaders = ConcurrentHashMap<String, String>()
init {
// 使用 NetConfig 提供的默认请求头初始化
dynamicHeaders.putAll(defaultHeaders)
}
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
// 应用所有动态请求头
dynamicHeaders.forEach { (key, value) ->
requestBuilder.header(key, value)
}
// --- 动态切换 BaseUrl 逻辑 ---
val domainKey = originalRequest.header(HEADER_BASE_URL_KEY)
if (!domainKey.isNullOrEmpty()) {
val baseUrls = NetManager.getConfig().baseUrls
val newBaseUrl = baseUrls[domainKey]
if (!newBaseUrl.isNullOrEmpty()) {
val newHttpUrl = newBaseUrl.toHttpUrlOrNull()
if (newHttpUrl != null) {
val oldHttpUrl = originalRequest.url
val finalUrl = oldHttpUrl.newBuilder()
.scheme(newHttpUrl.scheme)
.host(newHttpUrl.host)
.port(newHttpUrl.port)
.build()
requestBuilder.url(finalUrl)
}
}
// 移除用于切换的辅助 Header,避免发送到服务端
requestBuilder.removeHeader(HEADER_BASE_URL_KEY)
}
return chain.proceed(requestBuilder.build())
}
/**
* 动态添加请求头
*/
fun addHeader(key: String, value: String) {
dynamicHeaders[key] = value
}
/**
* 动态移除请求头
*/
fun removeHeader(key: String) {
dynamicHeaders.remove(key)
}
/**
* 清空所有请求头
*/
fun clearHeaders() {
dynamicHeaders.clear()
}
}
3:测试网络请求
ini
private fun performRawRequest() {
binding.tvResult.text = "正在请求 GitHub (Any) ..."
// 使用协程演示 awaitRawResult()
lifecycleScope.launch {
demoService.getGithubUserAny().awaitRawResult()
.onSuccess { data ->
binding.tvResult.text = """
[请求成功]
模式: Raw API (awaitRawResult)
返回类型: ${data?.javaClass?.simpleName}
内容: $data
""".trimIndent()
}
.onError { code, message ->
binding.tvResult.text = "[请求失败]\n错误码: $code\n消息: $message"
}
}
}
总结
配置动态域名 --> 请求配置Header --> 拦截器动态替换域名
注意:
- 不同域名的数据结构不同
- 不同域名返回Code需要特殊处理
通过这种方式 就能动态的支持Retrofit的域名的动态配置,方便日常开发过程中网络请求一套代码兼容多个域名的网络请求.