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

Jetpack Compose大量输入后超长帧卡顿问题求助

问题描述

应用启动约10秒后,点击键盘按钮会突然出现严重性能暴跌,帧处理时长达到5-10秒。单次重组耗时骤增,且一旦出现慢帧,该状态会持续到应用重启。Profiler仅显示超长帧但未明确原因,内存使用初期稳定,后期每次输入字母内存会增加约1.2MB且无法释放。

相关代码片段

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GuessWordValidator.initValidWords(this)
    viewModel.setupGame()
    setContent {
        WordGameTheme {
            DailyWordScreen(
                state = viewModel.state.collectAsStateWithLifecycle(),
                onEvent = viewModel::onEvent
            )
        }
    }
}

ViewModel

class DailyWordViewModel(application: Application) : AndroidViewModel(application) {

    private val _state = MutableStateFlow(DailyWordState())
    private val context = getApplication<Application>().applicationContext

    val state
        get() = _state.stateIn(
            viewModelScope,
            SharingStarted.WhileSubscribed(5000L),
            DailyWordState()
        )
    // ...
}

ViewModel.onEvent()

fun onEvent(event: DailyWordEvent) {
    when (event) {
        is DailyWordEvent.OnCharacterPress -> {
            val currentGuess = _state.value.currentGuess
            currentGuess?.getLetterForInput?.let { guessLetter ->
                guessLetter.updateCharacter(event.character)
                _state.update {
                    it.copy(
                        currentGuess = currentGuess,
                        currentWord = currentGuess.displayWord
                    )
                }
            }
        }

        DailyWordEvent.OnDeletePress -> {
            val currentGuess = _state.value.currentGuess
            currentGuess?.getLetterToErase?.let { guessLetter ->
                guessLetter.updateCharacter(' ')
                _state.update {
                    it.copy(
                        currentGuess = currentGuess,
                        currentWord = currentGuess.displayWord
                    )
                }
            }
        }
    // ...
    }
}

问题根源

核心问题是状态对象的可变性导致Compose无法正确判断重组范围。即使将DailyWordState改为包含只读List,由于Kotlin中List只是只读接口(底层仍可能是MutableList),Compose仍会将其标记为可变类型,触发不必要的深度检查和过度重组,最终导致性能暴跌和内存泄漏。添加@Immutable注解能强制Compose将其视为不可变,但可以通过设计真正不可变的数据结构来替代注解方案。

无需@Immutable注解的最优数据结构方案

1. 定义完全不可变的数据类

确保所有状态对象及内部嵌套对象均为不可变数据类,禁止任何可变属性或可变集合:

// 不可变的字母状态类
data class GuessLetter(
    val character: Char
)

// 不可变的猜测状态类
data class CurrentGuess(
    val letters: List<GuessLetter>,
    val displayWord: String
) {
    // 纯函数式更新:返回新对象而非修改原对象
    fun updateLetterAt(index: Int, newChar: Char): CurrentGuess {
        val updatedLetters = letters.toList().toMutableList().apply {
            set(index, get(index).copy(character = newChar))
        }
        return copy(
            letters = updatedLetters,
            displayWord = updatedLetters.joinToString("") { it.character.toString() }
        )
    }

    fun eraseLastLetter(): CurrentGuess {
        val lastIndex = letters.indexOfLast { it.character != ' ' }
        if (lastIndex == -1) return this
        val updatedLetters = letters.toList().toMutableList().apply {
            set(lastIndex, get(lastIndex).copy(character = ' '))
        }
        return copy(
            letters = updatedLetters,
            displayWord = updatedLetters.joinToString("") { it.character.toString() }
        )
    }
}

// 顶层不可变状态类
data class DailyWordState(
    val currentGuess: CurrentGuess?,
    val currentWord: String
)

2. 调整ViewModel状态更新逻辑

修改onEvent方法,通过纯函数创建新的不可变对象,避免直接修改原状态:

fun onEvent(event: DailyWordEvent) {
    when (event) {
        is DailyWordEvent.OnCharacterPress -> {
            _state.update { currentState ->
                val currentGuess = currentState.currentGuess ?: return@update currentState
                // 替换为实际的索引逻辑
                val targetIndex = currentGuess.letters.indexOfFirst { it.character == ' ' }
                if (targetIndex == -1) return@update currentState
                val updatedGuess = currentGuess.updateLetterAt(targetIndex, event.character)
                currentState.copy(
                    currentGuess = updatedGuess,
                    currentWord = updatedGuess.displayWord
                )
            }
        }

        DailyWordEvent.OnDeletePress -> {
            _state.update { currentState ->
                val currentGuess = currentState.currentGuess ?: return@update currentState
                val updatedGuess = currentGuess.eraseLastLetter()
                currentState.copy(
                    currentGuess = updatedGuess,
                    currentWord = updatedGuess.displayWord
                )
            }
        }
    // ...
    }
}

3. 强制使用不可变集合

处理列表时,优先用listOf()创建固定不可变列表,或通过toList()将可变集合转为不可变集合,彻底切断对底层可变对象的引用。

方案优势

  • 完全匹配Compose状态设计原则,Compose可精准判断状态变化,消除不必要重组
  • 无需依赖@Immutable注解,代码逻辑自解释性更强
  • 纯函数式更新确保线程安全,规避并发问题
  • 内存泄漏问题自然解决,旧状态对象可被正常GC回收

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 21:30:30