使用OkHttp下载文件频繁触发SocketTimeoutException问题排查
首先,我完全懂你此刻的困惑——文件明明已经完整下载到SD卡了,可writeResponseBodyToDisk方法却因为java.net.SocketTimeoutException返回false,这个问题确实和服务器未返回contentLength(值为-1)以及你的OkHttp超时配置直接相关,咱们一步步拆解解决:
核心原因
1. 缺失readTimeout配置
你当前只给OkHttpClient设置了connectTimeout和writeTimeout(均为60秒),但完全没配置readTimeout!OkHttp默认的readTimeout是10秒,当服务器不返回contentLength时,OkHttp没法预知要读取多少数据,会一直从流里读直到结束。如果下载过程中(尤其是大文件或网络波动时),两次数据传输的间隔超过10秒,就会触发超时异常——哪怕文件最后已经完整写入磁盘,这个异常还是会在读取阶段抛出,导致方法返回false。
2. 进度计算的潜在bug
你用percent = (int) (100.0f * (float) downloadSize / totalSize)计算下载进度,但totalSize因为服务器没返回contentLength而无效(大概率是0),这会导致进度计算错误(比如触发除以0的情况),虽然这不是超时的直接原因,但会影响下载状态的正常反馈。
具体解决方案
1. 补充readTimeout配置
在OkHttpClient.Builder里加上readTimeout,建议设置和connect、write相同的时长(或更长,根据你的文件大小和网络环境调整):
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS); // 新增这一行
这会让OkHttp在等待数据时拥有足够的超时窗口,避免因为服务器未返回contentLength而提前触发超时异常。
2. 修复进度计算逻辑
因为服务器没返回文件大小,totalSize无效,所以要调整进度计算逻辑,避免无效运算:
// 替换原来的percent计算代码 if (totalSize > 0) { percent = (int) (100.0f * (float) downloadSize / totalSize); } else { // 服务器未返回文件大小,可设置为-1表示无法计算进度,或者给用户显示“正在下载中” percent = -1; }
3. 优化超时异常的特殊处理
在writeResponseBodyToDisk方法中,你可以单独捕获SocketTimeoutException,额外判断文件是否已经完整写入(比如对比文件实际大小和downloadSize,或者尝试打开文件验证完整性),如果文件确实完整,就返回true而非直接返回false:
catch (SocketTimeoutException e) { Log.i(TAG, "Download timed out, checking file integrity..."); // 这里可以添加文件完整性校验逻辑,比如检查文件是否存在且大小匹配下载的字节数 if (file.exists() && file.length() == downloadSize) { return true; } Log.i(TAG, "File integrity check failed: " + e.toString()); return false; }
为什么文件已下载却抛出异常?
这种情况通常是服务器在传输完所有文件数据后,没有及时关闭连接,OkHttp会继续等待更多数据,直到超过readTimeout时长后抛出异常。此时文件已经通过输出流完整写入磁盘,但异常会触发方法返回false,让你误以为下载失败。
内容的提问来源于stack exchange,提问作者Ehsan

