use coroutines in the mesh service to move processing out of the GUI thread

1.2-legacy
geeksville 2020-04-04 14:37:44 -07:00
rodzic 1e34e77fe9
commit 83c1bfda69
5 zmienionych plików z 77 dodań i 34 usunięć

Wyświetl plik

@ -1,6 +1,12 @@
# High priority
Work items for soon alpha builds
* use states for meshservice: disconnected -> connected -> deviceasleep -> disconnected
* use compose on each page, but not for the outer wrapper
* one view per page: https://developer.android.com/guide/navigation/navigation-swipe-view-2
* use viewgroup with a unique ID https://developer.android.com/reference/kotlin/androidx/ui/core/package-summary#(android.view.ViewGroup).setContent(kotlin.Function0)
* let channel be editited
* make link sharing work
* finish map view
@ -19,6 +25,8 @@ Work items for soon alpha builds
# Medium priority
Features for future builds
* use coroutines in the services, to ensure low latency for both API calls and GUI operations https://developer.android.com/kotlin/coroutines &
https://medium.com/@kenkyee/android-kotlin-coroutine-best-practices-bc033fed62e7 & https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#5
* fix notification setSmallIcon parameter - change it to use the meshtastic icon
* ditch compose and use https://github.com/zsmb13/MaterialDrawerKt + https://github.com/Kotlin/anko/wiki/Anko-Layouts?
* describe user experience: devices always point to each other and show distance, you can send texts between nodes

Wyświetl plik

@ -82,6 +82,10 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
// You need to depend on the lite runtime library, not protobuf-java
// For now I'm not using javalite, because I want JSON printing
//implementation 'com.google.protobuf:protobuf-java:3.11.1'

Wyświetl plik

@ -19,3 +19,8 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# per https://medium.com/@kenkyee/android-kotlin-coroutine-best-practices-bc033fed62e7
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keepclassmembernames class kotlinx.** { volatile <fields>; }

Wyświetl plik

@ -11,6 +11,7 @@ import android.os.Build
import android.os.IBinder
import android.os.RemoteException
import androidx.annotation.RequiresApi
import androidx.annotation.UiThread
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_MIN
import com.geeksville.analytics.DataPair
@ -27,12 +28,26 @@ import com.geeksville.util.toRemoteExceptions
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.*
import com.google.protobuf.ByteString
import kotlinx.coroutines.*
import java.nio.charset.Charset
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
class RadioNotConnectedException() : Exception("Not connected to radio")
private val errorHandler = CoroutineExceptionHandler { _, exception ->
Exceptions.report(exception, "MeshService-coroutine", "coroutine-exception")
}
/// Wrap launch with an exception handler, FIXME, move into a utility lib
fun CoroutineScope.handledLaunch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
) = this.launch(context = context + errorHandler, start = start, block = block)
/**
* Handles all the communication with android apps. Also keeps an internal model
* of the network state.
@ -96,6 +111,9 @@ class MeshService : Service(), Logging {
IRadioInterfaceService.Stub.asInterface(it)
}
private val serviceJob = Job()
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
/*
see com.geeksville.mesh broadcast intents
// RECEIVED_OPAQUE for data received from other nodes
@ -117,7 +135,7 @@ class MeshService : Service(), Logging {
private var lastSendMsec = 0L
override fun onLocationResult(locationResult: LocationResult) {
exceptionReporter {
serviceScope.handledLaunch {
super.onLocationResult(locationResult)
var l = locationResult.lastLocation
@ -163,6 +181,7 @@ class MeshService : Service(), Logging {
* per https://developer.android.com/training/location/change-location-settings
*/
@SuppressLint("MissingPermission")
@UiThread
private fun startLocationRequests() {
if (fusedLocationClient == null) {
GeeksvilleApplication.analytics.track("location_start") // Figure out how many users needed to use the phone GPS
@ -375,6 +394,7 @@ class MeshService : Service(), Logging {
radio.close()
super.onDestroy()
serviceJob.cancel()
}
@ -712,14 +732,17 @@ class MeshService : Service(), Logging {
if (!myNodeInfo!!.hasGPS) {
// If we have at least one other person in the mesh, send our GPS position otherwise stop listening to GPS
if (numOnlineNodes >= 2)
startLocationRequests()
else
stopLocationRequests()
serviceScope.handledLaunch(Dispatchers.Main) {
if (numOnlineNodes >= 2)
startLocationRequests()
else
stopLocationRequests()
}
} else
debug("Our radio has a built in GPS, so not reading GPS in phone")
}
/// Called when we gain/lose connection to our radio
private fun onConnectionChanged(c: Boolean) {
debug("onConnectionChanged connected=$c")
@ -773,41 +796,42 @@ class MeshService : Service(), Logging {
// Important to never throw exceptions out of onReceive
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
serviceScope.handledLaunch {
debug("Received broadcast ${intent.action}")
when (intent.action) {
RadioInterfaceService.RADIO_CONNECTED_ACTION -> {
try {
onConnectionChanged(intent.getBooleanExtra(EXTRA_CONNECTED, false))
debug("Received broadcast ${intent.action}")
when (intent.action) {
RadioInterfaceService.RADIO_CONNECTED_ACTION -> {
try {
onConnectionChanged(intent.getBooleanExtra(EXTRA_CONNECTED, false))
// forward the connection change message to anyone who is listening to us. but change the action
// to prevent an infinite loop from us receiving our own broadcast. ;-)
intent.action = ACTION_MESH_CONNECTED
explicitBroadcast(intent)
} catch (ex: RemoteException) {
// This can happen sometimes (especially if the device is slowly dying due to killing power, don't report to crashlytics
warn("Abandoning reconnect attempt, due to errors during init: ${ex.message}")
// forward the connection change message to anyone who is listening to us. but change the action
// to prevent an infinite loop from us receiving our own broadcast. ;-)
intent.action = ACTION_MESH_CONNECTED
explicitBroadcast(intent)
} catch (ex: RemoteException) {
// This can happen sometimes (especially if the device is slowly dying due to killing power, don't report to crashlytics
warn("Abandoning reconnect attempt, due to errors during init: ${ex.message}")
}
}
}
RadioInterfaceService.RECEIVE_FROMRADIO_ACTION -> {
val proto =
MeshProtos.FromRadio.parseFrom(
intent.getByteArrayExtra(
EXTRA_PAYLOAD
)!!
)
info("Received from radio service: ${proto.toOneLineString()}")
when (proto.variantCase.number) {
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(
proto.packet
)
RadioInterfaceService.RECEIVE_FROMRADIO_ACTION -> {
val proto =
MeshProtos.FromRadio.parseFrom(
intent.getByteArrayExtra(
EXTRA_PAYLOAD
)!!
)
info("Received from radio service: ${proto.toOneLineString()}")
when (proto.variantCase.number) {
MeshProtos.FromRadio.PACKET_FIELD_NUMBER -> handleReceivedMeshPacket(
proto.packet
)
else -> TODO("Unexpected FromRadio variant")
else -> TODO("Unexpected FromRadio variant")
}
}
}
else -> TODO("Unexpected radio interface broadcast")
else -> TODO("Unexpected radio interface broadcast")
}
}
}
}

Wyświetl plik

@ -3,6 +3,8 @@
buildscript {
ext.kotlin_version = '1.3.61'
ext.compose_version = '0.1.0-dev07'
ext.coroutines_version = "1.3.5"
repositories {
google()
jcenter()