4. 同步与异步请求调用方式:execute() 与 enqueue()、回调与响应处理
4.1 原理概述
Retrofit 为每个接口方法返回 Call<T> 对象。调用方必须从同步(execute())和异步(enqueue())两种模式中选择一种执行请求。同步调用会阻塞当前线程直到网络响应到达;异步调用则将请求放入后台线程池执行,并在主线程通过回调返回结果。正确的选择直接影响应用的响应性与稳定性。
4.2 同步调用:execute()
execute() 在调用线程上发起 HTTP 请求并阻塞等待响应。返回值类型为 Response<T>,其中包含状态码、头部以及反序列化后的数据体。由于会阻塞线程,此类调用严禁在主线程执行,否则将触发 NetworkOnMainThreadException。
典型使用场景:后台 service、JobScheduler、RxJava/Coroutine 封装的基础拦截。
// 同步调用示例(需在子线程中执行)
Call<User> call = userService.getUser("123");
try {
Response<User> response = call.execute();
if (response.isSuccessful()) {
User user = response.body();
// 处理 user
} else {
// 处理非 2xx 状态码
}
} catch (IOException e) {
// 网络或解析异常
}
4.3 异步调用:enqueue()
enqueue(Callback<T> callback) 将请求放入 Retrofit 内部的线程池执行,不阻塞主线程。请求结束后会在主线程调用回调方法。回调接口包含两个抽象方法:
onResponse(Call<T> call, Response<T> response)— 无论 HTTP 状态码如何,只要网络收到响应就触发(包括 4xx/5xx)。onFailure(Call<T> call, Throwable t)— 网络连接失败、超时、解析或请求取消时触发。
// 异步调用示例(安全在主线程调用)
Call<User> call = userService.getUser("123");
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User user = response.body();
// UI 更新等
} else {
// 非成功状态码,如 404
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
// 网络异常、超时、取消等
}
});
4.4 同步 vs 异步对比
| 维度 | 同步 execute() | 异步 enqueue() |
|---|---|---|
| 调用线程 | 任意(严禁主线程) | 任意(通常主线程) |
| 阻塞行为 | 阻塞直到响应或异常 | 立即返回,不阻塞 |
| 结果获取 | 直接返回 Response<T> | 通过 Callback 回调 |
| 错误处理 | try/catch IOException | onFailure + onResponse 状态码检查 |
| 适用场景 | 后台任务、批量请求编排 | UI 驱动、用户交互 |
| 线程安全 | 调用方自行保证子线程 | 回调自动在主线程 |
4.5 响应处理要点
- 使用
response.isSuccessful()判断 2xx:不要只依赖response.code() == 200,因为 201/204 也是成功。 - body() 为空处理:当响应体为空(如 204 No Content)或反序列化失败时
response.body()可能为 null,务必判空。 - errorBody() 读取:非成功状态码时可通过
response.errorBody()获取服务端返回的错误结构。 - 取消请求:
call.cancel()可中断尚未完成的请求,取消后回调不会执行。
4.6 常见错误与规避
错误 1:在主线程调用 execute()
会导致 NetworkOnMainThreadException 或 ANR。解决方案:始终使用 enqueue() 或使用协程/RxJava 调度器。
会导致 NetworkOnMainThreadException 或 ANR。解决方案:始终使用 enqueue() 或使用协程/RxJava 调度器。
错误 2:忽略 onFailure 回调
许多开发者仅关注 onResponse,漏掉 onFailure 中的异常捕获,导致应用崩溃或静默失败。建议在 onFailure 中统一记录日志并给用户提示。
许多开发者仅关注 onResponse,漏掉 onFailure 中的异常捕获,导致应用崩溃或静默失败。建议在 onFailure 中统一记录日志并给用户提示。
错误 3:重复调用 execute() 或 enqueue()
同一个 Call 实例只能执行一次,重复调用会抛出 IllegalStateException。每次应创建新 Call 或调用 clone()。
同一个 Call 实例只能执行一次,重复调用会抛出 IllegalStateException。每次应创建新 Call 或调用 clone()。
4.7 性能与稳定性优化建议
- UI 层强制使用
enqueue(),避免子线程管理复杂化。 - 对同步调用启用 OkHttp 超时(connectTimeout、readTimeout),防止无限阻塞。
- 后台批量请求建议使用
OkHttpClient自定义线程池,避免 Retrofit 默认池资源竞争。 - 考虑集成
CallAdapter将Call转换为CompletableFuture或RxJava Observable,更灵活控制线程与错误传播。
4.8 执行流程对比(SVG)
4.9 小结
同步调用 execute() 适用于后台批量脚本或需要在同一线程完成请求‑处理的场景;异步调用 enqueue() 是 Android UI 开发的标准做法。掌握回调中 onResponse / onFailure 的分支处理,并留意 call.cancel() 与生命周期绑定,即可构建稳定、可维护的网络层。