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

Release构建下Jetpack Compose Text组件停止更新问题求助

Release构建下Compose Text组件停止更新的问题分析与解决

问题重现

Debug构建中,显示通话时长的Text组件能随callDuration正常每秒更新;但在Release构建里,时长更新到"00:02"后就卡住不再变化,实际callDuration参数仍在每秒更新。只有触摸或交互屏幕其他组件时,时长才会恢复更新。

相关代码如下:

调用方式

CallStatusInfo(
    modifier = Modifier
        .align(Alignment.Center)
        .padding(vertical = 8.dp),
    callStatus = viewModel.callStatus,
    callDuration = viewModel.callDuration
)

Composable实现

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CallStatusInfo(modifier: Modifier = Modifier, callStatus: String, callDuration: String?) {
    Text(
        modifier = modifier.basicMarquee(),        
        text = when(callStatus){
             "ringing" -> "ringing"
             "connected" -> callDuration ?: ""
            else -> ""
        },
        style = MaterialTheme.typography.body2,
        fontWeight = FontWeight.W600,
        maxLines = 1
    )
}

ViewModel核心逻辑

var callDuration by mutableStateOf<String?>(null)
    private set

var callStatus by mutableStateOf<String>("connected")
    private set
private var startTime = Instant.now()

init {
    viewModelScope.launch {
        while (true) {
            val timeElapsed = System.currentTimeMillis() - startTime.toEpochMilli()
            val duration = formatTimeToDuration(timeElapsed)
            callDuration = duration
            delay(1.seconds)
        }
    }
}

问题原因

这是basicMarquee这个实验性修饰符在Release构建下的重组优化bug导致的:

  • Compose的Release构建会做激进的重组跳过优化,只有当Composable的输入状态被正确标记为"需要重组"时,UI才会更新。
  • basicMarquee内部的状态跟踪存在缺陷,当Text内容(由callDuration驱动)持续变化时,Marquee的动画状态没有触发Compose的重组检测,导致Compose认为UI无需更新,直到有外部交互(比如触摸)触发全局重组,才会刷新显示。

解决方案

方案1:替换实验性的basicMarquee

如果不需要跑马灯效果,直接移除basicMarquee()修饰符:

Text(
    modifier = modifier, // 移除basicMarquee
    text = when(callStatus){
         "ringing" -> "ringing"
         "connected" -> callDuration ?: ""
        else -> ""
    },
    style = MaterialTheme.typography.body2,
    fontWeight = FontWeight.W600,
    maxLines = 1
)

如果需要跑马灯,使用稳定的第三方实现或者自定义Marquee组件,避免依赖实验性API。

方案2:强制触发重组

通过给Text添加key修饰符,强制Compose在callDuration变化时重组Text:

Text(
    modifier = modifier.basicMarquee(),
    text = when(callStatus){
         "ringing" -> "ringing"
         "connected" -> callDuration ?: ""
        else -> ""
    },
    style = MaterialTheme.typography.body2,
    fontWeight = FontWeight.W600,
    maxLines = 1,
    key = callDuration // 添加key,强制重组
)

方案3:使用derivedStateOf计算文本内容

将文本内容用derivedStateOf包装,确保Compose能正确跟踪状态变化:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CallStatusInfo(modifier: Modifier = Modifier, callStatus: String, callDuration: String?) {
    val displayText by remember {
        derivedStateOf {
            when(callStatus){
                 "ringing" -> "ringing"
                 "connected" -> callDuration ?: ""
                else -> ""
            }
        }
    }

    Text(
        modifier = modifier.basicMarquee(),        
        text = displayText,
        style = MaterialTheme.typography.body2,
        fontWeight = FontWeight.W600,
        maxLines = 1
    )
}

临时方案的原理说明

你用的临时方案之所以有效,是因为:

var duration by remember { mutableStateOf("") }
LaunchedEffect(key1 = callDuration) {
    duration = callDuration ?: ""
}
  • LaunchedEffect会监听callDuration的变化,每次变化都会更新本地的mutableState变量duration。
  • 本地mutableState的变化会被Compose正确检测到,强制触发Text组件的重组,绕过了basicMarquee带来的重组抑制问题。但这只是绕开bug的临时手段,不是根本解决方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:55:15