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记录依然存在,导致首次请求还是失败。
修复方案
核心修改思路
- 让OkHttpClient和Retrofit支持动态重建,而非初始化时固定实例
- 调整DNS缓存策略,缩短缓存时间或直接绕过默认缓存
- 监听网络变化,主动刷新网络客户端
- 增加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解析失败异常后,自动重置客户端并重试请求,无需用户手动操作。
- 网络变化监听:注册网络回调,当网络切换时主动重置客户端,确保使用最新的网络配置。
使用建议
- 在应用启动时调用
NetworkService.getInstance().registerNetworkCallback(applicationContext),注册网络状态监听。 - 保留错误日志逻辑,方便后续排查其他网络问题。
- 如果需要DNS缓存优化,可以自行实现短时间缓存(比如30秒),平衡性能与缓存有效性。
内容的提问来源于stack exchange,提问作者Evgeny
相关产品推荐
相关产品推荐

