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

Compose Kotlin中SplashActivity ViewModel异步任务等待问题排查

解决启动页异步刷新Token后导航逻辑错误的问题

问题核心

当前代码中,SplashActivity检测到存在refreshToken后,调用refreshAccessToken的同时立刻跳转至MainActivity,完全未等待Token刷新异步任务完成。加上ViewModel里用runBlocking阻塞线程的写法不规范,且无法让Activity感知刷新完成状态,导致导航时新Token还未保存,后续逻辑出错。

修复方案

1. 更新状态类,添加刷新状态标记

修改SplashScreenState,增加追踪Token刷新状态的字段:

data class SplashScreenState(
    val isRefreshTokenExist: Boolean = false,
    val refreshToken: String? = null,
    val error: Boolean = false,
    val isRefreshingToken: Boolean = false, // 标记是否正在刷新Token
    val isTokenRefreshCompleted: Boolean = false // 标记刷新是否完成
)

2. 重构ViewModel的Token刷新逻辑

去掉runBlocking,改用viewModelScope处理异步任务,刷新开始和完成时更新状态,让Activity能感知:

@HiltViewModel
class SplashScreenViewModel @Inject constructor(
    private val tokenManager: TokenManager
) : ViewModel() {

    private val _state = MutableStateFlow(SplashScreenState())
    val state: StateFlow<SplashScreenState> = _state.asStateFlow()
   
    init {
        getRefreshToken()
    }

    fun refreshAccessToken(refreshToken: String) {
        viewModelScope.launch(Dispatchers.IO) {
            // 标记开始刷新
            _state.update { it.copy(isRefreshingToken = true) }
            try {
                // 注意:Retrofit实例建议全局单例,不要每次刷新都创建,这里先保留原有逻辑
                val loggingInterceptor = HttpLoggingInterceptor()
                loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
                val okHttpClient = OkHttpClient
                    .Builder()
                    .addInterceptor(loggingInterceptor)
                    .build()

                val retrofit = Retrofit.Builder()
                    .baseUrl(Constants.BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(okHttpClient)
                    .build()

                val service = retrofit.create(RegisterService::class.java)

                val response = service.refreshToken(model = RefreshToken(refreshToken))

                if (response.isSuccessful && response.body() != null) {
                    val newAccessToken = response.body()?.access_token
                    val newRefreshToken = response.body()?.refresh_token
                    if (newAccessToken != null && newRefreshToken != null) {
                        tokenManager.saveAccessToken(newAccessToken)
                        tokenManager.saveRefreshToken(newRefreshToken)
                    }
                }
                // 标记刷新完成
                _state.update { 
                    it.copy(
                        isRefreshingToken = false,
                        isTokenRefreshCompleted = true
                    ) 
                }
            } catch (e: Exception) {
                _state.update {
                    it.copy(
                        error = true,
                        isRefreshingToken = false,
                        isTokenRefreshCompleted = true
                    )
                }
            }
        }
    }

    private fun getRefreshToken() {
        viewModelScope.launch(Dispatchers.IO) {
            tokenManager.getRefreshToken().collect { refreshToken ->
                _state.update {
                    it.copy(
                        isRefreshTokenExist = refreshToken != null,
                        refreshToken = refreshToken
                    )
                }
            }
        }
    }
}

3. 修改SplashActivity的导航逻辑,等待刷新完成再跳转

只有当不需要刷新(无refreshToken)或者刷新完成后,才执行跳转逻辑:

@AndroidEntryPoint
@SuppressLint("CustomSplashScreen")
class SplashActivity : ComponentActivity() {

    private val viewModel: SplashScreenViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val splashScreen = installSplashScreen()
            splashScreen.setKeepOnScreenCondition { 
                // 保持启动页显示直到刷新完成或无需刷新
                viewModel.state.value.isRefreshingToken || (!viewModel.state.value.isTokenRefreshCompleted && viewModel.state.value.isRefreshTokenExist)
            }
        }
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.state.collect { state ->
                    val errMsg = "error occurs"
                    if (state.error) {
                        Toast.makeText(this@SplashActivity, errMsg, Toast.LENGTH_LONG).show()
                    }

                    // 两种情况跳转:1. 无refreshToken;2. 有refreshToken且已完成刷新
                    val shouldNavigate = !state.isRefreshTokenExist || (state.isRefreshTokenExist && state.isTokenRefreshCompleted)
                    if (shouldNavigate) {
                        val intent = Intent(this@SplashActivity, MainActivity::class.java)
                        intent.putExtra("isTokenExist", state.isRefreshTokenExist)
                        startActivity(intent)
                        finish()
                    } else if (state.isRefreshTokenExist && !state.isRefreshingToken) {
                        // 存在refreshToken且未开始刷新时,触发刷新
                        state.refreshToken?.let {
                            viewModel.refreshAccessToken(it)
                        }
                    }
                }
            }
        }
    }
}

4. 优化建议:避免用Intent传值,直接在MainActivity中检查Token状态

去掉Intent传值,让MainActivity直接从TokenManager获取状态,减少耦合:

// MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    lateinit var navHostController: NavHostController
    @Inject lateinit var tokenManager: TokenManager // 注入TokenManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 直接从TokenManager检查有效Token
        val isTokenExist = runBlocking { tokenManager.getAccessToken().firstOrNull() != null }

        // 屏幕尺寸、Google登出等原有代码...

        setContent {
            DiyetkolikTheme {
                navHostController = rememberNavController()
                Surface(modifier = Modifier.fillMaxSize()) {
                    RootNavGraph(navHostController = navHostController, isTokenExist)
                }
            }
        }
    }

    // onActivityResult等原有代码...
}

额外注意点

  • Retrofit和OkHttpClient实例建议全局单例化,不要每次刷新Token都重新创建,避免资源浪费和性能损耗。
  • 启动页的keepOnScreenCondition结合状态动态控制,可避免不必要的固定延迟。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:14:57