You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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();
        }
    });
}

额外优化建议

  1. 考虑用editor.apply()替代commit():apply()是异步提交,不会阻塞主线程,除非你需要立刻读取存储的值,否则apply()性能更好。
  2. 可以把Token相关的操作封装成一个单例的TokenManager类,统一管理Token的获取、存储、校验,避免代码散落在各个地方。
  3. 处理Token获取失败的重试逻辑:比如可以在onTokenFailure()里添加有限次数的重试,提升用户体验。

内容的提问来源于stack exchange,提问作者msdev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 03:22:27