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

Material3日期选择器触发API加载LazyColumn时主线程卡顿问题

问题描述

我尝试将API获取的数据展示在LazyColumn中,每次从Material3日期选择器选择日期时都会触发API调用,但应用运行时每次API调用都会在Logcat中出现#1、#2的警告栈信息。尽管已在LaunchedEffect中执行相关逻辑,API调用有时仍耗时长达15秒,以下是相关代码示例供分析:


相关代码示例

TodayMatchesByLeague Composable

@Composable
fun TodayMatchesByLeague(matchesList: List<TodayResponseItem?>)
    LazyColumn {
        items(matchesList.size) {
            TodayMatchItem(matchItem)
        }
    }
}

FetchTodayGamesData Composable

@Composable
fun FetchTodayGamesData(
    mainViewModel: MainViewModel = hiltViewModel()
) {
    val date = mainViewModel.dialogueDate.observeAsState().value!!
    if (mainViewModel.isClicked.value!!) {
        LaunchedEffect(key1 = null) {
            mainViewModel.getTodayMatches(
                date, 2023
            )
            mainViewModel.saveCalendarClicked(false)
        }
    }
    when (val state =
        mainViewModel.todayMatchesState.collectAsState().value
    ) {
        is TodayMatchesState.Empty -> {}
        is TodayMatchesState.Loading -> {}
        is TodayMatchesState.Error -> {}
        is TodayMatchesState.Success -> {
            TodayMatchesByLeague(
                matchesList = state.data.body()!!.response!!
            )
        }
    }
}

日期选择器逻辑

val selectedDate = rememberSaveable { mutableStateOf<LocalDate?>(LocalDate.now()) }
val localDate = selectedDate.value
viewModel.saveDialogueDate(localDate.toString())
val datePickerState = rememberDatePickerState()
var sheetState by remember{ mutableStateOf(false) }
val selectedDate1 = Instant.ofEpochMilli(datePickerState.selectedDateMillis ?: 0)
          .atZone(ZoneId.systemDefault())
          .toLocalDate()

if (datePickerState.selectedDateMillis != null) {
          selectedDate.value = selectedDate1
          viewModel.saveCalendarClicked(true)
}

if (sheetState)
          BottomSheetDatePicker(
                state = datePickerState,
                onDismissRequest = { sheetState = false }
          )
IconButton(
          onClick = {
                sheetState = true
          }
) {
          Icon(imageVector = Icons.Default.CalendarMonth, contentDescription = "日历")
}

ViewModel代码

@HiltViewModel
class MainViewModel @Inject constructor(private val matchesRepository: MatchesRepository): ViewModel() {

    private var _todayMatchesState = MutableStateFlow<TodayMatchesState>(TodayMatchesState.Empty)
    val todayMatchesState: StateFlow<TodayMatchesState> = _todayMatchesState

    // 日历弹窗日期
    private var _dialogueDate = MutableLiveData("")
    val dialogueDate: LiveData<String> = _dialogueDate
    private var _isClicked = MutableLiveData(true)
    val isClicked: LiveData<Boolean> = _isClicked

    fun saveCalendarClicked(isClicked: Boolean) {
        _isClicked.value = isClicked
    }

    fun saveDialogueDate(date: String) {
        _dialogueDate.value = date
    }

    fun getTodayMatches(date: String, season: Int) {
        _todayMatchesState.value = TodayMatchesState.Loading

        viewModelScope.launch(Dispatchers.IO) {

            try {
                val todayMatchesResponse = matchesRepository.getTodayMatches(date, season)
                _todayMatchesState.value = TodayMatchesState.Success(todayMatchesResponse)
            }
            catch (exception: HttpException) {
                _todayMatchesState.value = TodayMatchesState.Error("出现错误")
            }
            catch (exception: IOException) {
                _todayMatchesState.value = TodayMatchesState.Error("无网络连接")
            }
        }
    }
}

Logcat警告信息

#1 APP_SCOUT_WARNING

事件:APP_SCOUT_WARNING 线程:main 调用栈:
    at java.lang.Thread.currentThread(Native Method)
    at java.lang.ThreadLocal.get(ThreadLocal.java:162)
    at android.os.Looper.myLooper(Looper.java:323)
    at kotlinx.coroutines.android.HandlerContext.isDispatchNeeded(HandlerDispatcher.kt:137)
    at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:160)
    at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:474)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:508)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:497)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:368)
    at kotlinx.coroutines.flow.StateFlowSlot.makePending(StateFlow.kt:284)
    at kotlinx.coroutines.flow.StateFlowImpl.updateState(StateFlow.kt:349)
    at kotlinx.coroutines.flow.StateFlowImpl.setValue(StateFlow.kt:316)
    at coil.compose.ConstraintsSizeResolver.measure-3p2s80s(AsyncImage.kt:209)
    at androidx.compose.ui.node.BackwardsCompatNode.measure-3p2s80s(BackwardsCompatNode.kt:312)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
    at androidx.compose.foundation.layout.SizeNode.measure-3p2s80s(Size.kt:838)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
    at androidx.compose.foundation.layout.PaddingNode.measure-3p2s80s(Padding.kt:397)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:255)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:254)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:488)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:501)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:257)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1622)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:39)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:623)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:599)
    at androidx.compose.foundation.layout.RowColumnMeasurementHelper.measureWithoutPlacing-_EkL_-Y(RowColumnMeasurementHelper.kt:120)
    at androidx.compose.foundation.layout.RowColumnMeasurePolicy.measure-3p2s80s(RowColumnImpl.kt:69)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:134)
    at androidx.compose.foundation.layout.PaddingNode.measure-3p2s80s(Padding.kt:397)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:255)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:254)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:488)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:501)

