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
相关产品推荐
相关产品推荐

