协程范围内的处理列表导致异常

Processing list in Coroutines scope causes exception

我有一个功能可以在 Google 地图上显示标记(我称之为任务)。为了使其更清晰,我选择了一种遍历已显示任务和新任务的方法。如果任务已存在于地图中但不在新列表中,我将删除其标记。如果任务在两个列表中但其版本已更改,我修改其标记。如果任务只在新列表中,我会添加它的标记。 每次用户滚动浏览地图时,我都会根据滚动的位置搜索任务。用户可以多次滚动,这应该会停止该函数的上一次调用。

问题是有时我会得到这个异常

Fatal Exception: java.util.ConcurrentModificationException
       at java.util.ArrayList$Itr.next(ArrayList.java:860)
       at com.weryou.android.ui.missions.search.map.SearchMissionMapFragment$displayMissions.invokeSuspend(SearchMissionMapFragment.kt:435)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

还有这个例外

Fatal Exception: java.lang.IndexOutOfBoundsException: Index: 132, Size: 132
       at java.util.ArrayList.get(ArrayList.java:437)
       at kotlin.collections.CollectionsKt__MutableCollectionsKt.filterInPlace$CollectionsKt__MutableCollectionsKt(CollectionsKt__MutableCollectionsKt.java:284)
       at kotlin.collections.CollectionsKt__MutableCollectionsKt.removeAll(CollectionsKt__MutableCollectionsKt.java:269)
       at com.weryou.android.ui.missions.search.map.SearchMissionMapFragment$displayMissions.invokeSuspend(SearchMissionMapFragment.kt:220)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

这是我的功能

private val alreadyDisplayedMissions = ArrayList<Pair<MissionHeaderEntity, Marker>>()

private fun displayMissions(newMissions : List<MissionHeaderEntity>)
{
    lifecycleScope.launch(Dispatchers.Default) {
        
        val missionsToRemove : List<Pair<MissionHeaderEntity, Marker>> = alreadyDisplayedMissions.filter { oldMission ->
            
            val newVersionOfOldMission : MissionHeaderEntity? = newMissions.find { it.id == oldMission.first.id }
            
            newVersionOfOldMission == null || newVersionOfOldMission != oldMission.first
            
        }
        
        alreadyDisplayedMissions.removeAll { mission -> missionsToRemove.any { mission.first.id == it.first.id } }
        
        // Loop in the new missions to display in map all new missions with a determined location,
        // plus the tuto mission of the user if exists.
        val missionsToShow : List<Pair<MissionHeaderEntity, MarkerOptions>> = newMissions.filter { newMission ->
            (newMission.firstMission == true && newMission.owned == true)
            || alreadyDisplayedMissions.none { newMission.id == it.first.id }
            || (newMission.place?.address?.location?.latitude != null && newMission.place?.address?.location?.longitude != null)
        }.map { newMission ->
            
            // The icon resource of the marker to display
            val markerIcon : Drawable = when(newMission.state)
            {
                MissionState.BOOKED -> markerMissionBooked
                MissionState.AVAILABLE -> markerMissionAvailable
                MissionState.PRE_RELEASED -> markerMissionPreReleased
                else -> markerMissionOwned
            }
            
            // The icon of the marker to display .
            val markerBitmap : Bitmap = mapUtils.createMissionMarkerIcon(markerIcon, newMission.costing?.price.toCurrency(requireContext()))
            
            val markerLatitude : Double = newMission.place?.address?.location?.latitude!!
            val markerLongitude : Double = newMission.place?.address?.location?.longitude!!
        
            
            // The location of the marker to display.
            val markerLatLng = LatLng(markerLatitude, markerLongitude)
            
            val markerOptions : MarkerOptions = MarkerOptions()
                .position(markerLatLng)
                .icon(BitmapDescriptorFactory.fromBitmap(markerBitmap))
            
            Pair(newMission, markerOptions)
        }
        
        lifecycleScope.launch(Dispatchers.Main) {
            
            missionsToRemove.forEach { it.second.remove() }
            
            missionsToShow.forEach {
                
                val marker : Marker? = googleMap?.addMarker(it.second)
                marker?.tag = it.first
                
                alreadyDisplayedMissions.add(Pair(it.first, marker!!))
            }
        }
    }
}

我的函数有什么问题以及如何解决这些异常?

我假设 java.util.ConcurrentModificationException 异常发生在 alreadyDisplayedMissions 列表中。您可以尝试使用 Job 实例,由 launch 函数返回。您可以在下一个作业开始时取消当前作业。在这种情况下,您应该使用 isActive 属性 或 ensureActive 函数检查当前作业是否处于活动状态:

private var job:工作? =空

private fun displayMissions(newMissions : List<MissionHeaderEntity>) {
    job?.cancel()
    lifecycleScope.launch(Dispatchers.Default) {
        // use ensureActive() everywhere before using alreadyDisplayedMissions list
        ensureActive()
        ... 
    }
}

ensureActive() 如果作业不再处于活动状态,则抛出 CancellationException,因此在该函数之后将不会继续执行。