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 IOExceptiononFailure + 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 调度器。
错误 2:忽略 onFailure 回调
许多开发者仅关注 onResponse,漏掉 onFailure 中的异常捕获,导致应用崩溃或静默失败。建议在 onFailure 中统一记录日志并给用户提示。
错误 3:重复调用 execute() 或 enqueue()
同一个 Call 实例只能执行一次,重复调用会抛出 IllegalStateException。每次应创建新 Call 或调用 clone()。

4.7 性能与稳定性优化建议

  • UI 层强制使用 enqueue(),避免子线程管理复杂化。
  • 对同步调用启用 OkHttp 超时(connectTimeout、readTimeout),防止无限阻塞。
  • 后台批量请求建议使用 OkHttpClient 自定义线程池,避免 Retrofit 默认池资源竞争。
  • 考虑集成 CallAdapter 将 Call 转换为 CompletableFuture 或 RxJava Observable,更灵活控制线程与错误传播。

4.8 执行流程对比(SVG)

同步 vs 异步请求执行流程 调用方 主线程(阻塞) Retrofit 网络响应 返回主线程 调用方 主线程(非阻塞) enqueue() 子线程池 回调(主线程) 无需额外子线程管理

4.9 小结

同步调用 execute() 适用于后台批量脚本或需要在同一线程完成请求‑处理的场景;异步调用 enqueue() 是 Android UI 开发的标准做法。掌握回调中 onResponse / onFailure 的分支处理,并留意 call.cancel() 与生命周期绑定,即可构建稳定、可维护的网络层。