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

Kotlin异步操作等待问题:Token刷新后页面跳转受阻

问题解决与最佳实践

核心问题分析

你当前代码里的refreshAccessToken使用了runBlocking(Dispatchers.IO),这会直接阻塞调用线程——而Activity的lifecycleScope默认运行在主线程,调用这个函数会导致Splash界面卡住无响应。同时现有写法无法感知异步操作的完成时机,自然做不到完成后再执行页面跳转。

解决方案代码修改

1. 重构MyViewModel.kt

移除runBlocking,改用ViewModel自带的viewModelScope(它会在ViewModel销毁时自动取消协程,避免内存泄漏),并将refreshAccessToken改为挂起函数,让调用方可以等待操作完成:

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

    private val _state = MutableStateFlow(SplashScreenState())
    val state: StateFlow<SplashScreenState> = _state.asStateFlow()

    // 改为挂起函数,移除runBlocking
    suspend fun refreshAccessToken(refreshToken: String) {
        try {
            val loggingInterceptor = HttpLoggingInterceptor()
            loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
            val okHttpClient = OkHttpClient
                .Builder()
                .addInterceptor(loggingInterceptor)
                //.authenticator(AuthAuthenticator(tokenManager))
                .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)
                }
            }
        } catch (e: Exception) {
            _state.update {
                it.copy(
                    error = true
                )
            }
        }
    }
}

data class SplashScreenState(
    val error: Boolean = false,
)

2. 修改SplashActivity.kt

  • 调用refreshAccessToken时等待操作完成,再执行跳转
  • 监听ViewModel的state变化,错误时显示提示
  • 优化协程逻辑,用first()代替collect获取单次token(如果TokenManager返回的是Flow)
@AndroidEntryPoint
@SuppressLint("CustomSplashScreen")
class SplashActivity : ComponentActivity() {

    private val viewModel: SplashScreenViewModel by viewModels()
    private val tokenManager = TokenManager(this)
    private val errMsg = "Bir hata ile karşılaşıldı."

    fun navigateToMain(isTokenExist: Boolean) {
        val intent = Intent(this@SplashActivity, MainActivity::class.java)
        intent.putExtra("isTokenExist", isTokenExist)
        startActivity(intent)
        finish()
    }

    private suspend fun handleSplashLogic() {
        // 仅获取一次refreshToken,用first()替代collect
        val refreshToken = tokenManager.getRefreshToken().first()
        if (refreshToken != null) {
            // 等待token刷新完成
            viewModel.refreshAccessToken(refreshToken)
            // 检查是否出现错误
            if (viewModel.state.value.error) {
                Toast.makeText(this@SplashActivity, errMsg, Toast.LENGTH_LONG).show()
            }
            navigateToMain(true)
        } else {
            navigateToMain(false)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        var keepSplashOn = true
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val splashScreen = installSplashScreen()
            splashScreen.setKeepOnScreenCondition { keepSplashOn }
        }
        super.onCreate(savedInstanceState)

        // 监听状态中的错误变化
        lifecycleScope.launch {
            viewModel.state.collect { state ->
                if (state.error) {
                    Toast.makeText(this@SplashActivity, errMsg, Toast.LENGTH_LONG).show()
                }
            }
        }

        // 执行启动逻辑,完成后关闭闪屏
        lifecycleScope.launch {
            handleSplashLogic()
            keepSplashOn = false
        }
    }
}

该场景的最佳实践

  1. ViewModel协程用viewModelScope:ViewModel自带的viewModelScope会在ViewModel销毁时自动取消所有协程,避免内存泄漏,绝对不要在ViewModel中使用runBlocking阻塞线程。
  2. 异步操作用挂起函数/Flow:让ViewModel的异步方法返回挂起函数或Flow,调用方(Activity/Fragment)通过协程等待或监听结果,而非阻塞线程。
  3. SplashScreen正确用法:Android 12+的闪屏可以通过setKeepOnScreenCondition控制显示时长,完成逻辑后将条件设为false,让系统自动关闭闪屏,避免手动控制的繁琐。
  4. 主线程只做UI操作:所有网络IO、本地存储操作必须放在IO协程调度器中,主线程仅处理UI更新。
  5. 状态统一管理:用StateFlow管理ViewModel状态,Activity/Fragment通过collect监听状态变化,实现UI与业务逻辑解耦。

内容的提问来源于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:52:23