如何在Java/Android中等待Retrofit的call.enqueue异步请求?
嘿,从C#转Java/Android开发,肯定会怀念C#里await那种“等拿到结果再往下走”的顺畅感吧!我完全懂你现在面对Retrofit的call.enqueue()异步回调时,看着日志顺序乱掉、没法直接在LoginActivity里按顺序处理登录结果的苦恼😅
下面给你几种可行的方案,既能模拟类似await的等待效果,又符合Android的开发规范:
方法一:使用Retrofit的同步执行方法call.execute()
Retrofit本身提供了同步执行请求的方法call.execute(),它会阻塞当前线程直到请求完成,拿到响应结果后再继续执行后续代码,和你熟悉的await逻辑很像。
但要划重点:绝对不能在UI线程调用这个方法! Android禁止在主线程进行网络请求,会直接抛出异常导致APP崩溃,所以必须把这段逻辑放到后台线程里。比如用ExecutorService来实现:
// 在LoginActivity的登录方法中 ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { try { // 构造登录请求Call Call<LoginResponse> loginCall = loginService.login(yourCredentials); // 同步执行,阻塞当前线程直到拿到响应 Response<LoginResponse> response = loginCall.execute(); // 请求完成后,回到主线程处理结果(更新UI等操作必须在主线程) runOnUiThread(() -> { if (response.isSuccessful()) { // 登录成功,执行后续逻辑(比如跳转主页) Log.d("LoginActivity", "登录成功,开始处理后续步骤"); } else { // 登录失败,提示错误 Log.d("LoginActivity", "登录失败:" + response.message()); } }); } catch (IOException e) { e.printStackTrace(); runOnUiThread(() -> { Log.d("LoginActivity", "网络请求出错:" + e.getMessage()); }); } }); executor.shutdown();
方法二:用CountDownLatch手动阻塞等待
如果你还是想用call.enqueue()的异步回调,又想让当前线程等待回调完成,可以用Java的CountDownLatch来实现“等待通知”的逻辑:
// 同样要在后台线程执行这段代码 ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { CountDownLatch latch = new CountDownLatch(1); final Response<LoginResponse>[] responseHolder = new Response[1]; // 用数组存结果,因为匿名内部类只能访问final变量 Call<LoginResponse> loginCall = loginService.login(yourCredentials); loginCall.enqueue(new Callback<LoginResponse>() { @Override public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) { responseHolder[0] = response; latch.countDown(); // 告诉等待的线程:请求完成了! } @Override public void onFailure(Call<LoginResponse> call, Throwable t) { t.printStackTrace(); latch.countDown(); // 失败也要通知,避免线程一直阻塞 } }); try { latch.await(); // 阻塞当前线程,直到countDown()被调用 } catch (InterruptedException e) { e.printStackTrace(); } // 现在可以安全地处理响应结果了 Response<LoginResponse> response = responseHolder[0]; runOnUiThread(() -> { if (response != null && response.isSuccessful()) { Log.d("LoginActivity", "登录成功"); } else { Log.d("LoginActivity", "登录失败"); } }); }); executor.shutdown();
这个方法的核心是用CountDownLatch作为“开关”,回调完成后触发开关,等待的线程再继续执行。同样要注意:绝对不能在UI线程调用latch.await(),会导致ANR(应用无响应)!
更推荐的Android异步方案:ViewModel + LiveData
其实Android开发更推荐用“响应式”的异步处理方式,而不是强行阻塞线程。比如用ViewModel处理网络请求,再通过LiveData把结果通知给Activity,这样既符合Android的生命周期管理,又能避免ANR:
第一步:定义LoginViewModel
public class LoginViewModel extends ViewModel { private final MutableLiveData<LoginResult> loginResult = new MutableLiveData<>(); private final LoginService loginService; public LoginViewModel() { // 初始化Retrofit的LoginService loginService = RetrofitClient.getInstance().create(LoginService.class); } // 给Activity提供观察结果的LiveData public LiveData<LoginResult> getLoginResult() { return loginResult; } // 登录方法,内部处理异步请求 public void performLogin(UserCredentials credentials) { Call<LoginResponse> loginCall = loginService.login(credentials); loginCall.enqueue(new Callback<LoginResponse>() { @Override public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) { if (response.isSuccessful()) { // 登录成功,发送成功结果 loginResult.postValue(new LoginResult.Success(response.body())); } else { // 登录失败,发送错误信息 loginResult.postValue(new LoginResult.Error("登录失败:" + response.message())); } } @Override public void onFailure(Call<LoginResponse> call, Throwable t) { loginResult.postValue(new LoginResult.Error("网络错误:" + t.getMessage())); } }); } // 定义结果封装类,区分成功和失败状态 public abstract static class LoginResult { public static class Success extends LoginResult { private final LoginResponse data; public Success(LoginResponse data) { this.data = data; } public LoginResponse getData() { return data; } } public static class Error extends LoginResult { private final String message; public Error(String message) { this.message = message; } public String getMessage() { return message; } } } }
第二步:在LoginActivity中观察结果
public class LoginActivity extends AppCompatActivity { private LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); // 初始化ViewModel loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class); // 观察登录结果,自动在主线程回调 loginViewModel.getLoginResult().observe(this, result -> { if (result instanceof LoginViewModel.LoginResult.Success) { // 登录成功,处理后续步骤(比如跳转主页) Log.d("LoginActivity", "登录成功,开始处理后续逻辑"); } else if (result instanceof LoginViewModel.LoginResult.Error) { // 登录失败,提示用户 Log.d("LoginActivity", ((LoginViewModel.LoginResult.Error) result).getMessage()); } }); // 登录按钮点击事件 findViewById(R.id.btn_login).setOnClickListener(v -> { UserCredentials credentials = new UserCredentials( ((EditText) findViewById(R.id.et_username)).getText().toString(), ((EditText) findViewById(R.id.et_password)).getText().toString() ); // 调用ViewModel的登录方法 loginViewModel.performLogin(credentials); }); } }
这种方式虽然不是直接“等待”请求完成,但能让你的代码逻辑更清晰,还能自动处理Activity的生命周期(比如屏幕旋转时不会丢失请求状态),是Android官方推荐的最佳实践哦!
内容的提问来源于stack exchange,提问作者Jeremy P

