为什么需要设置超时?
想象一下,你向一个朋友发送消息,但他可能没收到手机,或者手机坏了,或者他故意不回你,如果你一直等下去,你的时间就被浪费了。

网络请求也是同理,设置超时的主要目的是:
- 避免应用卡死:如果网络连接中断或服务器响应极慢,没有超时机制,你的应用线程会一直等待,导致 ANR (Application Not Responding),用户体验极差。
- 提升用户体验:让用户知道请求失败了,而不是让界面一直处于“加载中”的状态,可以给出明确的错误提示,如“网络连接超时,请检查网络后重试”。
- 快速失败:及时释放占用的资源(如网络连接、线程),让应用可以去处理其他任务或发起重试。
超时的几种类型
网络请求的超时通常分为两种:
-
连接超时
- 定义:指客户端(你的App)与服务器建立TCP连接所花费的最长时间,这个过程包括三次握手。
- 场景:如果目标服务器IP地址错误、服务器宕机、或者网络中存在大量防火墙/NAT设备导致连接困难,都会导致连接超时。
- 经验值:通常设置为 10-15秒,对于移动网络,这个时间可以适当放宽一些。
-
读取超时
(图片来源网络,侵删)- 定义:指客户端成功连接到服务器后,从服务器读取第一个字节的数据开始,到完整接收所有数据为止,所允许的最长时间。
- 场景:连接已经建立,但服务器处理请求很慢(比如正在执行一个复杂的数据库查询),或者网络传输速度很慢(如信号差的地铁里),导致数据迟迟传不过来。
- 经验值:通常设置为 30-60秒,这个时间需要根据业务场景来定,如果服务器处理一个正常请求就需要20秒,那么设置30秒的超时就是不合理的。
如何在Android中设置超时?(主流方案)
在 Android 开发中,我们有多种网络请求库可供选择,它们设置超时的方式略有不同。
使用原生 HttpURLConnection (不推荐,但基础)
这是 Android 最底层的网络 API,虽然现在不推荐用于新项目,但了解它有助于理解超时的本质。
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
public class NetworkUtil {
public static String makeRequest(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 1. 设置连接超时时间 (单位: 毫秒)
connection.setConnectTimeout(15000); // 15秒
// 2. 设置读取超时时间 (单位: 毫秒)
connection.setReadTimeout(60000); // 60秒
// 设置请求方法
connection.setRequestMethod("GET");
// 获取响应码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取输入流...
return "Success";
} else {
return "Error: " + responseCode;
}
}
}
使用 OkHttp (强烈推荐)
OkHttp 是目前 Android 开发中最主流的网络请求库,功能强大且易用。
OkHttp 将超时设置封装在 OkHttpClient 的 Builder 中。
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpExample {
private final OkHttpClient client;
// 在构造函数中配置 OkHttpClient
public OkHttpExample() {
this.client = new OkHttpClient.Builder()
// 1. 设置连接超时
.connectTimeout(15, TimeUnit.SECONDS)
// 2. 设置读取超时
.readTimeout(60, TimeUnit.SECONDS)
// (可选) 设置写入超时,用于向服务器发送数据
.writeTimeout(60, TimeUnit.SECONDS)
.build();
}
public void run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
// 获取响应体
String responseBody = response.body().string();
System.out.println(responseBody);
}
}
}
OkHttp 超时设置的优点:
- 自动重试:OkHttp 在遇到超时或连接失败时,对于某些类型的请求(如
GET),会自动重试一次(最多一次),这对于不稳定的网络环境非常友好。 - 连接池:内置连接池,可以复用 TCP 连接,减少握手带来的延迟和超时风险。
- 配置清晰:所有配置都在
Builder中一目了然。
使用 Retrofit (基于 OkHttp)
Retrofit 是一个类型安全的 HTTP 客户端,它内部默认使用 OkHttp 作为网络引擎,设置超时的方式和 OkHttp 完全一样,都是在构建 OkHttpClient 时进行配置。
错误做法:很多人会尝试在 Retrofit 的接口方法上用 @Timeout 注解,但这通常用于 单个请求 的超时设置,而不是全局的默认超时。
// 1. 创建并配置 OkHttpClient
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build();
// 2. 使用这个 OkHttpClient 创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient) // <-- 关键步骤
.addConverterFactory(GsonConverterFactory.create())
.build();
// 3. 创建 API 接口实例
MyApiService apiService = retrofit.create(MyApiService.class);
// 4. 发起请求
apiService.getData().enqueue(new Callback<Data>() {
@Override
public void onResponse(Call<Data> call, Response<Data> response) {
// 处理成功响应
}
@Override
public void onFailure(Call<Data> call, Throwable t) {
// 处理失败,t 可能是超时异常
if (t instanceof SocketTimeoutException) {
// 明确是超时错误
Toast.makeText(context, "请求超时,请检查网络", Toast.LENGTH_SHORT).show();
} else {
// 其他网络错误,如 UnknownHostException
Toast.makeText(context, "网络连接失败", Toast.LENGTH_SHORT).show();
}
}
});
超时后的处理
当请求因超时而失败时,你需要在 onFailure 回调中进行处理。
-
识别超时异常:
- 连接超时和读取超时通常会抛出
java.net.SocketTimeoutException,捕获这个异常,你可以给用户一个明确的提示。
- 连接超时和读取超时通常会抛出
-
提供友好的用户反馈:
- 不要让界面一直转圈。
- 显示一个 Toast 或 SnackBar,提示“网络连接超时,请检查您的网络设置”。
- 显示一个重试按钮,让用户可以手动重新发起请求。
-
实现自动重试机制 (可选):
- 对于非幂等的请求(如
POST),绝对不能自动重试,因为这会导致数据重复提交。 - 对于幂等的请求(如
GET),可以设计一个自动重试机制,在onFailure中判断,如果失败原因是超时,则等待几秒后再次调用call.clone().enqueue(...),但要注意设置最大重试次数,避免无限重试。
- 对于非幂等的请求(如
最佳实践总结
- 选择合适的库:优先使用 OkHttp 或 Retrofit,它们是行业标准,稳定且功能完善。
- 设置合理的超时时间:
- 连接超时:
10-15秒是一个不错的起点。 - 读取超时:
30-60秒比较常见,但要根据你的后端性能进行调整。
- 连接超时:
- 区分超时类型:理解连接超时和读取超时的区别,有助于你排查问题,如果连接总是超时,问题可能出在 DNS 解析、防火墙或服务器本身,如果连接成功但读取超时,问题可能出在服务器处理慢或网络传输慢。
- 全局配置,局部覆盖:在
OkHttpClient.Builder中设置全局默认超时,如果某个特殊接口需要不同的超时时间,可以创建一个专用的OkHttpClient实例,或者使用 OkHttp 的timeout()方法(需要 OkHttp 4.0+)。 - 优雅处理失败:始终在
onFailure中捕获异常,并根据异常类型(如SocketTimeoutException)提供清晰的用户反馈和后续操作(如重试)。 - 考虑网络变化:可以结合
ConnectivityManager来监听网络状态的变化,在网络恢复后自动重试失败的请求,提升用户体验。
通过合理设置和处理网络超时,你可以显著提升 Android 应用的稳定性和用户满意度。
