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

Android Maps Compose MarkerState.setMarker IllegalStateException崩溃求助

问题分析与解决方案:android-maps-compose MarkerState关联冲突崩溃

问题概述

基于Jetpack Compose开发的Android应用,使用android-maps-compose库,Google Play后台收到重复崩溃报告,本地无法复现。两个崩溃栈核心指向同一个问题:MarkerState被同时关联到多个Marker实例,第二个崩溃栈明确提示MarkerState may only be associated with one Marker at a time。

崩溃栈信息

初始崩溃栈

Exception java.lang.IllegalStateException:
  at com.google.maps.android.compose.MarkerState.setMarker$maps_compose_release (Marker.kt:29)
  at com.google.maps.android.compose.MarkerNode.onAttached (Marker.kt:5)
  at com.google.maps.android.compose.MapApplier.insertBottomUp (MapApplier.kt:13)
  at com.google.maps.android.compose.MapApplier.insertBottomUp (MapApplier.kt:13)
  at androidx.compose.runtime.changelist.Operation$PostInsertNodeFixup.execute (Operation.kt:26)
  at androidx.compose.runtime.changelist.Operations.executeAndFlushAllPendingOperations (Operations.kt:23)
  at androidx.compose.runtime.changelist.FixupList.executeAndFlushAllPendingFixups (FixupList.java:36)
  at androidx.compose.runtime.changelist.Operation$InsertSlotsWithFixups.execute (Operation.kt:36)
  at androidx.compose.runtime.changelist.Operations.executeAndFlushAllPendingOperations (Operations.kt:23)
  at androidx.compose.runtime.changelist.ChangeList.executeAndFlushAllPendingChanges (ChangeList.kt:3)
  at androidx.compose.runtime.CompositionImpl.applyChangesInLocked (Composition.kt:50)
  at androidx.compose.runtime.CompositionImpl.applyChanges (Composition.kt:6)
  at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke (Recomposer.kt:174)
  at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke (Recomposer.kt:174)
  at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame (AndroidUiFrameClock.android.kt:7)
  at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch (AndroidUiDispatcher.java:48)
  at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch (AndroidUiDispatcher.java:48)
  at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame (AndroidUiDispatcher.android.kt:48)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1648)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1659)
  at android.view.Choreographer.doCallbacks (Choreographer.java:1129)
  at android.view.Choreographer.doFrame (Choreographer.java:1045)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1622)
  at android.os.Handler.handleCallback (Handler.java:958)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:230)
  at android.os.Looper.loop (Looper.java:319)
  at android.app.ActivityThread.main (ActivityThread.java:8893)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:608)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103)

更新后崩溃栈

Exception java.lang.IllegalStateException: MarkerState may only be associated with one Marker at a time.
  at com.google.maps.android.compose.MarkerState.setMarker$maps_compose_release (Marker.kt:29)
  at com.google.maps.android.compose.MarkerNode.onAttached (Marker.kt:5)
  at com.google.maps.android.compose.MapApplier.insertBottomUp (MapApplier.kt:13)
  at com.google.maps.android.compose.MapApplier.insertBottomUp (MapApplier.kt:13)
  at androidx.compose.runtime.changelist.Operation$PostInsertNodeFixup.execute (Operation.kt:26)
  at androidx.compose.runtime.changelist.Operations.executeAndFlushAllPendingOperations (Operations.kt:23)
  at androidx.compose.runtime.changelist.FixupList.executeAndFlushAllPendingFixups (FixupList.java:36)
  at androidx.compose.runtime.changelist.Operation$InsertSlotsWithFixups.execute (Operation.kt:36)
  at androidx.compose.runtime.changelist.Operations.executeAndFlushAllPendingOperations (Operations.kt:23)
  at androidx.compose.runtime.changelist.ChangeList.executeAndFlushAllPendingChanges (ChangeList.kt:3)
  at androidx.compose.runtime.CompositionImpl.applyChangesInLocked (Composition.kt:50)
  at androidx.compose.runtime.CompositionImpl.applyChanges (Composition.kt:6)
  at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke (Recomposer.kt:174)
  at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke (Recomposer.kt:174)
  at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame (AndroidUiFrameClock.android.kt:7)
  at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch (AndroidUiDispatcher.java:48)
  at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch (AndroidUiDispatcher.java:48)
  at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame (AndroidUiDispatcher.android.kt:48)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1648)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1659)
  at android.view.Choreographer.doCallbacks (Choreographer.java:1129)
  at android.view.Choreographer.doFrame (Choreographer.java:1045)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1622)
  at android.os.Handler.handleCallback (Handler.java:958)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:230)
  at android.os.Looper.loop (Looper.java:319)
  at android.app.ActivityThread.main (ActivityThread.java:8893)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:608)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103)

