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)
代码问题定位
- Marker节点跟踪错误:使用
forEach遍历markersToDisplay渲染Marker,未给每个MarkerComposable指定唯一key,导致Compose重组时无法正确识别元素唯一性,可能复用旧的MarkerNode,引发State关联冲突。 - 手动管理MarkerState生命周期遗漏:通过
mutableMap手动维护MarkerState,但未在Marker从列表移除时清理对应的State,残留的State可能被新的Marker重复绑定。 - 重组时直接修改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自动回收,避免残留冲突。
排查建议
- 添加崩溃上下文日志:在应用中集成崩溃日志工具,记录崩溃时
markersToDisplay的大小、当前排序/分组状态、涉及的markerId等信息,便于在Play Console中分析触发场景。 - 模拟极端场景:尝试快速切换排序模式、分组开关,或批量增删Marker,模拟高频重组场景,看能否复现崩溃。
- 升级库版本:检查
android-maps-compose是否有新版本,部分旧版本存在MarkerState绑定的已知bug,升级可能解决问题。 - 分析设备特征:查看Play Console崩溃报告的设备分布,确认是否集中在特定Android版本或设备型号,针对性调试。
内容的提问来源于stack exchange,提问作者TheoG
相关产品推荐
相关产品推荐

