You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Retrofit遇No address associated with hostname错误的修复求助

解决"Unable to resolve host" DNS解析失效问题

问题背景

周期性收到用户反馈:设备网络正常,但应用无法获取数据,报错为Unable to resolve host "site.ru": No address associated with hostname。
通过开关移动网络可复现该问题:设备联网后应用无法访问目标地址,但同一设备的浏览器能正常打开该URL。重启应用无法解决,需等待5分钟或重启网络后再打开应用才可恢复。尝试重建NetworkService单例后,无需重启网络,但首次请求仍可能失败。

问题根源

这个问题的核心是OkHttpClient默认的DNS缓存策略导致的。当设备网络切换(比如从WiFi切到移动网络),旧的DNS解析结果已经失效,但OkHttpClient会默认缓存DNS记录5分钟;而浏览器会主动重新发起DNS解析,因此出现应用无法访问但浏览器正常的矛盾情况。

之前修改单例的方式仅重置了NetworkService实例,但Retrofit和OkHttpClient实例是初始化时就创建的,并未被重建,缓存的失效DNS记录依然存在,导致首次请求还是失败。

修复方案

核心修改思路

  1. 让OkHttpClient和Retrofit支持动态重建,而非初始化时固定实例
  2. 调整DNS缓存策略,缩短缓存时间或直接绕过默认缓存
  3. 监听网络变化,主动刷新网络客户端
  4. 增加DNS解析失败时的自动重试机制

修改后的完整代码

object NetworkService {
    const val BASE_URL_API = "https://site.ru/"
    private var mContext: Context? = null
    private var mRetrofit: Retrofit? = null
    private var mOkHttpClient: OkHttpClient? = null

    val gson = GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()

    fun getInstance(): NetworkService {
        return this
    }

    private fun getOkHTTPClient(): OkHttpClient {
        if (mOkHttpClient == null) {
            val client = OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                // 自定义DNS解析,绕过默认长时缓存
                .dns { hostname ->
                    InetAddress.getAllByName(hostname).toList()
                }
                // 缩短连接池空闲连接存活时间,减少旧连接复用
                .connectionPool(ConnectionPool(5, 1, TimeUnit.MINUTES))
                .addInterceptor(object : Interceptor {
                    override fun intercept(chain: Interceptor.Chain): Response {
                        val request = chain.request()
                        return try {
                            chain.proceed(request)
                        } catch (e: IOException) {
                            if (e.message?.contains("Unable to resolve host") == true) {
                                // DNS解析失败,重建客户端并重试请求
                                resetClient()
                                getOkHTTPClient().newCall(request).execute()
                            } else {
                                setError(e, request.url)
                                throw IOException("Network error", e)
                            }
                        }
                    }
                })
            mOkHttpClient = client.build()
        }
        return mOkHttpClient!!
    }

    private fun getRetrofit(): Retrofit {
        if (mRetrofit == null) {
            mRetrofit = Retrofit.Builder()
                .baseUrl(BASE_URL_API)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .callFactory(getOkHTTPClient())
                .build()
        }
        return mRetrofit!!
    }

    // 重置客户端实例,清除旧缓存
    fun resetClient() {
        mOkHttpClient = null
        mRetrofit = null
    }

    fun getJSONApi(context: Context): NetworkAPI {
        mContext = context
        return getRetrofit().create(NetworkAPI::class.java)
    }

    fun setError(t: IOException, url: HttpUrl) {
        mContext?.let { ctx ->
            val db = Room.databaseBuilder(
                ctx,
                AppDatabase::class.java, "database-file-name.db"
            ).build()

            val current = LocalDateTime.now()
            val logError = Log(0, "${current}${url}${t.message}", 0)

            GlobalScope.launch(Dispatchers.IO) {
                db.logDao().insert(logError)
            }
        }
    }

    // 注册网络变化监听,网络切换时主动重置客户端
    fun registerNetworkCallback(context: Context) {
        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val networkCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                super.onAvailable(network)
                resetClient()
            }

            override fun onLost(network: Network) {
                super.onLost(network)
                resetClient()
            }
        }
        connectivityManager.registerDefaultNetworkCallback(networkCallback)
    }
}

关键修改点说明

  • 动态实例创建:将mOkHttpClient和mRetrofit改为可空类型,通过懒加载方法创建,确保DNS失效时能重建全新实例。
  • 自定义DNS解析:直接调用系统DNS解析接口,绕过OkHttpClient的默认5分钟缓存,每次请求都获取最新的IP地址。
  • 缩短连接池存活时间:将空闲连接的存活时间从默认5分钟改为1分钟,减少旧连接的复用概率。
  • 自动重试机制:捕获DNS解析失败异常后,自动重置客户端并重试请求,无需用户手动操作。
  • 网络变化监听:注册网络回调,当网络切换时主动重置客户端,确保使用最新的网络配置。

使用建议

  1. 在应用启动时调用NetworkService.getInstance().registerNetworkCallback(applicationContext),注册网络状态监听。
  2. 保留错误日志逻辑,方便后续排查其他网络问题。
  3. 如果需要DNS缓存优化,可以自行实现短时间缓存(比如30秒),平衡性能与缓存有效性。

内容的提问来源于stack exchange,提问作者Evgeny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 06:44:59