基于Token过期条件的Retrofit异步调用问题:首次API请求因Token未生成失败
解决异步Token获取导致首次请求Token为null的问题
我太懂这种异步时序坑了!你遇到的问题本质就是Retrofit的enqueue()是异步执行的——调用getToken()后,代码会立刻往下走,但Token的生成和存储要等到后台请求完成才会在onResponse()里执行,这时候你直接给retrievedToken赋值,它肯定还是null,自然会导致请求失败。
问题根源拆解
你的原逻辑里,当检测到Token无效时,调用getToken()后马上尝试用token变量,但这个变量要等到异步回调触发才会被赋值,时序完全对不上,首次请求自然拿不到有效Token。
解决方案:用回调接口确保时序正确
我们可以给getToken()加一个回调接口,只有当Token成功获取并存储后,再触发后续的API请求,彻底解决异步时序问题。
步骤1:定义Token回调接口
先创建一个接口用来通知Token获取的结果:
public interface TokenCallback { void onTokenSuccess(String validToken); void onTokenFailure(); }
步骤2:修改getToken()方法,加入回调逻辑
同时要补上空指针防护,避免Response失败或Body为空导致崩溃:
public void getToken(final TokenCallback callback){ Credentials credentials = new Credentials("public_user","public_pass"); Call<Authentication> call = service.getToken(credentials); call.enqueue(new Callback<Authentication>() { @RequiresApi(api = Build.VERSION_CODES.O) @Override public void onResponse(Call<Authentication> call, Response<Authentication> response) { // 先判断请求是否成功、Body是否有效 if (response.isSuccessful() && response.body() != null) { if (response.body().getCode() == 0) { token = response.body().getDocument(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx.getApplicationContext()); SharedPreferences.Editor editor = preferences.edit(); editor.putString("token", token); long expiryMillis = Instant.now().plus(Duration.ofSeconds(200000)).toEpochMilli(); editor.putLong("token_expiry_time", expiryMillis); editor.commit(); // 同步提交,确保立刻能读取到 // Token获取成功,通知回调执行后续请求 if (callback != null) { callback.onTokenSuccess(token); } } else { if (callback != null) { callback.onTokenFailure(); } } } else { if (callback != null) { callback.onTokenFailure(); } } } @Override public void onFailure(Call<Authentication> call, Throwable t) { if (callback != null) { callback.onTokenFailure(); } } }); }
步骤3:重构onCreateView()的请求逻辑
把API请求逻辑抽成单独方法,复用代码,同时确保只有Token有效时才发起请求:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { service = ApiClient.getClientStats().create(ApiInterface.class); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx.getApplicationContext()); // 简化Token有效性判断 boolean isTokenValid = checkTokenValidity(preferences); if (!isTokenValid) { // Token无效,先获取新Token,成功后再请求数据 getToken(new TokenCallback() { @Override public void onTokenSuccess(String newToken) { fetchMapData(newToken); } @Override public void onTokenFailure() { // 处理Token获取失败的情况,比如弹Toast提示用户 Toast.makeText(ctx, "Failed to authenticate, please try again", Toast.LENGTH_SHORT).show(); } }); } else { // Token有效,直接发起数据请求 String retrievedToken = preferences.getString("token", null); fetchMapData(retrievedToken); } // 返回你的布局视图 return inflater.inflate(R.layout.your_fragment_layout, container, false); } // 抽取出Token校验逻辑,让代码更清晰 private boolean checkTokenValidity(SharedPreferences preferences) { String token = preferences.getString("token", null); long expiryTime = preferences.getLong("token_expiry_time", 0); if (token == null || expiryTime == 0) { return false; } return expiryTime > Instant.now().toEpochMilli(); } // 抽取出数据请求逻辑,复用代码 private void fetchMapData(String token) { Call<Stat> call = service.getMapData("Bearer " + token); call.enqueue(new Callback<Stat>() { @Override public void onResponse(Call<Stat> call, Response<Stat> response) { if (response.isSuccessful() && response.body() != null) { // 这里处理数据请求成功的逻辑,比如更新UI } } @Override public void onFailure(Call<Stat> call, Throwable t) { // 处理数据请求失败的逻辑 Toast.makeText(ctx, "Failed to load data", Toast.LENGTH_SHORT).show(); } }); }
额外优化建议
- 考虑用
editor.apply()替代commit():apply()是异步提交,不会阻塞主线程,除非你需要立刻读取存储的值,否则apply()性能更好。 - 可以把Token相关的操作封装成一个单例的
TokenManager类,统一管理Token的获取、存储、校验,避免代码散落在各个地方。 - 处理Token获取失败的重试逻辑:比如可以在
onTokenFailure()里添加有限次数的重试,提升用户体验。
内容的提问来源于stack exchange,提问作者msdev
相关产品推荐
相关产品推荐