#2 APP_SCOUT_HANG

事件:APP_SCOUT_HANG 线程:main 调用栈:
    at java.lang.ref.Reference$SinkHolder.-$$Nest$sfgetfinalize_count(Unknown Source:2)
    at java.lang.ref.Reference.reachabilityFence(Reference.java:342)
    at libcore.util.NativeAllocationRegistry.registerNativeAllocation(NativeAllocationRegistry.java:281)
    at android.graphics.ColorFilter.getNativeInstance(ColorFilter.java:68)
    at android.graphics.Paint.getNativeInstance(Paint.java:738)
    at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:95)
    at androidx.compose.ui.graphics.AndroidCanvas.drawImageRect-HPBpro0(AndroidCanvas.android.kt:275)
    at androidx.compose.ui.graphics.drawscope.CanvasDrawScope.drawImage-AZ2fEMs(CanvasDrawScope.kt:257)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawImage-AZ2fEMs(Unknown Source:24)
    at androidx.compose.ui.graphics.drawscope.DrawScope.drawImage-AZ2fEMs$default(DrawScope.kt:566)
    at androidx.compose.ui.graphics.vector.DrawCache.drawInto(DrawCache.kt:102)
    at androidx.compose.ui.graphics.vector.VectorComponent.draw(Vector.kt:181)
    at androidx.compose.ui.graphics.vector.VectorPainter.onDraw(VectorPainter.kt:248)
    at androidx.compose.ui.graphics.painter.Painter.draw-x_KDEd0(Painter.kt:212)
    at androidx.compose.ui.draw.PainterNode.draw(PainterModifier.kt:342)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:376)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:365)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:265)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:373)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:365)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:962)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:183)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:66)
    at androidx.compose.material.ripple.AndroidRippleIndicationInstance.drawIndication(Ripple.android.kt:270)
    at androidx.compose.foundation.IndicationModifier.draw(Indication.kt:346)
    at androidx.compose.ui.node.BackwardsCompatNode.draw(BackwardsCompatNode.kt:350)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
    at androidx.compose.ui.node.LayoutNodeDrawScope.performDraw(LayoutNodeDrawScope.kt:76)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:55)
    at androidx.compose.foundation.BackgroundNode.draw(Background.kt:159)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:376)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:56)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:395)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:394)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:488)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:501)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:257)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:394)

问题分析与解决方案

1. API调用延迟问题

  • 核心原因:当前触发逻辑依赖isClicked状态,且LaunchedEffect使用null作为key,导致重组时可能重复触发;日期选择器的状态更新逻辑存在冗余回调。
  • 修复方案:
    • 将LaunchedEffect的key改为date,仅在日期变化时触发API,移除isClicked依赖:
      @Composable
      fun FetchTodayGamesData(
          mainViewModel: MainViewModel = hiltViewModel()
      ) {
          val date by mainViewModel.dialogueDate.observeAsState("")
          LaunchedEffect(date) {
              if (date.isNotEmpty()) {
                  mainViewModel.getTodayMatches(date, 2023)
              }
          }
          // 状态处理逻辑保持不变
      }
      
    • 移除日期选择器中viewModel.saveCalendarClicked(true)的调用,直接通过日期变化触发API。

2. Logcat警告问题

  • #1警告分析:Coil的AsyncImage在UI测量阶段更新StateFlow,导致主线程协程调度冲突。
    • 修复:给AsyncImage设置固定尺寸,避免动态测量时修改状态;升级Coil到最新稳定版本。
  • #2挂起警告分析:主线程绘制矢量图时Native内存分配阻塞,多因大量图标同时绘制或资源过大。
    • 修复:使用rememberVectorPainter复用矢量图标绘制实例;将常用图标提取为全局常量;检查并释放未回收的Native资源。

3. 其他优化点

  • 移除强制非空断言,添加空值判断避免崩溃:
    is TodayMatchesState.Success -> {
        val response = state.data.body()?.response ?: emptyList()
        TodayMatchesByLeague(matchesList = response)
    }
    
  • 添加API防抖,避免快速切换日期时重复调用:
    private val debounceJob = AtomicReference<Job?>()
    
    fun getTodayMatches(date: String, season: Int) {
        debounceJob.getAndSet(null)?.cancel()
        debounceJob.set(viewModelScope.launch(Dispatchers.IO) {
            delay(300) // 300ms防抖延迟
            _todayMatchesState.value = TodayMatchesState.Loading
            try {
                val todayMatchesResponse = matchesRepository.getTodayMatches(date, season)
                _todayMatchesState.value = TodayMatchesState.Success(todayMatchesResponse)
            } catch (e: HttpException) {
                _todayMatchesState.value = TodayMatchesState.Error("出现错误")
            } catch (e: IOException) {
                _todayMatchesState.value = TodayMatchesState.Error("无网络连接")
            }
        })
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 03:00:56