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

Jetpack Compose 内Kotlin协程发起Retrofit请求被触发两次问题咨询

问题根因

你遇到的重复调用问题核心是Jetpack Compose的重组特性导致的:

  • Compose的可组合函数会在状态变化时触发重组(重新执行),你直接将homeViewModel.assignSyncer()写在了setContent的Composable作用域内,首次组合+状态更新触发的第二次重组都会执行这个函数,所以出现两次调用。
  • 第一次调用assignSyncer()后,请求成功更新了receivedSyncer这个mutableStateOf持有状态,这个状态被HomeFrame组件依赖,直接触发当前Composable作用域重组,重组时再次执行了homeViewModel.assignSyncer(),就出现了第二次调用。
修复方案

1. 调整初始化逻辑的执行位置

你可以直接把assignSyncer()放在onCreate的setContent外面,因为ViewModel生命周期和Activity绑定,只会在Activity创建时执行一次:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    println("CP_1")
    // 移到此处,仅执行一次
    homeViewModel.assignSyncer()
    setContent {
        AppyTheme {
            HomeFrame(syncer = homeViewModel.receivedSyncer)
        }
    }
}

如果你确实需要在Composable内部执行单次初始化逻辑,可以使用LaunchedEffect包裹,指定固定key保证仅执行一次:

setContent {
    AppyTheme {
        HomeFrame(syncer = homeViewModel.receivedSyncer)
        LaunchedEffect(Unit) {
            homeViewModel.assignSyncer()
        }
    }
}

2. 可选优化:给APIService单例加线程安全保证

你当前的getInstance()方法不是线程安全的,协程并发调用场景下可能出现多次创建实例的问题,建议改用Kotlin lazy特性实现线程安全的单例:

companion object {
    private const val BASE_URL = "http://192.168.1.4:8080/"
    val apiService: APIService by lazy {
        println("CP_5")
        val gson = GsonBuilder().setLenient().create()
        val okHttpClient = OkHttpClient
            .Builder()
            .readTimeout(15, TimeUnit.SECONDS)
            .connectTimeout(10, TimeUnit.SECONDS)
            .build()
        Retrofit
            .Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build()
            .create(APIService::class.java)
    }

    fun getInstance(): APIService = apiService
}

3. 可选优化:给assignSyncer加防重复调用标记

避免后续其他场景触发重复调用,可以在ViewModel中增加执行标记:

class HomeViewModel : ViewModel() {
    var receivedSyncer: Syncer by mutableStateOf(Syncer())
    var connectionError: Boolean by mutableStateOf(false)
    // 执行标记防止重复请求
    private var syncerFetched = false

    fun assignSyncer() {
        if (syncerFetched) return
        syncerFetched = true
        println("CP_2")
        viewModelScope.launch {
            try {
                println("CP_3")
                val api = APIService.getInstance()
                receivedSyncer = api.requestSyncer(0L, 0L)
            } catch (e: Exception) {
                println("CP_EXC - ${e.message}")
                connectionError = true
                // 如果需要支持失败重试,可以在这里重置标记:syncerFetched = false
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 02:15:09