[Android Q&A, Help & Troubleshooting] uavengineer:
We are developing on Samsung Tab Active 4 Pro using Android Studio, kotlin and java. We are getting constant at random times app deadlocks. I am not new to developing with threads and synchronization methods however I am very new to Android development. I assume I am doing something wrong and will share what I am doing. But to set up the situation, we are using OSMDroid for the map viewing and are using layers of the map for draw icons that represent aircraft flying over the map. The main android app is done in kotlin and the UDP over ethernet is written in java files. Aircraft telemetry is received at about 1 hz over ethernet inside protobuf packets. The protobuf is received and parsed in a Listener java class using a thread and implemented in the run() method. In the udp handler where we receive and parse protobuf we call a kotlin object "class" (unfortunate use of terms with kotlin) that has a method UpdateEntity(). Here is a snippet of this code that handles a proto message type called Entity. EntityClass is the java wrapper created. This part is working fine, no issues here, just showing for context.

@Override
public void run() {
. . .
while (running) {
try {

. . .

socket.receive(DpReceive); // blocks here until UDP message arrives

. . .

MessageWrapperClass.MessageWrapper msg = MessageWrapperClass.MessageWrapper.parseDelimitedFrom(input);

if (msg.getMessageTypeEnum() == MessageWrapperClass.MessageWrapper.MessageTypeEnum.MESSAGE_TYPE_ENTITY) {
EntityClass.Entity entity = msg.getMessageEntity();
Log.d(TAG, "Listener:run (thread): Entity received\"" + entity.toString());

. . .

HostileEntity unit = new HostileEntity(
Utils.TrimUUID(entity.getEntityID().getValue().toString()),
//todo: we need callsign here
"red", //entity.getEntityID().getValue().toString(),
position, false);

Datasource.INSTANCE.UpdateEntity(unit); // <--------------- calling method that updates (moves positions) icons on map

}
. . .

Datasource.INSTANCE.UpdateEntity(unit); call accesses the share list. Here is that method in total. Note Datasource is that singleton-like object construct that has all public "static" methods.

/**
* [Datasource] holds the data that is utilized by the UI
*/
object Datasource {

val entityUnitListMutex = Mutex(false)

/*
This is the list of units. Any unit added to this list will get added to the map.
*/
val entityUnitList = mutableStateListOf<EntityUnit>()

fun UpdateEntity(entity:EntityUnit)
{
if (!entity.isPositionValid()) {
Log.e(ContentValues.TAG, "Datasource::UpdateEntity " + "Invalid entity passed:")
return
}
entity.lifeTime_ms = System.currentTimeMillis() // update its life time

try {

val existingEntity = FindEntity(entity.id)

runBlocking {
entityUnitListMutex.withLock {
if (existingEntity != null) {
entity.status = existingEntity.status
entity.missionCount = existingEntity.missionCount
val index = entityUnitList.indexOf(existingEntity)

entityUnitList[index] = entity

} else {
entityUnitList.add(entity)
}
}// end mutex
}
}
catch(e:Exception)
{
Log.e(ContentValues.TAG, "Datasource::UpdateEntity " + e.toString())
}
}
/*
Find the entity in the list of active entities
*/
fun FindEntity(entityName: String): EntityUnit? {
try {
var returnval: EntityUnit? = null
runBlocking {
entityUnitListMutex.withLock {
for (i in entityUnitList.indices) {
if (entityUnitList[i].id == entityName) {
returnval = entityUnitList[i]
break
}
}
}// end mutex
}
return returnval
} catch (e: Exception) {
Log.e(ContentValues.TAG, "Datasource::FindEntity " + e.toString())

return null
}
}

My understanding of kotlin and android is that you need to use a coroutine if you want to use a mutex. I used a mutex to protect the shared resource, the entityUnitList of type mutableStateListOf<EntityUnit>

The Listener is started up on the main, UI thread however it uses its own thread to receive udp packets. The call to Datasource::UpdateEntity() is done in that thread, not the UI thread. Datasource is used by the UI thread as well and it needs access to the entityUnitLst to read from. The UI thread does not write to any of the elements in the list nor does it add or subtract elements from that list.

So the problem is the app will lock (not crash and disappear) and be non-responsive or frozen. This happens at various times but typically within a minute or two. My best guess is a thead race condition.

There was someone else developing the UI portions of this app and they said the UI only updates when a data element of it changes. So in UpdateEntity() if we add to the list or copy entity to the existing entity that matches the ID in the list, overwriting the current entity then the UI updates. The connection there as I understand it is they made a data kotlin class that is connected to the UI so if any of its members change the UI updates. EntityUnit shown there are the elements in the share resource entityUnitList, shared between the Listener (thread) and UI.

Here's that state data class

data class FOXUiState(
var detailsShown: Boolean = false,
var currentStatus: String = "",
var currentEntity: EntityUnit? = null,
var autonomyStatus: AutonomyStatus = AutonomyStatus.BLANK,
var activeTab:Int = 0,
var centerMap:Boolean = false,
)

@Composable
fun MapRow(
uiState: FOXUiState,
onEntityClicked: (entityClicked: String) -> Unit,
modifier: Modifier = Modifier){
Row(modifier = modifier) {
AndroidView(
factory = { context ->

MapView(context).apply {
SetUpMap( uiState, this, onEntityClicked)
}
},
update = {
mapView ->
PutStuffOnMap( uiState, mapView, onEntityClicked)
}
)
}
}

fun PutStuffOnMap(
uiState: FOXUiState, mapView: MapView, onEntityClicked: (entityClicked: String) -> Unit
) {
try {
val items = ArrayList<OverlayItem>()

runBlocking {
Datasource.entityUnitListMutex.withLock {
for (i in entityUnitList.indices) {
val entity = entityUnitList[i]
PlotEntity(mapView, entity)?.let { items.add(it) }
}
}// end mutex
}
. . .

I am thinking we have a mutex locking unlocking issue where it stays locked and the UI blocks with that runBlocking{ } block waiting for the unlock. The Listener gets in and gets out when updating the entity pretty quick. The only link to the UI thread is Datasource::UpdateEntity(). When we comment out UpdateEntity() we never deaklock or freeze the app. We can even pre-load entityUnitList with entity class elements and interact with them again with no app freeze. Its only when we enable getting movement updates over ethernet (using USB/Ethernet adapter plugged into the tablet) when we freeze the app eventually.

So I think using runBlocking is wrong so what is the correct way to share that entityUnitList over multiple threads safely? We do not have to use a mutex and we could consider other list types that are better with thread synchronization.