代码问题定位

  1. Marker节点跟踪错误:使用forEach遍历markersToDisplay渲染Marker,未给每个MarkerComposable指定唯一key,导致Compose重组时无法正确识别元素唯一性,可能复用旧的MarkerNode,引发State关联冲突。
  2. 手动管理MarkerState生命周期遗漏:通过mutableMap手动维护MarkerState,但未在Marker从列表移除时清理对应的State,残留的State可能被新的Marker重复绑定。
  3. 重组时直接修改State:在重组流程中直接修改MarkerState.position,可能导致Compose状态更新与节点绑定的时序冲突。

修复方案

1. 使用items替代forEach,确保元素唯一性

将MapMarkersAndCircles中的forEach替换为items,并指定marker.id作为唯一key,让Compose正确跟踪每个Marker的生命周期:

@Composable
private fun MapMarkersAndCircles(
    viewModel: AdaptedMapViewModel
) {
    val markers = viewModel.markersToDisplay.value
    // 使用items并指定唯一key
    items(markers, key = { it.id }) { marker ->
        // 每个Marker对应唯一的MarkerState,由remember管理生命周期
        val markerState = remember(marker.id) {
            MarkerState(position = marker.latLng()!!)
        }

        // 用LaunchedEffect安全更新位置,避免重组时直接修改State
        LaunchedEffect(marker.latLng()) {
            markerState.position = marker.latLng()!!
        }

        val groupEnabled = viewModel.groups.value.find { it.id == marker.groupId }?.enable ?: true
        val isVisible = groupEnabled && marker.active

        MarkerComposable(
            tag = marker.id,
            state = markerState,
            anchor = if (marker.emoji.isNotEmpty()) Offset(0.5F, 0.5F) else Offset(0.5F, 1F),
            visible = isVisible,
        ) {
            Icon(
                painter = painterResource(id = R.drawable.ic_marker_fill),
                contentDescription = null,
                tint = markerColor(viewModel.mapStyle.value),
                modifier = Modifier.size(40.dp)
            )
        }

        marker.circles.sortedByDescending { it.radius() }.forEach { circle ->
            MapCircle(viewModel, marker, circle)
        }
    }
}

2. 自动更新markersToDisplay,避免手动触发

在ViewModel中使用combine自动监听markers、groups、sortMode的变化,自动更新待显示标记列表,减少手动触发的频繁重组:

class AdaptedMapViewModel(
    private val userPreferencesManager: UserPreferencesManager,
    private val appDataBaseManager: AppDataBaseManager,
) : ViewModel() {

    private val _markersToDisplay = mutableStateOf(listOf<FullDataMarker>())
    val markersToDisplay: State<List<FullDataMarker>> = _markersToDisplay

    private val _mapType = mutableStateOf(userPreferencesManager.defaultAppMapType())
    val mapType: State<MapType> = _mapType

    private val _mapStyle = mutableStateOf(MapStyle.STANDARD)
    val mapStyle: State<MapStyle> = _mapStyle

    private val _initiated = mutableStateOf(false)

    val sortMode: State<SortOrder> = userPreferencesManager.sortOrder
    val circleStyle = userPreferencesManager.circleStyle
    val markers = appDataBaseManager.markers
    val groups = appDataBaseManager.markerGroups

    init {
        viewModelScope.launch {
            // 自动组合三个数据源的变化,更新标记列表
            combine(markers, groups, sortMode) { markersList, groupsList, sortOrder ->
                markersList.generalOrder(null, groupsList, sortOrder).filter { it.active }
            }.collect { filteredMarkers ->
                _markersToDisplay.value = filteredMarkers
            }
        }
    }

    fun init(darkTheme: Boolean) {
        if (_initiated.value) return
        _mapStyle.value = userPreferencesManager.defaultAppMapStyle(darkTheme)
        _initiated.value = true
    }
}

3. 移除手动维护的markerPositionStates

不再需要手动管理mutableMap存储MarkerState,由remember(marker.id)自动为每个Marker创建并维护独立的State,当Marker从列表中移除时,对应的State会被Compose自动回收,避免残留冲突。

排查建议

  1. 添加崩溃上下文日志:在应用中集成崩溃日志工具,记录崩溃时markersToDisplay的大小、当前排序/分组状态、涉及的markerId等信息,便于在Play Console中分析触发场景。
  2. 模拟极端场景:尝试快速切换排序模式、分组开关,或批量增删Marker,模拟高频重组场景,看能否复现崩溃。
  3. 升级库版本:检查android-maps-compose是否有新版本,部分旧版本存在MarkerState绑定的已知bug,升级可能解决问题。
  4. 分析设备特征:查看Play Console崩溃报告的设备分布,确认是否集中在特定Android版本或设备型号,针对性调试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:44:50